From 0d94e9ac7f3c92f112a78655cd0a78db71cbf85d Mon Sep 17 00:00:00 2001 From: Chicken-Bomes Date: Tue, 29 Apr 2014 00:35:55 +1000 Subject: [PATCH] Initial commit --- .gitignore | 2 + build/build.gradle | 173 +++++ build/build.properties | 4 + build/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 51106 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + build/gradlew | 164 +++++ build/gradlew.bat | 90 +++ build/settings.gradle | 19 + .../assets/codechickencore/asm/tweaks.asm | 40 ++ resources/cccmod.info | 11 + resources/dependancies.info | 9 + src/codechicken/core/CCUpdateChecker.java | 118 ++++ src/codechicken/core/ClassDiscoverer.java | 144 +++++ src/codechicken/core/ClientUtils.java | 60 ++ src/codechicken/core/CommonUtils.java | 63 ++ src/codechicken/core/IGuiPacketSender.java | 8 + src/codechicken/core/IStringMatcher.java | 6 + src/codechicken/core/ProfileTimer.java | 39 ++ src/codechicken/core/ReentrantCheck.java | 38 ++ src/codechicken/core/ReflectionManager.java | 316 +++++++++ src/codechicken/core/RegistrationHelper.java | 13 + src/codechicken/core/ServerUtils.java | 62 ++ src/codechicken/core/TaskProfiler.java | 66 ++ .../asm/CodeChickenAccessTransformer.java | 115 ++++ .../core/asm/CodeChickenCoreModContainer.java | 73 +++ .../asm/DefaultImplementationTransformer.java | 94 +++ .../core/asm/DelegatedTransformer.java | 154 +++++ .../core/asm/DependancyLister.java | 95 +++ .../core/asm/InterfaceDependancies.java | 14 + .../asm/InterfaceDependancyTransformer.java | 49 ++ .../core/asm/MCPDeobfuscationTransformer.java | 249 ++++++++ src/codechicken/core/asm/MethodASMifier.java | 74 +++ .../core/asm/TweakTransformer.java | 82 +++ .../core/commands/CoreCommand.java | 158 +++++ .../core/commands/PlayerCommand.java | 60 ++ .../core/commands/ServerCommand.java | 28 + .../core/featurehack/EntityRenderHook.java | 49 ++ .../core/featurehack/EntityUpdateHook.java | 47 ++ .../core/featurehack/FeatureHack.java | 38 ++ .../core/featurehack/GameDataManipulator.java | 40 ++ .../core/featurehack/LiquidTextures.java | 73 +++ .../featurehack/RenderEntityRenderHook.java | 26 + .../core/featurehack/RenderNull.java | 19 + .../featurehack/TweakTransformerHelper.java | 39 ++ .../core/featurehack/mc/TextureLavaFX.java | 113 ++++ .../featurehack/mc/TextureLavaFlowFX.java | 131 ++++ .../core/featurehack/mc/TextureWaterFX.java | 120 ++++ .../featurehack/mc/TextureWaterFlowFX.java | 133 ++++ .../core/fluid/ExtendedFluidTank.java | 118 ++++ src/codechicken/core/fluid/FluidUtils.java | 98 +++ src/codechicken/core/fluid/TankAccess.java | 32 + src/codechicken/core/gui/GuiCCButton.java | 75 +++ src/codechicken/core/gui/GuiCCTextField.java | 189 ++++++ src/codechicken/core/gui/GuiScreenWidget.java | 141 ++++ src/codechicken/core/gui/GuiScrollSlot.java | 361 +++++++++++ src/codechicken/core/gui/GuiWidget.java | 87 +++ .../core/gui/IGuiActionListener.java | 6 + .../core/internal/CCCEventHandler.java | 27 + .../core/inventory/ContainerExtended.java | 267 ++++++++ .../core/inventory/ContainerSynchronised.java | 69 ++ .../core/inventory/GuiContainerWidget.java | 133 ++++ .../core/inventory/IContainerSyncVar.java | 14 + .../core/inventory/IntegerSync.java | 34 + .../core/inventory/MappedInventoryAccess.java | 122 ++++ src/codechicken/core/inventory/SlotDummy.java | 73 +++ .../core/inventory/SlotDummyOutput.java | 19 + .../core/inventory/SlotHandleClicks.java | 16 + .../core/launch/CodeChickenCorePlugin.java | 173 +++++ src/codechicken/core/launch/DepLoader.java | 601 ++++++++++++++++++ .../obfuscator/ConstantObfuscator.java | 83 +++ .../obfuscator/DummyOutputStream.java | 13 + .../obfuscator/IHeirachyEvaluator.java | 20 + src/codechicken/obfuscator/ILogStreams.java | 9 + src/codechicken/obfuscator/ObfDirection.java | 34 + src/codechicken/obfuscator/ObfRemapper.java | 91 +++ .../obfuscator/ObfuscationMap.java | 343 ++++++++++ .../obfuscator/ObfuscationRun.java | 253 ++++++++ .../obfuscator/SystemLogStreams.java | 20 + 78 files changed, 7045 insertions(+) create mode 100644 .gitignore create mode 100644 build/build.gradle create mode 100644 build/build.properties create mode 100644 build/gradle/wrapper/gradle-wrapper.jar create mode 100644 build/gradle/wrapper/gradle-wrapper.properties create mode 100644 build/gradlew create mode 100644 build/gradlew.bat create mode 100644 build/settings.gradle create mode 100644 resources/assets/codechickencore/asm/tweaks.asm create mode 100644 resources/cccmod.info create mode 100644 resources/dependancies.info create mode 100644 src/codechicken/core/CCUpdateChecker.java create mode 100644 src/codechicken/core/ClassDiscoverer.java create mode 100644 src/codechicken/core/ClientUtils.java create mode 100644 src/codechicken/core/CommonUtils.java create mode 100644 src/codechicken/core/IGuiPacketSender.java create mode 100644 src/codechicken/core/IStringMatcher.java create mode 100644 src/codechicken/core/ProfileTimer.java create mode 100644 src/codechicken/core/ReentrantCheck.java create mode 100644 src/codechicken/core/ReflectionManager.java create mode 100644 src/codechicken/core/RegistrationHelper.java create mode 100644 src/codechicken/core/ServerUtils.java create mode 100644 src/codechicken/core/TaskProfiler.java create mode 100644 src/codechicken/core/asm/CodeChickenAccessTransformer.java create mode 100644 src/codechicken/core/asm/CodeChickenCoreModContainer.java create mode 100644 src/codechicken/core/asm/DefaultImplementationTransformer.java create mode 100644 src/codechicken/core/asm/DelegatedTransformer.java create mode 100644 src/codechicken/core/asm/DependancyLister.java create mode 100644 src/codechicken/core/asm/InterfaceDependancies.java create mode 100644 src/codechicken/core/asm/InterfaceDependancyTransformer.java create mode 100644 src/codechicken/core/asm/MCPDeobfuscationTransformer.java create mode 100644 src/codechicken/core/asm/MethodASMifier.java create mode 100644 src/codechicken/core/asm/TweakTransformer.java create mode 100644 src/codechicken/core/commands/CoreCommand.java create mode 100644 src/codechicken/core/commands/PlayerCommand.java create mode 100644 src/codechicken/core/commands/ServerCommand.java create mode 100644 src/codechicken/core/featurehack/EntityRenderHook.java create mode 100644 src/codechicken/core/featurehack/EntityUpdateHook.java create mode 100644 src/codechicken/core/featurehack/FeatureHack.java create mode 100644 src/codechicken/core/featurehack/GameDataManipulator.java create mode 100644 src/codechicken/core/featurehack/LiquidTextures.java create mode 100644 src/codechicken/core/featurehack/RenderEntityRenderHook.java create mode 100644 src/codechicken/core/featurehack/RenderNull.java create mode 100644 src/codechicken/core/featurehack/TweakTransformerHelper.java create mode 100644 src/codechicken/core/featurehack/mc/TextureLavaFX.java create mode 100644 src/codechicken/core/featurehack/mc/TextureLavaFlowFX.java create mode 100644 src/codechicken/core/featurehack/mc/TextureWaterFX.java create mode 100644 src/codechicken/core/featurehack/mc/TextureWaterFlowFX.java create mode 100644 src/codechicken/core/fluid/ExtendedFluidTank.java create mode 100644 src/codechicken/core/fluid/FluidUtils.java create mode 100644 src/codechicken/core/fluid/TankAccess.java create mode 100644 src/codechicken/core/gui/GuiCCButton.java create mode 100644 src/codechicken/core/gui/GuiCCTextField.java create mode 100644 src/codechicken/core/gui/GuiScreenWidget.java create mode 100644 src/codechicken/core/gui/GuiScrollSlot.java create mode 100644 src/codechicken/core/gui/GuiWidget.java create mode 100644 src/codechicken/core/gui/IGuiActionListener.java create mode 100644 src/codechicken/core/internal/CCCEventHandler.java create mode 100644 src/codechicken/core/inventory/ContainerExtended.java create mode 100644 src/codechicken/core/inventory/ContainerSynchronised.java create mode 100644 src/codechicken/core/inventory/GuiContainerWidget.java create mode 100644 src/codechicken/core/inventory/IContainerSyncVar.java create mode 100644 src/codechicken/core/inventory/IntegerSync.java create mode 100644 src/codechicken/core/inventory/MappedInventoryAccess.java create mode 100644 src/codechicken/core/inventory/SlotDummy.java create mode 100644 src/codechicken/core/inventory/SlotDummyOutput.java create mode 100644 src/codechicken/core/inventory/SlotHandleClicks.java create mode 100644 src/codechicken/core/launch/CodeChickenCorePlugin.java create mode 100644 src/codechicken/core/launch/DepLoader.java create mode 100644 src/codechicken/obfuscator/ConstantObfuscator.java create mode 100644 src/codechicken/obfuscator/DummyOutputStream.java create mode 100644 src/codechicken/obfuscator/IHeirachyEvaluator.java create mode 100644 src/codechicken/obfuscator/ILogStreams.java create mode 100644 src/codechicken/obfuscator/ObfDirection.java create mode 100644 src/codechicken/obfuscator/ObfRemapper.java create mode 100644 src/codechicken/obfuscator/ObfuscationMap.java create mode 100644 src/codechicken/obfuscator/ObfuscationRun.java create mode 100644 src/codechicken/obfuscator/SystemLogStreams.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccc5e8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build/build +/build/.gradle diff --git a/build/build.gradle b/build/build.gradle new file mode 100644 index 0000000..3f0b8cc --- /dev/null +++ b/build/build.gradle @@ -0,0 +1,173 @@ +// This sets us up for building a forge project - you need all of these +buildscript { + repositories { + mavenCentral() + maven { + name = "forge" + url = "http://files.minecraftforge.net/maven" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/repositories/snapshots/" + } + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:1.2-SNAPSHOT' + } +} + +// Apply the forge plugin - this adds all the magic for automatically obfuscating, deobfuscating etc +apply plugin: 'forge' + +// This is our group +group = "codechicken" // http://maven.apache.org/guides/mini/guide-naming-conventions.html +// This is our actual project within the group. +archivesBaseName = "CodeChickenCore" + +// Define properties file +ext.configFile = file "build.properties" + +configFile.withReader { + // Load config. It shall from now be referenced as simply config or project.config + def prop = new Properties() + prop.load(it) + project.ext.config = new ConfigSlurper().parse prop +} +dependencies { + compile "codechicken:CodeChickenLib:${config.mc_version}-${config.ccl_version}:dev" +} + +version = "${project.config.mod_version}." + System.getenv("BUILD_NUMBER") ?: "1" + +println config.mc_version + "-" + config.forge_version +// Setup the forge minecraft plugin data. Specify the preferred forge/minecraft version here +minecraft { + version = config.mc_version + "-" + config.forge_version +} + +// this sets our output jar to have a 'tag' of 'universal' on it +// It also adds the minecraft version in a custom version name +// The result is files named ---universal.jar +jar { + classifier = 'universal' + version = "${project.minecraft.version}-${project.version}" + manifest { + attributes 'FMLCorePlugin': 'codechicken.core.launch.CodeChickenCorePlugin' + } + +} +sourceSets { + main { + def root = project.projectDir.parentFile + java { + srcDir new File(root, "src") + } + resources { + srcDir new File(root, "resources") + } + } +} +processResources { + //redo task if any of these properties change + inputs.property "version", project.version + inputs.property "mc_version", config.mc_version + inputs.property "ccl_version", config.ccl_version + + // Replace properties in info files + from(sourceSets.main.resources.srcDirs) { + include '*.info' + expand 'version':project.version,'mc_version':config.mc_version,'ccl_version':config.ccl_version + } + // Copy everything else + from(sourceSets.main.resources.srcDirs) { + include 'assets/**/*.*' + } +} + +// Add in a source jar for people, should they desire to download such a thing +task sourceJar(type: Jar) { + from sourceSets.main.allSource + classifier = 'src' + version = "${project.minecraft.version}-${project.version}" +} + +// Add in an mcp named jar, for those who wish to run in a development environment (assuming mcp naming matches) +task deobfJar(type: Jar) { + from sourceSets.main.output + classifier = 'dev' + version = "${project.minecraft.version}-${project.version}" + manifest { + attributes 'FMLCorePlugin': 'codechicken.core.launch.CodeChickenCorePlugin' + } +} + +// Tell the artifact system about our extra jars +artifacts { + archives sourceJar, deobfJar +} + +// Configure an upload task. this is setup for uploading to files.minecraftforge.net. There are other examples around +uploadArchives { + dependsOn 'reobf' + repositories { + if (project.hasProperty("filesmaven")) { + logger.info('Publishing to files server') + + mavenDeployer { + configuration = configurations.deployJars + + repository(url: project.filesmaven.url) { + authentication(userName: project.filesmaven.username, privateKey: project.filesmaven.key) + } + + // This is just the pom data for the maven repo + pom { + groupId = project.group + // Force the maven upload to use the - syntax preferred at files + version = "${project.minecraft.version}-${project.version}" + artifactId = project.archivesBaseName + project { + name project.archivesBaseName + packaging 'jar' + description 'CodeChickenCore' + url 'https://github.com/Chicken-Bones/CodeChickenCore' + + scm { + url 'https://github.com/Chicken-Bones/CodeChickenCore' + connection 'scm:git:git://github.com/Chicken-Bones/CodeChickenCore.git' + developerConnection 'scm:git:git@github.com:Chicken-Bones/CodeChickenCore.git' + } + + issueManagement { + system 'github' + url 'https://github.com/Chicken-Bones/CodeChickenCore/issues' + } + + licenses { + license { + name 'GNU Lesser Public License (GPL), Version 2.1' + url 'https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt' + distribution 'repo' + } + } + + developers { + developer { + id 'chicken-bones' + name 'chicken-bones' + roles { role 'developer' } + } + } + } + } + } + } else { + logger.info('Publishing to repo folder') + + mavenDeployer { + pom.version = "${project.minecraft.version}-${project.version}" + repository(url: 'file://localhost/' + project.file('repo').getAbsolutePath()) + } + } + } +} \ No newline at end of file diff --git a/build/build.properties b/build/build.properties new file mode 100644 index 0000000..373e069 --- /dev/null +++ b/build/build.properties @@ -0,0 +1,4 @@ +mc_version=1.7.2 +forge_version=10.12.0.1054 +ccl_version=1.1.0.74 +mod_version=1.0 diff --git a/build/gradle/wrapper/gradle-wrapper.jar b/build/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..3c7abdf12790879c06b07176de29647f77aa4129 GIT binary patch literal 51106 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

CflOl1i4(-2^1KXo)&I5QlgjRKFQgM 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 z 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%0DvTus6fLI&f~`9YxjD=W5pR41LBYQt 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 literal 0 HcmV?d00001 diff --git a/build/gradle/wrapper/gradle-wrapper.properties b/build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..244a668 --- /dev/null +++ b/build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Mar 26 13:33:58 CDT 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-bin.zip diff --git a/build/gradlew b/build/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/build/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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 +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# 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\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +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" ] ; 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"` + + # 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 + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/build/gradlew.bat b/build/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/build/gradlew.bat @@ -0,0 +1,90 @@ +@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 + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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 Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +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/build/settings.gradle b/build/settings.gradle new file mode 100644 index 0000000..398e21f --- /dev/null +++ b/build/settings.gradle @@ -0,0 +1,19 @@ +/* + * This settings file was auto generated by the Gradle buildInit task + * by 'sunstrike' at '21/11/13 14:14' with Gradle 1.9 + * + * The settings file is used to specify which projects to include in your build. + * In a single project build this file can be empty or even removed. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user guide at http://gradle.org/docs/1.9/userguide/multi_project_builds.html + */ + +/* +// To declare projects as part of a multi-project build use the 'include' method +include 'shared' +include 'api' +include 'services:webservice' +*/ + +rootProject.name = 'CodeChickenLib' diff --git a/resources/assets/codechickencore/asm/tweaks.asm b/resources/assets/codechickencore/asm/tweaks.asm new file mode 100644 index 0000000..49b2c57 --- /dev/null +++ b/resources/assets/codechickencore/asm/tweaks.asm @@ -0,0 +1,40 @@ +#A bunch of vanilla tweaks + +list d_environmentallyFriendlyCreepers +ALOAD 0 +GETFIELD net/minecraft/entity/monster/EntityCreeper.field_70170_p:Lnet/minecraft/world/World; #worldObj +INVOKEVIRTUAL net/minecraft/world/World.func_82736_K()Lnet/minecraft/world/GameRules; #getGameRules +LDC "mobGriefing" +INVOKEVIRTUAL net/minecraft/world/GameRules.func_82766_b(Ljava/lang/String;)Z #getGameRuleBooleanValue + +list environmentallyFriendlyCreepers +ICONST_0 + +list softLeafReplace +ALOAD 0 +ALOAD 1 +ILOAD 2 +ILOAD 3 +ILOAD 4 +INVOKEVIRTUAL net/minecraft/block/Block.isAir(Lnet/minecraft/world/IBlockAccess;III)Z #forge method +IRETURN + +list n_doFireTick +LDC "doFireTick" +INVOKEVIRTUAL net/minecraft/world/GameRules.func_82766_b(Ljava/lang/String;)Z #getGameRuleBooleanValue +IFEQ LRET + +list doFireTick +ALOAD 1 +ILOAD 2 +ILOAD 3 +ILOAD 4 +ALOAD 5 +INVOKESTATIC codechicken/core/featurehack/TweakTransformerHelper.quenchFireTick(Lnet/minecraft/world/World;IIILjava/util/Random;)V +LSKIP + +list finiteWater +ALOAD 0 +GETFIELD net/minecraft/block/BlockDynamicLiquid.field_149815_a:I +ICONST_2 +IF_ICMPLT LEND \ No newline at end of file diff --git a/resources/cccmod.info b/resources/cccmod.info new file mode 100644 index 0000000..415b85f --- /dev/null +++ b/resources/cccmod.info @@ -0,0 +1,11 @@ +[ +{ + "modid": "CodeChickenCore", + "name": "CodeChicken Core", + "description": "Base common code for all chickenbones mods.", + "version": "0", + "mcversion": "1.7.2", + "url": "http://www.minecraftforum.net/topic/909223", + "authors": [ "ChickenBones" ] +} +] \ No newline at end of file diff --git a/resources/dependancies.info b/resources/dependancies.info new file mode 100644 index 0000000..a1959cd --- /dev/null +++ b/resources/dependancies.info @@ -0,0 +1,9 @@ +{ + "repo": "http://files.minecraftforge.net/CodeChickenLib/", + "file": "CodeChickenLib-universal-@MCVERSION@-@VERSION@.jar", + "dev": "CodeChickenLib-dev-@MCVERSION@-@VERSION@.jar", + "class": "codechicken.lib.asm.ASMHelper", + "version": "1.0.0.62", + "mcversion": "1.6.4", + "coreLib": true +} \ No newline at end of file diff --git a/src/codechicken/core/CCUpdateChecker.java b/src/codechicken/core/CCUpdateChecker.java new file mode 100644 index 0000000..c9501b2 --- /dev/null +++ b/src/codechicken/core/CCUpdateChecker.java @@ -0,0 +1,118 @@ +package codechicken.core; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; + +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.versioning.ComparableVersion; +import cpw.mods.fml.relauncher.FMLInjectionData; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.ChatComponentText; + +public class CCUpdateChecker +{ + private static final ArrayList updates = new ArrayList(); + + private static class ThreadUpdateCheck extends Thread + { + private final URL url; + private final String mod; + private final ComparableVersion version; + + public ThreadUpdateCheck(URL url, String mod, ComparableVersion version) + { + this.url = url; + this.mod = mod; + this.version = version; + + setName("CodeChicken Update Checker"); + } + + @Override + public void run() + { + try + { + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + BufferedReader read = new BufferedReader(new InputStreamReader(conn.getInputStream())); + String ret = read.readLine(); + read.close(); + if(!ret.startsWith("Ret: ")) + { + System.err.println("Failed to check update for "+mod+" returned: "+ret); + return; + } + ComparableVersion newversion = new ComparableVersion(ret.substring(5)); + if(newversion.compareTo(version) > 0) + { + addUpdateMessage("Version "+newversion+" of "+mod+" is available"); + } + } + catch(SocketTimeoutException ste) + {} + catch(UnknownHostException uhe) + {} + catch(IOException iox) + { + iox.printStackTrace(); + } + } + } + + public static void tick() + { + Minecraft mc = Minecraft.getMinecraft(); + if(!mc.inGameHasFocus) + return; + + synchronized(updates) + { + for(String s : updates) + mc.thePlayer.addChatMessage(new ChatComponentText(s)); + updates.clear(); + } + } + + public static void addUpdateMessage(String s) + { + synchronized(updates) + { + updates.add(s); + } + } + + public static String mcVersion() + { + return (String) FMLInjectionData.data()[4]; + } + + public static void updateCheck(String mod, String version) + { + try + { + new ThreadUpdateCheck( + new URL("http://www.chickenbones.craftsaddle.org/Files/New_Versions/version.php?"+ + "version="+mcVersion()+"&"+ + "file="+mod), + mod, new ComparableVersion(version)).start(); + } + catch(MalformedURLException e) + {} + } + + public static void updateCheck(String mod) + { + updateCheck(mod, Loader.instance().getIndexedModList().get(mod).getVersion()); + } +} diff --git a/src/codechicken/core/ClassDiscoverer.java b/src/codechicken/core/ClassDiscoverer.java new file mode 100644 index 0000000..6b6446a --- /dev/null +++ b/src/codechicken/core/ClassDiscoverer.java @@ -0,0 +1,144 @@ +package codechicken.core; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import net.minecraft.launchwrapper.Launch; +import org.objectweb.asm.tree.ClassNode; + +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.asm.ASMHelper; + +import com.google.common.collect.ImmutableList; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModClassLoader; +import cpw.mods.fml.relauncher.CoreModManager; + +public class ClassDiscoverer +{ + public IStringMatcher matcher; + public String[] superclasses; + public ArrayList> classes; + public ModClassLoader modClassLoader; + + public ClassDiscoverer(IStringMatcher matcher, Class... superclasses) { + this.matcher = matcher; + this.superclasses = new String[superclasses.length]; + for (int i = 0; i < superclasses.length; i++) + this.superclasses[i] = superclasses[i].getName().replace('.', '/'); + + classes = new ArrayList>(); + modClassLoader = (ModClassLoader) Loader.instance().getModClassLoader(); + } + + public ClassDiscoverer(Class... superclasses) { + this(new IStringMatcher() + { + public boolean matches(String test) {return true;} + }, superclasses); + } + + public ArrayList> findClasses() { + try { + findClasspathMods(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return classes; + } + + private void checkAddClass(String resource) { + try { + String classname = resource.replace(".class", "").replace("\\", ".").replace("/", "."); + byte[] bytes = Launch.classLoader.getClassBytes(classname); + if (bytes == null) + return; + + ClassNode cnode = ASMHelper.createClassNode(bytes); + for (String superclass : superclasses) + if (!cnode.interfaces.contains(superclass) && !cnode.superName.equals(superclass)) + return; + + addClass(classname); + } catch (IOException e) { + System.err.println("Unable to load class: " + resource); + e.printStackTrace(); + } + } + + private void addClass(String classname) { + try { + Class class1 = Class.forName(classname, true, modClassLoader); + classes.add(class1); + } catch (Exception cnfe) { + System.err.println("Unable to load class: " + classname); + cnfe.printStackTrace(); + } + } + + private void findClasspathMods() { + List knownLibraries = ImmutableList.builder() + .addAll(modClassLoader.getDefaultLibraries()) + .addAll(CoreModManager.getLoadedCoremods()).build(); + File[] minecraftSources = modClassLoader.getParentSources(); + HashSet searchedSources = new HashSet(); + for (File minecraftSource : minecraftSources) { + if (searchedSources.contains(minecraftSource.getAbsolutePath())) + continue; + searchedSources.add(minecraftSource.getAbsolutePath()); + + if (minecraftSource.isFile()) { + if (!knownLibraries.contains(minecraftSource.getName())) { + FMLLog.fine("Found a minecraft related file at %s, examining for codechicken classes", minecraftSource.getAbsolutePath()); + try { + readFromZipFile(minecraftSource); + } catch (Exception e) { + System.err.println("Failed to scan " + minecraftSource.getAbsolutePath() + ", the zip file is invalid"); + e.printStackTrace(); + } + } + } else if (minecraftSource.isDirectory()) { + FMLLog.fine("Found a minecraft related directory at %s, examining for codechicken classes", minecraftSource.getAbsolutePath()); + readFromDirectory(minecraftSource, minecraftSource); + } + } + } + + private void readFromZipFile(File file) throws IOException { + FileInputStream fileinputstream = new FileInputStream(file); + ZipInputStream zipinputstream = new ZipInputStream(fileinputstream); + do { + ZipEntry zipentry = zipinputstream.getNextEntry(); + if (zipentry == null) { + break; + } + String fullname = zipentry.getName().replace('\\', '/'); + int pos = fullname.lastIndexOf('/'); + String name = pos == -1 ? fullname : fullname.substring(pos + 1); + if (!zipentry.isDirectory() && matcher.matches(name)) { + checkAddClass(fullname); + } + } + while (true); + fileinputstream.close(); + } + + private void readFromDirectory(File directory, File basedirectory) { + for (File child : directory.listFiles()) { + if (child.isDirectory()) { + readFromDirectory(child, basedirectory); + } else if (child.isFile() && matcher.matches(child.getName())) { + String fullname = CommonUtils.getRelativePath(basedirectory, child); + checkAddClass(fullname); + } + } + } +} diff --git a/src/codechicken/core/ClientUtils.java b/src/codechicken/core/ClientUtils.java new file mode 100644 index 0000000..cbb107f --- /dev/null +++ b/src/codechicken/core/ClientUtils.java @@ -0,0 +1,60 @@ +package codechicken.core; + +import codechicken.core.internal.CCCEventHandler; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.network.NetworkManager; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; + +public class ClientUtils extends CommonUtils +{ + private static Minecraft mc() { + return Minecraft.getMinecraft(); + } + + public static World getWorld() { + return mc().theWorld; + } + + public static boolean inWorld()//TODO unused + { + return mc().getNetHandler() != null; + } + + public static void openSMPGui(int windowId, GuiScreen gui) { + mc().displayGuiScreen(gui); + if (windowId != 0) + mc().thePlayer.openContainer.windowId = windowId; + } + + public static float getRenderFrame() { + return CCCEventHandler.renderFrame; + } + + public static double getRenderTime() { + return CCCEventHandler.renderTime + getRenderFrame(); + } + + public static String getServerIP() { + try { + NetworkManager networkManager = mc().getNetHandler().getNetworkManager(); + String s = networkManager.getSocketAddress().toString(); + s = s.substring(s.indexOf("/") + 1); + return s; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SideOnly(Side.CLIENT) + public static String getWorldSaveName() { + return mc().isSingleplayer() ? MinecraftServer.getServer().getFolderName() : null; + } +} diff --git a/src/codechicken/core/CommonUtils.java b/src/codechicken/core/CommonUtils.java new file mode 100644 index 0000000..c7086c9 --- /dev/null +++ b/src/codechicken/core/CommonUtils.java @@ -0,0 +1,63 @@ +package codechicken.core; + +import java.io.File; +import java.lang.reflect.Array; +import java.util.LinkedList; +import java.util.List; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModContainer; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ChatAllowedCharacters; +import net.minecraft.util.ChatComponentText; +import net.minecraft.world.World; +import net.minecraftforge.common.DimensionManager; + +public class CommonUtils +{ + public static boolean isClient() { + return FMLCommonHandler.instance().getSide().isClient(); + } + + public static File getSaveLocation(int dim) { + File basesave = DimensionManager.getCurrentSaveRootDirectory(); + if (dim != 0) + return new File(basesave, DimensionManager.getWorld(dim).provider.getSaveFolder()); + + return basesave; + } + + public static String getWorldName(World world) { + return world.getWorldInfo().getWorldName(); + } + + public static int getDimension(World world) { + return world.provider.dimensionId; + } + + public static File getModsFolder() { + return new File(getMinecraftDir(), "mods"); + } + + public static File getMinecraftDir() { + return Minecraft.getMinecraft().mcDataDir; + } + + public static String getRelativePath(File parent, File child) { + if (parent.isFile() || !child.getPath().startsWith(parent.getPath())) + return null; + + return child.getPath().substring(parent.getPath().length() + 1); + } + + public static ModContainer findModContainer(String modID) { + for (ModContainer mc : Loader.instance().getModList()) + if (modID.equals(mc.getModId())) + return mc; + + return null; + } +} diff --git a/src/codechicken/core/IGuiPacketSender.java b/src/codechicken/core/IGuiPacketSender.java new file mode 100644 index 0000000..30b5783 --- /dev/null +++ b/src/codechicken/core/IGuiPacketSender.java @@ -0,0 +1,8 @@ +package codechicken.core; + +import net.minecraft.entity.player.EntityPlayerMP; + +public interface IGuiPacketSender +{ + void sendPacket(EntityPlayerMP player, int windowId); +} diff --git a/src/codechicken/core/IStringMatcher.java b/src/codechicken/core/IStringMatcher.java new file mode 100644 index 0000000..12e320c --- /dev/null +++ b/src/codechicken/core/IStringMatcher.java @@ -0,0 +1,6 @@ +package codechicken.core; + +public interface IStringMatcher +{ + public boolean matches(String test); +} diff --git a/src/codechicken/core/ProfileTimer.java b/src/codechicken/core/ProfileTimer.java new file mode 100644 index 0000000..86b4d8a --- /dev/null +++ b/src/codechicken/core/ProfileTimer.java @@ -0,0 +1,39 @@ +package codechicken.core; + +public class ProfileTimer +{ + public double decay; + public long startTime; + public long nanoTime; + public int scanCount; + public int logScans; + public String logName; + + public ProfileTimer() { + this(0.98); + } + + public ProfileTimer(double decay) { + this.decay = decay; + } + + public ProfileTimer log(String logName, int scans) { + this.logScans = scans; + this.logName = logName; + return this; + } + + public void start() { + startTime = System.nanoTime(); + } + + public void end() { + long t = System.nanoTime()-startTime; + nanoTime = (long)(nanoTime*decay+t*(1-decay)); + + scanCount++; + if(logScans > 0 && scanCount % logScans == 0) + System.out.println("Profiled " + logName + " " + nanoTime + "ns"); + + } +} diff --git a/src/codechicken/core/ReentrantCheck.java b/src/codechicken/core/ReentrantCheck.java new file mode 100644 index 0000000..4886912 --- /dev/null +++ b/src/codechicken/core/ReentrantCheck.java @@ -0,0 +1,38 @@ +package codechicken.core; + +public class ReentrantCheck +{ + private boolean entered; + + public ReentrantCheck() + { + entered = false; + } + + public boolean entered() + { + return entered; + } + + public void enter() + { + entered = true; + } + + public void exit() + { + entered = false; + } + + public static ThreadLocal threadLocal() + { + return new ThreadLocal() + { + @Override + protected ReentrantCheck initialValue() + { + return new ReentrantCheck(); + } + }; + } +} diff --git a/src/codechicken/core/ReflectionManager.java b/src/codechicken/core/ReflectionManager.java new file mode 100644 index 0000000..c52c92d --- /dev/null +++ b/src/codechicken/core/ReflectionManager.java @@ -0,0 +1,316 @@ +package codechicken.core; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; + +import codechicken.lib.asm.ObfMapping; + +public class ReflectionManager +{ + public static HashMap, Class> primitiveWrappers = new HashMap, Class>(); + + static + { + primitiveWrappers.put(Integer.TYPE, Integer.class); + primitiveWrappers.put(Short.TYPE, Short.class); + primitiveWrappers.put(Byte.TYPE, Byte.class); + primitiveWrappers.put(Long.TYPE, Long.class); + primitiveWrappers.put(Double.TYPE, Double.class); + primitiveWrappers.put(Float.TYPE, Float.class); + primitiveWrappers.put(Boolean.TYPE, Boolean.class); + primitiveWrappers.put(Character.TYPE, Character.class); + } + + public static boolean isInstance(Class class1, Object obj) + { + Class primitive = primitiveWrappers.get(class1); + if(primitive != null) + { + if(primitive == Long.class && Long.class.isInstance(obj)) + return true; + if((primitive == Long.class || primitive == Integer.class) && Integer.class.isInstance(obj)) + return true; + if((primitive == Long.class || primitive == Integer.class || primitive == Short.class) && Short.class.isInstance(obj)) + return true; + if((primitive == Long.class || primitive == Integer.class || primitive == Short.class || primitive == Byte.class) && Integer.class.isInstance(obj)) + return true; + + if(primitive == Double.class && Double.class.isInstance(obj)) + return true; + if((primitive == Double.class || primitive == Float.class) && Float.class.isInstance(obj)) + return true; + + return primitive.isInstance(obj); + } + return class1.isInstance(obj); + } + + public static Class findClass(String name) + { + return findClass(name, true); + } + + public static boolean classExists(String name) + { + return findClass(name, false) != null; + } + + public static Class findClass(String name, boolean init) + { + try + { + return Class.forName(name, init, ReflectionManager.class.getClassLoader()); + } + catch (ClassNotFoundException cnfe) + { + try + { + return Class.forName("net.minecraft.src."+name, init, ReflectionManager.class.getClassLoader()); + } + catch (ClassNotFoundException cnfe2) + { + return null; + } + } + } + + public static void setField(Class class1, Object instance, String name, Object value) throws IllegalArgumentException, IllegalAccessException + { + setField(class1, instance, new String[]{name}, value); + } + + public static void setField(Class class1, Object instance, String[] names, Object value) throws IllegalArgumentException, IllegalAccessException + { + for(Field field : class1.getDeclaredFields()) + { + boolean match = false; + for(String name : names) + { + if(field.getName().equals(name)) + { + match = true; + break; + } + } + if(!match) + { + continue; + } + + field.setAccessible(true); + field.set(instance, value); + return; + } + } + + public static void setField(Class class1, Object instance, int fieldindex, Object value) throws IllegalArgumentException, IllegalAccessException + { + Field field = class1.getDeclaredFields()[fieldindex]; + field.setAccessible(true); + field.set(instance, value); + } + + /** + * Static function + * void return type + * single name + */ + public static void callMethod(Class class1, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + callMethod(class1, null, new String[]{name}, params); + } + + /** + * Static function + * void return type + * single name + */ + public static void callMethod(Class class1, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + callMethod(class1, null, names, params); + } + + /** + * void return type + * single name + */ + public static void callMethod(Class class1, Object instance, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + callMethod(class1, null, instance, new String[]{name}, params); + } + + /** + * void return type + */ + public static void callMethod(Class class1, Object instance, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + callMethod(class1, null, instance, names, params); + } + + /** + * Static method + * single name + */ + public static R callMethod(Class class1, Class returntype, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + return callMethod(class1, returntype, null, new String[]{name}, params); + } + + /** + * Static method + */ + public static R callMethod(Class class1, Class returntype, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + return callMethod(class1, returntype, null, names, params); + } + + /** + * sinlge name + */ + public static R callMethod(Class class1, Class returntype, Object instance, String name, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + return callMethod(class1, returntype, instance, new String[]{name}, params); + } + + public static R callMethod(Class class1, Class returntype, Object instance, String[] names, Object... params) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException + { + nextMethod: for(Method method : class1.getDeclaredMethods()) + { + boolean match = false; + for(String name : names) + { + if(method.getName().equals(name)) + { + match = true; + break; + } + } + if(!match) + { + continue; + } + + Class[] paramtypes = method.getParameterTypes(); + if(paramtypes.length != params.length) + continue; + + for(int i = 0; i < params.length; i++) + { + if(!isInstance(paramtypes[i], params[i])) + continue nextMethod; + } + + method.setAccessible(true); + return (R)method.invoke(instance, params); + } + return null; + } + + public static T getField(Class class1, Class fieldType, Object instance, int fieldIndex) throws IllegalArgumentException, IllegalAccessException + { + Field field = class1.getDeclaredFields()[fieldIndex]; + field.setAccessible(true); + return (T) field.get(instance); + } + + public static T getField(Class class1, Class fieldType, Object instance, String fieldName) + { + try + { + Field field = class1.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(instance); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + public static T newInstance(Class class1, Object... params) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException + { + nextMethod: for(Constructor constructor : class1.getDeclaredConstructors()) + { + Class[] paramtypes = constructor.getParameterTypes(); + if(paramtypes.length != params.length) + continue; + + for(int i = 0; i < params.length; i++) + { + if(!isInstance(paramtypes[i], params[i])) + continue nextMethod; + } + + constructor.setAccessible(true); + return (T)constructor.newInstance(params); + } + return null; + } + + public static boolean hasField(Class class1, String fieldName) + { + try + { + class1.getDeclaredField(fieldName); + return true; + } + catch(NoSuchFieldException nfe) + { + return false; + } + } + + public static T get(Field field, Class class1) + { + return get(field, class1, null); + } + + public static T get(Field field, Class class1, Object instance) + { + try + { + return (T)field.get(instance); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + public static void set(Field field, Object value) + { + set(field, null, value); + } + + public static void set(Field field, Object instance, Object value) + { + try + { + field.set(instance, value); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + public static Field getField(ObfMapping mapping) + { + mapping.toRuntime(); + + try + { + Class clazz = ReflectionManager.class.getClassLoader().loadClass(mapping.javaClass()); + Field field = clazz.getDeclaredField(mapping.s_name); + field.setAccessible(true); + return field; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/src/codechicken/core/RegistrationHelper.java b/src/codechicken/core/RegistrationHelper.java new file mode 100644 index 0000000..f928de8 --- /dev/null +++ b/src/codechicken/core/RegistrationHelper.java @@ -0,0 +1,13 @@ +package codechicken.core; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; + +public class RegistrationHelper +{ + public static void registerHandledEntity(Class entityClass, String identifier) + { + EntityList.classToStringMapping.put(entityClass, identifier); + EntityList.stringToClassMapping.put(identifier, entityClass); + } +} diff --git a/src/codechicken/core/ServerUtils.java b/src/codechicken/core/ServerUtils.java new file mode 100644 index 0000000..5dd759f --- /dev/null +++ b/src/codechicken/core/ServerUtils.java @@ -0,0 +1,62 @@ +package codechicken.core; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.command.CommandHandler; +import net.minecraft.inventory.Container; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.command.ICommand; +import net.minecraft.util.IChatComponent; + +public class ServerUtils extends CommonUtils +{ + public static MinecraftServer mc() { + return MinecraftServer.getServer(); + } + + public static EntityPlayerMP getPlayer(String playername) { + return mc().getConfigurationManager().getPlayerForUsername(playername); + } + + public static List getPlayers() { + return mc().getConfigurationManager().playerEntityList; + } + + public static ArrayList getPlayersInDimension(int dimension) { + ArrayList players = new ArrayList(); + for (EntityPlayer p : getPlayers()) + if(p.dimension == dimension) + players.add(p); + + return players; + } + + public static void openSMPContainer(EntityPlayerMP player, Container container, IGuiPacketSender packetSender) { + player.getNextWindowId(); + player.closeContainer(); + packetSender.sendPacket(player, player.currentWindowId); + player.openContainer = container; + player.openContainer.windowId = player.currentWindowId; + player.openContainer.addCraftingToCrafters(player); + } + + public static boolean isPlayerOP(String username) { + return mc().getConfigurationManager().isPlayerOpped(username); + } + + public static boolean isPlayerOwner(String username) { + return mc().isSinglePlayer() && mc().getServerOwner().equalsIgnoreCase(username); + } + + public static void registerCommand(ICommand command) { + ((CommandHandler) mc().getCommandManager()).registerCommand(command); + } + + public static void sendChatToAll(IChatComponent msg) { + for(EntityPlayer p : getPlayers()) + p.addChatComponentMessage(msg); + } +} diff --git a/src/codechicken/core/TaskProfiler.java b/src/codechicken/core/TaskProfiler.java new file mode 100644 index 0000000..c1ca80d --- /dev/null +++ b/src/codechicken/core/TaskProfiler.java @@ -0,0 +1,66 @@ +package codechicken.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +public class TaskProfiler +{ + public static class ProfilerResult + { + public final String name; + public final long time; + public final double fraction; + + public ProfilerResult(String name, long time, long totalTime) + { + this.name = name; + this.time = time; + fraction = time/(double)totalTime; + } + } + + public HashMap times = new HashMap(); + + public String currentSection; + private long startTime; + private long totalTime; + + public void start(String section) + { + if(currentSection != null) + end(); + + currentSection = section; + startTime = System.nanoTime(); + } + + public void end() + { + long time = System.nanoTime()-startTime; + totalTime+=time; + + Long prev = times.get(currentSection); + if(prev == null) + prev = 0L; + times.put(currentSection, prev+time); + currentSection = null; + } + + public List getResults() + { + ArrayList results = new ArrayList(times.size()); + for(Entry e : times.entrySet()) + results.add(new ProfilerResult(e.getKey(), e.getValue(), totalTime)); + return results; + } + + public void clear() + { + if(currentSection != null) + end(); + times.clear(); + totalTime = 0; + } +} diff --git a/src/codechicken/core/asm/CodeChickenAccessTransformer.java b/src/codechicken/core/asm/CodeChickenAccessTransformer.java new file mode 100644 index 0000000..982a929 --- /dev/null +++ b/src/codechicken/core/asm/CodeChickenAccessTransformer.java @@ -0,0 +1,115 @@ +package codechicken.core.asm; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; + +import codechicken.core.launch.CodeChickenCorePlugin; +import com.google.common.collect.ImmutableBiMap; + +import codechicken.lib.asm.ObfMapping; + +import cpw.mods.fml.common.asm.transformers.AccessTransformer; +import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; + +public class CodeChickenAccessTransformer extends AccessTransformer +{ + private static CodeChickenAccessTransformer instance; + private static List mapFileList = new LinkedList(); + + private static boolean makeAllPublic; + private static Field f_classNameBiMap; + private static Object emptyMap = ImmutableBiMap.of(); + + public CodeChickenAccessTransformer() throws IOException + { + super(); + instance = this; + for(String file : mapFileList) + readMapFile(file); + mapFileList = null; + loadPublicConfig(); + } + + private void loadPublicConfig() { + if(ObfMapping.obfuscated) + return; + + makeAllPublic = CodeChickenCoreModContainer.config.getTag("dev.runtimePublic") + .setComment("Enabling this setting will make all minecraft classes public at runtime in MCP just as they are in modloader." + + "\nYou should ONLY use this when you are testing with a mod that relies on runtime publicity and doesn't include access transformers." + + "\nSuch mods are doing the wrong thing and should be fixed.") + .getBooleanValue(false); + + if(!makeAllPublic) + return; + + try + { + f_classNameBiMap = FMLDeobfuscatingRemapper.class.getDeclaredField("classNameBiMap"); + f_classNameBiMap.setAccessible(true); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + public static void addTransformerMap(String mapFile) + { + if(instance == null) + mapFileList.add(mapFile); + else + instance.readMapFile(mapFile); + } + + private void readMapFile(String mapFile) + { + CodeChickenCorePlugin.logger.debug("Adding Accesstransformer map: " + mapFile); + try + { + Method parentMapFile = AccessTransformer.class.getDeclaredMethod("readMapFile", String.class); + parentMapFile.setAccessible(true); + parentMapFile.invoke(this, mapFile); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + boolean setPublic = makeAllPublic && name.startsWith("net.minecraft."); + if(setPublic) + setClassMap(name); + bytes = super.transform(name, transformedName, bytes); + if(setPublic) + restoreClassMap(); + return bytes; + } + + private void restoreClassMap() { + try + { + f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, emptyMap); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + private void setClassMap(String name) { + try + { + f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, ImmutableBiMap.of(name.replace('.', '/'), "")); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/src/codechicken/core/asm/CodeChickenCoreModContainer.java b/src/codechicken/core/asm/CodeChickenCoreModContainer.java new file mode 100644 index 0000000..57b7c93 --- /dev/null +++ b/src/codechicken/core/asm/CodeChickenCoreModContainer.java @@ -0,0 +1,73 @@ +package codechicken.core.asm; + +import java.io.File; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import codechicken.core.CCUpdateChecker; +import codechicken.core.featurehack.LiquidTextures; +import codechicken.core.internal.CCCEventHandler; +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.config.ConfigFile; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import cpw.mods.fml.common.*; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; +import cpw.mods.fml.common.versioning.VersionRange; + +public class CodeChickenCoreModContainer extends DummyModContainer +{ + public static ConfigFile config; + + public static void loadConfig() { + if(config == null) + config = new ConfigFile(new File(CodeChickenCorePlugin.minecraftDir, "config/CodeChickenCore.cfg")).setComment("CodeChickenCore configuration file."); + } + + public CodeChickenCoreModContainer() { + super(MetadataCollection.from(MetadataCollection.class.getResourceAsStream("/cccmod.info"), "CodeChickenCore").getMetadataForId("CodeChickenCore", null)); + } + + @Override + public List getDependants() { + LinkedList deps = new LinkedList(); + deps.add(VersionParser.parseVersionReference("NotEnoughItems@[0,)")); + deps.add(VersionParser.parseVersionReference("EnderStorage@[1.4.3.6,)")); + deps.add(VersionParser.parseVersionReference("ChickenChunks@[1.3.3.4,)")); + deps.add(VersionParser.parseVersionReference("Translocator@[1.1.0.15,)")); + deps.add(VersionParser.parseVersionReference("WR-CBE|Core@[1.4.0.7,)")); + return deps; + } + + @Override + public boolean registerBus(EventBus bus, LoadController controller) { + bus.register(this); + return true; + } + + @Subscribe + public void preInit(FMLPreInitializationEvent event) { + if (event.getSide().isClient()) + LiquidTextures.init(); + } + + @Subscribe + public void init(FMLInitializationEvent event) { + if (event.getSide().isClient()) { + if (config.getTag("checkUpdates").getBooleanValue(true)) + CCUpdateChecker.updateCheck(getModId()); + FMLCommonHandler.instance().bus().register(new CCCEventHandler()); + } + } + + @Override + public VersionRange acceptableMinecraftVersionRange() { + return VersionParser.parseRange(CodeChickenCorePlugin.mcVersion); + } +} diff --git a/src/codechicken/core/asm/DefaultImplementationTransformer.java b/src/codechicken/core/asm/DefaultImplementationTransformer.java new file mode 100644 index 0000000..df16917 --- /dev/null +++ b/src/codechicken/core/asm/DefaultImplementationTransformer.java @@ -0,0 +1,94 @@ +package codechicken.core.asm; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.lib.asm.ASMHelper; +import codechicken.lib.asm.ClassHeirachyManager; +import codechicken.lib.asm.ObfMapping; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.LaunchClassLoader; + +public class DefaultImplementationTransformer implements IClassTransformer +{ + private static LaunchClassLoader cl = (LaunchClassLoader)ClassHeirachyManager.class.getClassLoader(); + private static ClassNode getClassNode(String name) { + try { + return ASMHelper.createClassNode(cl.getClassBytes(name.replace('/', '.'))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static class InterfaceImpl + { + public final String iname; + public ArrayList impls = new ArrayList(); + + public InterfaceImpl(String iname, String cname) + { + this.iname = iname; + HashSet names = new HashSet(); + ClassNode inode = getClassNode(iname); + for(MethodNode method : inode.methods) + names.add(method.name+method.desc); + + ClassNode cnode = getClassNode(cname); + for(MethodNode method : cnode.methods) + if(names.contains(method.name+method.desc)) { + impls.add(method); + method.desc = new ObfMapping(cnode.name, method.name, method.desc).toRuntime().s_desc; + } + } + + public boolean patch(ClassNode cnode) { + LinkedList names = new LinkedList(); + for(MethodNode method : cnode.methods) { + ObfMapping m = new ObfMapping(cnode.name, method.name, method.desc).toRuntime(); + names.add(m.s_name+m.s_desc); + } + + boolean changed = false; + for(MethodNode impl : impls) { + if(names.contains(impl.name+impl.desc)) + continue; + + MethodNode copy = new MethodNode(impl.access, impl.name, impl.desc, + impl.signature, impl.exceptions == null ? null : impl.exceptions.toArray(new String[0])); + ASMHelper.copy(impl, copy); + cnode.methods.add(impl); + changed = true; + } + return changed; + } + } + + private static HashMap impls = new HashMap(); + + public static void registerDefaultImpl(String iname, String cname) { + impls.put(iname.replace('.', '/'), new InterfaceImpl(iname, cname)); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + if(transformedName.startsWith("net.minecraft") || impls.isEmpty()) + return bytes; + + ClassNode cnode = ASMHelper.createClassNode(bytes); + boolean changed = false; + for(String iname : cnode.interfaces) { + InterfaceImpl impl = impls.get(iname); + if(impl != null) + changed |= impl.patch(cnode); + } + + return changed ? ASMHelper.createBytes(cnode, 0) : bytes; + } +} diff --git a/src/codechicken/core/asm/DelegatedTransformer.java b/src/codechicken/core/asm/DelegatedTransformer.java new file mode 100644 index 0000000..a96d80e --- /dev/null +++ b/src/codechicken/core/asm/DelegatedTransformer.java @@ -0,0 +1,154 @@ +package codechicken.core.asm; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Map; +import java.util.Stack; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; + +import net.minecraft.launchwrapper.Launch; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.LaunchClassLoader; + +import static codechicken.core.launch.CodeChickenCorePlugin.logger; + +public class DelegatedTransformer implements IClassTransformer +{ + private static ArrayList delegatedTransformers; + private static Method m_defineClass; + private static Field f_cachedClasses; + + public DelegatedTransformer() + { + delegatedTransformers = new ArrayList(); + try + { + m_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE); + m_defineClass.setAccessible(true); + f_cachedClasses = LaunchClassLoader.class.getDeclaredField("cachedClasses"); + f_cachedClasses.setAccessible(true); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + @Override + public byte[] transform(String name, String tname, byte[] bytes) + { + if (bytes == null) return null; + for(IClassTransformer trans : delegatedTransformers) + bytes = trans.transform(name, tname, bytes); + return bytes; + } + + public static void addTransformer(String transformer, JarFile jar, File jarFile) + { + logger.debug("Adding CCTransformer: " + transformer); + try + { + byte[] bytes; + bytes = Launch.classLoader.getClassBytes(transformer); + + if(bytes == null) + { + String resourceName = transformer.replace('.', '/')+".class"; + ZipEntry entry = jar.getEntry(resourceName); + if(entry == null) + throw new Exception("Failed to add transformer: "+transformer+". Entry not found in jar file "+jarFile.getName()); + + bytes = readFully(jar.getInputStream(entry)); + } + + defineDependancies(bytes, jar, jarFile); + Class clazz = defineClass(transformer, bytes); + + if(!IClassTransformer.class.isAssignableFrom(clazz)) + throw new Exception("Failed to add transformer: "+transformer+" is not an instance of IClassTransformer"); + + IClassTransformer classTransformer; + try + { + classTransformer = (IClassTransformer) clazz.getDeclaredConstructor(File.class).newInstance(jarFile); + } + catch(NoSuchMethodException nsme) + { + classTransformer = (IClassTransformer) clazz.newInstance(); + } + delegatedTransformers.add(classTransformer); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile) throws Exception + { + defineDependancies(bytes, jar, jarFile, new Stack()); + } + + private static void defineDependancies(byte[] bytes, JarFile jar, File jarFile, Stack depStack) throws Exception + { + ClassReader reader = new ClassReader(bytes); + DependancyLister lister = new DependancyLister(Opcodes.ASM4); + reader.accept(lister, 0); + + depStack.push(reader.getClassName()); + + for(String dependancy : lister.getDependancies()) + { + if(depStack.contains(dependancy)) + continue; + + try + { + Launch.classLoader.loadClass(dependancy.replace('/', '.')); + } + catch(ClassNotFoundException cnfe) + { + ZipEntry entry = jar.getEntry(dependancy+".class"); + if(entry == null) + throw new Exception("Dependancy "+dependancy+" not found in jar file "+jarFile.getName()); + + byte[] depbytes = readFully(jar.getInputStream(entry)); + defineDependancies(depbytes, jar, jarFile, depStack); + + logger.debug("Defining dependancy: "+dependancy); + + defineClass(dependancy.replace('/', '.'), depbytes); + } + } + + depStack.pop(); + } + + private static Class defineClass(String classname, byte[] bytes) throws Exception + { + Class clazz = (Class) m_defineClass.invoke(Launch.classLoader, classname, bytes, 0, bytes.length); + ((Map>)f_cachedClasses.get(Launch.classLoader)).put(classname, clazz); + return clazz; + } + + public static byte[] readFully(InputStream stream) throws IOException + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(stream.available()); + int r; + while ((r = stream.read()) != -1) + { + bos.write(r); + } + + return bos.toByteArray(); + } +} diff --git a/src/codechicken/core/asm/DependancyLister.java b/src/codechicken/core/asm/DependancyLister.java new file mode 100644 index 0000000..a732bbf --- /dev/null +++ b/src/codechicken/core/asm/DependancyLister.java @@ -0,0 +1,95 @@ +package codechicken.core.asm; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class DependancyLister extends ClassVisitor +{ + private static Pattern classdesc = Pattern.compile("L(.+?);"); + + private class DependancyMethodLister extends MethodVisitor + { + public DependancyMethodLister(int api) + { + super(api); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) + { + dependDesc(desc); + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) + { + dependDesc(desc); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) + { + depend(owner); + dependDesc(desc); + } + } + + private HashSet dependancies = new HashSet(); + + public DependancyLister(int api) + { + super(api); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) + { + dependDesc(desc); + return null; + } + + private void dependDesc(String desc) + { + Matcher match = classdesc.matcher(desc); + while(match.find()) + { + String s = match.group(); + depend(s.substring(1, s.length()-1)); + } + } + + private void depend(String classname) + { + dependancies.add(classname); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) + { + dependDesc(desc); + return new DependancyMethodLister(Opcodes.ASM4); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) + { + depend(superName); + if(interfaces != null) + for(String interfacename : interfaces) + depend(interfacename); + } + + public List getDependancies() + { + return new ArrayList(dependancies); + } +} diff --git a/src/codechicken/core/asm/InterfaceDependancies.java b/src/codechicken/core/asm/InterfaceDependancies.java new file mode 100644 index 0000000..9048a0f --- /dev/null +++ b/src/codechicken/core/asm/InterfaceDependancies.java @@ -0,0 +1,14 @@ +package codechicken.core.asm; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface InterfaceDependancies +{ + +} diff --git a/src/codechicken/core/asm/InterfaceDependancyTransformer.java b/src/codechicken/core/asm/InterfaceDependancyTransformer.java new file mode 100644 index 0000000..46f5758 --- /dev/null +++ b/src/codechicken/core/asm/InterfaceDependancyTransformer.java @@ -0,0 +1,49 @@ +package codechicken.core.asm; + +import java.util.Iterator; + +import net.minecraft.launchwrapper.Launch; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; + +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.asm.ASMHelper; +import codechicken.lib.asm.ObfMapping; + +import net.minecraft.launchwrapper.IClassTransformer; + +public class InterfaceDependancyTransformer implements IClassTransformer +{ + @Override + public byte[] transform(String name, String tname, byte[] bytes) { + if (bytes == null) return null; + ClassNode cnode = ASMHelper.createClassNode(bytes); + + boolean hasDependancyInterfaces = false; + if (cnode.visibleAnnotations != null) + for (AnnotationNode ann : cnode.visibleAnnotations) + if (ann.desc.equals(Type.getDescriptor(InterfaceDependancies.class))) { + hasDependancyInterfaces = true; + break; + } + + if (!hasDependancyInterfaces) + return bytes; + + hasDependancyInterfaces = false; + for (Iterator iterator = cnode.interfaces.iterator(); iterator.hasNext(); ) { + try { + Launch.classLoader.findClass(new ObfMapping(iterator.next()).toRuntime().javaClass()); + } catch (ClassNotFoundException cnfe) { + iterator.remove(); + hasDependancyInterfaces = true; + } + } + + if (!hasDependancyInterfaces) + return bytes; + + return ASMHelper.createBytes(cnode, 0); + } +} diff --git a/src/codechicken/core/asm/MCPDeobfuscationTransformer.java b/src/codechicken/core/asm/MCPDeobfuscationTransformer.java new file mode 100644 index 0000000..579437f --- /dev/null +++ b/src/codechicken/core/asm/MCPDeobfuscationTransformer.java @@ -0,0 +1,249 @@ +package codechicken.core.asm; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.Map.Entry; + +import javax.swing.JFileChooser; + +import cpw.mods.fml.relauncher.IFMLLoadingPlugin; +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.Launch; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.asm.ASMHelper; +import codechicken.lib.asm.CC_ClassWriter; +import codechicken.lib.asm.ObfMapping; +import codechicken.lib.config.ConfigFile; +import codechicken.lib.config.ConfigTag; +import codechicken.obfuscator.IHeirachyEvaluator; +import codechicken.obfuscator.ObfuscationRun; +import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; +import cpw.mods.fml.common.asm.transformers.AccessTransformer; +import cpw.mods.fml.common.asm.transformers.DeobfuscationTransformer; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.LaunchClassLoader; + +public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes, IHeirachyEvaluator +{ + public static class LoadPlugin implements IFMLLoadingPlugin + { + @Override + public String[] getASMTransformerClass() { + return new String[0]; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map data) { + load(); + } + + @Override + public String getAccessTransformerClass() { + return null; + } + } + + private static ObfuscationRun run; + private static List excludedPackages = new LinkedList(); + private static MCPDeobfuscationTransformer instance = new MCPDeobfuscationTransformer(); + + private static boolean activated; + + private static Field f_transformers; + private static Field f_modifiers; + private static Field f_Modifier_name; + private static Field f_Modifier_desc; + + static { + try { + f_transformers = LaunchClassLoader.class.getDeclaredField("transformers"); + f_modifiers = AccessTransformer.class.getDeclaredField("modifiers"); + Class c_Modifier = Class.forName(AccessTransformer.class.getName() + "$Modifier", false, Launch.classLoader); + f_Modifier_name = c_Modifier.getDeclaredField("name"); + f_Modifier_desc = c_Modifier.getDeclaredField("desc"); + + f_transformers.setAccessible(true); + f_modifiers.setAccessible(true); + f_Modifier_name.setAccessible(true); + f_Modifier_desc.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static Object get(Field f, Object inst) { + try { + return f.get(inst); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void set(Field f, Object inst, Object value) { + try { + f.set(inst, value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private static List getTransformers() { + return (List) get(f_transformers, Launch.classLoader); + } + + public static void load() { + CodeChickenCoreModContainer.loadConfig(); + ConfigFile config = CodeChickenCoreModContainer.config; + File mcDir = CodeChickenCorePlugin.minecraftDir; + + if (config.getTag("dev.deobfuscate") + .setComment("set to true to completely deobfuscate mcp names") + .getBooleanValue(!ObfMapping.obfuscated)) { + run = new ObfuscationRun(false, ObfMapping.MCPRemapper.getConfFiles(), + ObfuscationRun.fillDefaults(new HashMap())); + run.obf.setHeirachyEvaluator(instance); + run.setQuiet().parseMappings(); + for (String pkg : run.config.get("excludedPackages").split(";")) + excludedPackages.add(pkg); + + if (ObfMapping.obfuscated) { + ObfMapping.loadMCPRemapper(); + run.setSeargeConstants(); + getTransformers().add(instance); + } else { + getTransformers().add(0, instance);//insert transformer as first. + } + } + } + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + if (name.equals("cpw.mods.fml.common.Loader")) { + bytes = injectCallback(bytes); + activated = true; + } + + if (!activated || bytes == null) + return bytes; + + ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.EXPAND_FRAMES); + ClassWriter cw = new CC_ClassWriter(0, true); + run.remap(cnode, cw); + return cw.toByteArray(); + } + + private byte[] injectCallback(byte[] bytes) { + ClassNode cnode = ASMHelper.createClassNode(bytes); + MethodNode mnode = ASMHelper.findMethod(new ObfMapping(cnode.name, "", "()V"), cnode); + mnode.instructions.insert(new MethodInsnNode(INVOKESTATIC, "codechicken/core/asm/MCPDeobfuscationTransformer", "loadCallback", "()V")); + return ASMHelper.createBytes(cnode, 0); + } + + public static void loadCallback() { + if (ObfMapping.obfuscated) { + //move ourselves to the end + List transformers = getTransformers(); + for (Iterator iterator = transformers.iterator(); iterator.hasNext(); ) { + IClassTransformer t = iterator.next(); + if (t == instance || t instanceof DeobfuscationTransformer) + iterator.remove(); + } + transformers.add(instance); + } else { + //remap access transformers + for (IClassTransformer t : getTransformers()) + if (t instanceof AccessTransformer) + remapAccessTransformer(t); + } + } + + @SuppressWarnings("unchecked") + private static void remapAccessTransformer(IClassTransformer t) { + Multimap modifiers = (Multimap) get(f_modifiers, t); + Multimap t_modifiers = HashMultimap.create(); + for (Entry entry : modifiers.entries()) { + Object mod = entry.getValue(); + String m_owner = entry.getKey().replace('.', '/'); + String m_name = (String) get(f_Modifier_name, mod); + String m_desc = ((String) get(f_Modifier_desc, mod)).replace('.', '/'); + ObfMapping map; + if (m_name.equals("*")) { + map = new ObfMapping(m_owner); + map.s_name = m_name; + map.s_desc = m_desc; + } else { + try { + map = new ObfMapping(m_owner, m_name, m_desc).toClassloading(); + } catch (Exception e) { + new Object(); + continue; + } + } + + set(f_Modifier_name, mod, map.s_name); + set(f_Modifier_desc, mod, map.s_desc); + t_modifiers.put(map.javaClass(), mod); + } + set(f_modifiers, t, t_modifiers); + } + + @Override + public List getParents(ObfuscationEntry desc) { + try { + String name = ObfMapping.obfuscated ? desc.obf.s_owner : desc.mcp.s_owner; + name = name.replace('/', '.'); + byte[] bytes = Launch.classLoader.getClassBytes(name); + if (bytes != null) + return ObfuscationRun.getParents(ASMHelper.createClassNode(bytes)); + } catch (IOException e) { + } + return null; + } + + @Override + public boolean isLibClass(ObfuscationEntry desc) { + String name = desc.srg.s_owner; + for (String p : excludedPackages) + if (name.startsWith(p)) + return true; + + return false; + } + + public static String unmap(String name) { + if (run == null) + return null; + ObfuscationEntry e = run.obf.lookupMcpClass(name); + if (e == null) + return null; + return e.obf.s_owner; + } + + public static MCPDeobfuscationTransformer instance() { + return instance; + } +} diff --git a/src/codechicken/core/asm/MethodASMifier.java b/src/codechicken/core/asm/MethodASMifier.java new file mode 100644 index 0000000..8a40d79 --- /dev/null +++ b/src/codechicken/core/asm/MethodASMifier.java @@ -0,0 +1,74 @@ +package codechicken.core.asm; + +import java.io.File; +import java.io.PrintWriter; + +import net.minecraft.launchwrapper.Launch; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.util.Printer; +import org.objectweb.asm.util.TraceMethodVisitor; + +import codechicken.core.launch.CodeChickenCorePlugin; +import codechicken.lib.asm.ObfMapping; + +import static org.objectweb.asm.Opcodes.*; + +public class MethodASMifier extends ClassVisitor +{ + PrintWriter printWriter; + ObfMapping method; + Printer asmifier; + + public MethodASMifier(ObfMapping method, Printer printer, PrintWriter printWriter) { + super(ASM4); + this.method = method; + this.printWriter = printWriter; + asmifier = printer; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (method.matches(name, desc)) { + Printer localPrinter = asmifier.visitMethod(access, name, desc, signature, exceptions); + return new TraceMethodVisitor(null, localPrinter); + } + + return null; + } + + @Override + public void visitEnd() { + asmifier.visitClassEnd(); + asmifier.print(printWriter); + super.visitEnd(); + } + + public static void printMethod(ObfMapping method, Printer printer, File toFile) { + try { + printMethod(method, Launch.classLoader.getClassBytes(method.javaClass()), printer, toFile); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void printMethod(ObfMapping method, byte[] bytes, Printer printer, File toFile) { + try { + if (!toFile.getParentFile().exists()) + toFile.getParentFile().mkdirs(); + if (!toFile.exists()) + toFile.createNewFile(); + + PrintWriter printWriter = new PrintWriter(toFile); + + ClassVisitor cv = new MethodASMifier(method, printer, printWriter); + ClassReader cr = new ClassReader(bytes); + cr.accept(cv, 0); + + printWriter.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/codechicken/core/asm/TweakTransformer.java b/src/codechicken/core/asm/TweakTransformer.java new file mode 100644 index 0000000..2e6d460 --- /dev/null +++ b/src/codechicken/core/asm/TweakTransformer.java @@ -0,0 +1,82 @@ +package codechicken.core.asm; + +import codechicken.lib.asm.*; +import codechicken.lib.asm.ModularASMTransformer.MethodReplacer; +import codechicken.lib.asm.ModularASMTransformer.MethodTransformer; +import codechicken.lib.asm.ModularASMTransformer.MethodWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.lib.config.ConfigTag; +import net.minecraft.launchwrapper.IClassTransformer; + +import java.util.Map; + +import static codechicken.lib.asm.InsnComparator.*; + +public class TweakTransformer implements IClassTransformer, Opcodes +{ + private static ModularASMTransformer transformer = new ModularASMTransformer(); + private static Map blocks = ASMReader.loadResource("/assets/codechickencore/asm/tweaks.asm"); + public static ConfigTag tweaks; + + public static void load() { + CodeChickenCoreModContainer.loadConfig(); + tweaks = CodeChickenCoreModContainer.config.getTag("tweaks") + .setComment("Various tweaks that can be applied to game mechanics.").useBraces(); + tweaks.removeTag("persistantLava"); + + if (tweaks.getTag("environmentallyFriendlyCreepers") + .setComment("If set to true, creepers will not destroy landscape. (A version of mobGriefing setting just for creepers)") + .getBooleanValue(false)) { + transformer.add(new MethodReplacer(new ObfMapping("net/minecraft/entity/monster/EntityCreeper", "func_146077_cc", "()V"), + blocks.get("d_environmentallyFriendlyCreepers"), blocks.get("environmentallyFriendlyCreepers"))); + } + + if (!tweaks.getTag("softLeafReplace") + .setComment("If set to false, leaves will only replace air when growing") + .getBooleanValue(false)) { + transformer.add(new MethodWriter(ACC_PUBLIC, new ObfMapping("net/minecraft/block/Block", "canBeReplacedByLeaves", "(Lnet/minecraft/world/IBlockAccess;III)Z"), blocks.get("softLeafReplace"))); + } + + if (tweaks.getTag("doFireTickOut") + .setComment("If set to true and doFireTick is disabled in the game rules, fire will still dissipate if it's not over a fire source") + .getBooleanValue(true)) { + transformer.add(new MethodTransformer(new ObfMapping("net/minecraft/block/BlockFire", "func_149674_a", "(Lnet/minecraft/world/World;IIILjava/util/Random;)V")) + { + @Override + public void transform(MethodNode mv) { + ASMBlock needle = blocks.get("n_doFireTick"); + ASMBlock inject = blocks.get("doFireTick"); + + ASMBlock key = needle.applyLabels(findOnce(mv.instructions, needle.list)); + LabelNode jlabel = key.get("LRET"); + mv.instructions.insertBefore(jlabel, new JumpInsnNode(GOTO, inject.get("LSKIP"))); + mv.instructions.insert(jlabel, inject.list.list); + } + }); + } + + if (tweaks.getTag("finiteWater") + .setComment("If set to true two adjacent water source blocks will not generate a third.") + .getBooleanValue(false)) { + transformer.add(new MethodTransformer(new ObfMapping("net/minecraft/block/BlockDynamicLiquid", "func_149674_a", "(Lnet/minecraft/world/World;IIILjava/util/Random;)V")) + { + @Override + public void transform(MethodNode mv) { + InsnListSection key = findOnce(mv.instructions, blocks.get("finiteWater").list); + key.setLast(((JumpInsnNode) key.getLast()).label); + key.remove(); + } + }); + } + } + + @Override + public byte[] transform(String name, String tname, byte[] bytes) { + return transformer.transform(name, bytes); + } +} diff --git a/src/codechicken/core/commands/CoreCommand.java b/src/codechicken/core/commands/CoreCommand.java new file mode 100644 index 0000000..95f6a5a --- /dev/null +++ b/src/codechicken/core/commands/CoreCommand.java @@ -0,0 +1,158 @@ +package codechicken.core.commands; + +import java.util.List; + +import codechicken.core.ServerUtils; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.ChunkCoordinates; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.command.ICommand; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.IChatComponent; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.DimensionManager; + +public abstract class CoreCommand implements ICommand +{ + public class WCommandSender implements ICommandSender + { + public ICommandSender wrapped; + + public WCommandSender(ICommandSender sender) { + wrapped = sender; + } + + @Override + public String getCommandSenderName() { + return wrapped.getCommandSenderName(); + } + + @Override + public void addChatMessage(IChatComponent string) { + wrapped.addChatMessage(string); + } + + @Override + public boolean canCommandSenderUseCommand(int i, String s) { + return wrapped.canCommandSenderUseCommand(i, s); + } + + @Override + public ChunkCoordinates getPlayerCoordinates() { + return wrapped.getPlayerCoordinates(); + } + + @Override + public World getEntityWorld() { + return wrapped.getEntityWorld(); + } + + @Override + public IChatComponent func_145748_c_() { + return new ChatComponentText(getCommandSenderName()); + } + } + + public abstract boolean OPOnly(); + + @Override + public String getCommandUsage(ICommandSender var1) + { + return "/" + getCommandName() + " help"; + } + + @Override + public void processCommand(ICommandSender listener, String[] args) + { + WCommandSender wsender = new WCommandSender(listener); + + if(args.length < minimumParameters() || + args.length == 1 && args[0].equals("help")) + { + printHelp(wsender); + return; + } + + String command = getCommandName(); + for(String arg : args) + command+=" "+arg; + + handleCommand(command, wsender.getCommandSenderName(), args, wsender); + } + + public abstract void handleCommand(String command, String playername, String[] args, WCommandSender listener); + + public abstract void printHelp(WCommandSender listener); + + public final EntityPlayerMP getPlayer(String name) + { + return ServerUtils.getPlayer(name); + } + + public WorldServer getWorld(int dimension) + { + return DimensionManager.getWorld(dimension); + } + + public WorldServer getWorld(EntityPlayer player) + { + return (WorldServer) player.worldObj; + } + + public Integer parseInteger(String parse) + { + try + { + return Integer.parseInt(parse); + } + catch(NumberFormatException e) + { + return null; + } + } + + @Override + public int compareTo(Object arg0) + { + return getCommandName().compareTo(((ICommand)arg0).getCommandName()); + } + + @Override + public List getCommandAliases() + { + return null; + } + + @Override + public List addTabCompletionOptions(ICommandSender var1, String[] var2) + { + return null; + } + + @Override + public boolean isUsernameIndex(String[] astring, int i) + { + return false; + } + + @Override + public boolean canCommandSenderUseCommand(ICommandSender var1) + { + if(OPOnly()) + { + if(var1 instanceof EntityPlayer) + return ServerUtils.isPlayerOP(var1.getCommandSenderName()); + else if(var1 instanceof MinecraftServer) + return true; + else + return false; + } + return true; + } + + + public abstract int minimumParameters(); +} diff --git a/src/codechicken/core/commands/PlayerCommand.java b/src/codechicken/core/commands/PlayerCommand.java new file mode 100644 index 0000000..1b42cd5 --- /dev/null +++ b/src/codechicken/core/commands/PlayerCommand.java @@ -0,0 +1,60 @@ +package codechicken.core.commands; + +import net.minecraft.util.MovingObjectPosition.MovingObjectType; +import net.minecraft.world.ChunkPosition; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.Vec3; +import net.minecraft.world.WorldServer; + +public abstract class PlayerCommand extends CoreCommand +{ + @Override + public boolean canCommandSenderUseCommand(ICommandSender var1) + { + if(!super.canCommandSenderUseCommand(var1)) + return false; + return var1 instanceof EntityPlayer; + } + + + @Override + public void handleCommand(String command, String playername, String[] args, WCommandSender listener) + { + EntityPlayerMP player = (EntityPlayerMP)listener.wrapped; + handleCommand(getWorld(player), player, args); + } + + public abstract void handleCommand(WorldServer world, EntityPlayerMP player, String[] args); + + public ChunkPosition getPlayerLookingAtBlock(EntityPlayerMP player, float reach) + { + Vec3 vec3d = Vec3.createVectorHelper(player.posX, (player.posY + 1.6200000000000001D) - player.yOffset, player.posZ); + Vec3 vec3d1 = player.getLook(1.0F); + Vec3 vec3d2 = vec3d.addVector(vec3d1.xCoord * reach, vec3d1.yCoord * reach, vec3d1.zCoord * reach); + MovingObjectPosition hit = player.worldObj.rayTraceBlocks(vec3d, vec3d2); + if(hit == null || hit.typeOfHit != MovingObjectType.BLOCK) + { + return null; + } + + return new ChunkPosition(hit.blockX, hit.blockY, hit.blockZ); + } + + public Entity getPlayerLookingAtEntity(EntityPlayerMP player, float reach) + { + Vec3 vec3d = Vec3.createVectorHelper(player.posX, (player.posY + 1.6200000000000001D) - player.yOffset, player.posZ); + Vec3 vec3d1 = player.getLook(1.0F); + Vec3 vec3d2 = vec3d.addVector(vec3d1.xCoord * reach, vec3d1.yCoord * reach, vec3d1.zCoord * reach); + MovingObjectPosition hit = player.worldObj.rayTraceBlocks(vec3d, vec3d2); + if(hit == null || hit.typeOfHit != MovingObjectType.ENTITY) + { + return null; + } + + return hit.entityHit; + } +} diff --git a/src/codechicken/core/commands/ServerCommand.java b/src/codechicken/core/commands/ServerCommand.java new file mode 100644 index 0000000..ef06809 --- /dev/null +++ b/src/codechicken/core/commands/ServerCommand.java @@ -0,0 +1,28 @@ +package codechicken.core.commands; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.command.ICommandSender; + +public abstract class ServerCommand extends CoreCommand +{ + @Override + public void processCommand(ICommandSender var1, String[] var2) + { + handleCommand(var2, (MinecraftServer)var1); + } + + @Override + public boolean canCommandSenderUseCommand(ICommandSender var1) + { + if(!super.canCommandSenderUseCommand(var1)) + return false; + return var1 instanceof MinecraftServer; + } + + public abstract void handleCommand(String[] args, MinecraftServer listener); + + public final boolean OPOnly() + { + return false; + } +} diff --git a/src/codechicken/core/featurehack/EntityRenderHook.java b/src/codechicken/core/featurehack/EntityRenderHook.java new file mode 100644 index 0000000..8e89361 --- /dev/null +++ b/src/codechicken/core/featurehack/EntityRenderHook.java @@ -0,0 +1,49 @@ +package codechicken.core.featurehack; + +import net.minecraft.entity.Entity; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +public class EntityRenderHook extends Entity +{ + public static interface IRenderCallback + { + public void render(float frame, int pass); + + public boolean shouldRenderInPass(int pass); + + public boolean isValid(); + } + + public final IRenderCallback callback; + + public EntityRenderHook(World world, double x, double y, double z, IRenderCallback callback) { + super(world); + setPosition(x, y, z); + ignoreFrustumCheck = true; + this.callback = callback; + } + + @Override + public void onUpdate() { + if (!callback.isValid()) + setDead(); + } + + @Override + protected void entityInit() { + } + + @Override + protected void readEntityFromNBT(NBTTagCompound var1) { + } + + @Override + protected void writeEntityToNBT(NBTTagCompound var1) { + } + + @Override + public boolean shouldRenderInPass(int pass) { + return callback.shouldRenderInPass(pass); + } +} diff --git a/src/codechicken/core/featurehack/EntityUpdateHook.java b/src/codechicken/core/featurehack/EntityUpdateHook.java new file mode 100644 index 0000000..c6890d8 --- /dev/null +++ b/src/codechicken/core/featurehack/EntityUpdateHook.java @@ -0,0 +1,47 @@ +package codechicken.core.featurehack; + +import net.minecraft.entity.Entity; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; + +public class EntityUpdateHook extends Entity +{ + public static interface IUpdateCallback + { + public void onUpdate(); + + public boolean isValid(); + } + + public final IUpdateCallback callback; + public EntityUpdateHook(World world, int x, int y, int z, IUpdateCallback callback) + { + super(world); + setPosition(x, y, z); + this.callback = callback; + } + + @Override + public void onUpdate() + { + if(!callback.isValid()) + setDead(); + else + callback.onUpdate(); + } + + @Override + protected void entityInit() + { + } + + @Override + protected void readEntityFromNBT(NBTTagCompound var1) + { + } + + @Override + protected void writeEntityToNBT(NBTTagCompound var1) + { + } +} diff --git a/src/codechicken/core/featurehack/FeatureHack.java b/src/codechicken/core/featurehack/FeatureHack.java new file mode 100644 index 0000000..0f210d9 --- /dev/null +++ b/src/codechicken/core/featurehack/FeatureHack.java @@ -0,0 +1,38 @@ +package codechicken.core.featurehack; + +import cpw.mods.fml.client.registry.RenderingRegistry; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class FeatureHack +{ + private static boolean updateHookEnabled = false; + private static boolean renderHookEnabled = false; + + public static void enableUpdateHook() + { + if(updateHookEnabled) + return; + + updateHookEnabled = true; + if(FMLCommonHandler.instance().getSide().isClient()) + enableClientUpdateHook(); + } + + @SideOnly(Side.CLIENT) + public static void enableRenderHook() + { + if(renderHookEnabled) + return; + + renderHookEnabled = true; + RenderingRegistry.registerEntityRenderingHandler(EntityUpdateHook.class, new RenderNull()); + } + + @SideOnly(Side.CLIENT) + private static void enableClientUpdateHook() + { + RenderingRegistry.registerEntityRenderingHandler(EntityRenderHook.class, new RenderEntityRenderHook()); + } +} diff --git a/src/codechicken/core/featurehack/GameDataManipulator.java b/src/codechicken/core/featurehack/GameDataManipulator.java new file mode 100644 index 0000000..d591cf3 --- /dev/null +++ b/src/codechicken/core/featurehack/GameDataManipulator.java @@ -0,0 +1,40 @@ +package codechicken.core.featurehack; + +import codechicken.lib.asm.ObfMapping; +import net.minecraft.item.Item; +import net.minecraft.util.ObjectIntIdentityMap; +import net.minecraft.util.RegistryNamespaced; +import net.minecraft.util.RegistrySimple; + +import java.lang.reflect.Field; +import java.util.Map; + +public class GameDataManipulator +{ + public static Field f_registryObjects; + public static Field f_underlyingIntegerMap; + + static + { + try { + f_registryObjects = RegistrySimple.class.getDeclaredField( + new ObfMapping("net/minecraft/util/RegistrySimple", "registryObjects", "Ljava/util/Map;").toRuntime().s_name); + f_registryObjects.setAccessible(true); + f_underlyingIntegerMap = RegistryNamespaced.class.getDeclaredField( + new ObfMapping("net/minecraft/util/RegistryNamespaced", "underlyingIntegerMap", "Lnet/minecraft/util/ObjectIntIdentityMap;").toRuntime().s_name); + f_underlyingIntegerMap.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void replaceItem(int id, Item item) { + try { + String name = Item.itemRegistry.getNameForObject(Item.getItemById(id)); + ((Map)f_registryObjects.get(Item.itemRegistry)).put(name, item); + ((ObjectIntIdentityMap)f_underlyingIntegerMap.get(Item.itemRegistry)).func_148746_a(item, id); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/codechicken/core/featurehack/LiquidTextures.java b/src/codechicken/core/featurehack/LiquidTextures.java new file mode 100644 index 0000000..2911c5a --- /dev/null +++ b/src/codechicken/core/featurehack/LiquidTextures.java @@ -0,0 +1,73 @@ +package codechicken.core.featurehack; + +import java.lang.reflect.Field; +import codechicken.core.ReflectionManager; +import codechicken.core.featurehack.mc.TextureLavaFX; +import codechicken.core.featurehack.mc.TextureLavaFlowFX; +import codechicken.core.featurehack.mc.TextureWaterFX; +import codechicken.core.featurehack.mc.TextureWaterFlowFX; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.util.IIcon; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.common.MinecraftForge; + +import codechicken.core.asm.TweakTransformer; +import codechicken.lib.asm.ObfMapping; + +public class LiquidTextures +{ + public static IIcon[] newTextures = new IIcon[4]; + + public static boolean replaceLava; + public static boolean replaceWater; + + private static Field field_tex; + + public static void init() + { + replaceWater = TweakTransformer.tweaks.getTag("replaceWaterFX").setComment("Set this to true to use the pre1.5 water textures").getBooleanValue(false); + replaceLava = TweakTransformer.tweaks.getTag("replaceLavaFX").setComment("Set this to true to use the pre1.5 lava textures").getBooleanValue(false); + if(replaceWater) + { + newTextures[0] = new TextureWaterFX().texture; + newTextures[1] = new TextureWaterFlowFX().texture; + } + if(replaceLava) + { + newTextures[2] = new TextureLavaFX().texture; + newTextures[3] = new TextureLavaFlowFX().texture; + } + + if(replaceWater || replaceLava) + { + MinecraftForge.EVENT_BUS.register(new LiquidTextures()); + field_tex = ReflectionManager.getField(new ObfMapping("net/minecraft/block/BlockLiquid", "field_149806_a", "[Lnet/minecraft/util/IIcon;")); + } + } + + @SubscribeEvent + public void postStitch(TextureStitchEvent.Post event) + { + IIcon[] icons; + if(replaceLava) + { + icons = ReflectionManager.get(field_tex, IIcon[].class, Blocks.flowing_lava); + icons[0] = newTextures[2]; + icons[1] = newTextures[3]; + icons = ReflectionManager.get(field_tex, IIcon[].class, Blocks.lava); + icons[0] = newTextures[2]; + icons[1] = newTextures[3]; + } + if(replaceWater) + { + icons = ReflectionManager.get(field_tex, IIcon[].class, Blocks.flowing_water); + icons[0] = newTextures[0]; + icons[1] = newTextures[1]; + icons = ReflectionManager.get(field_tex, IIcon[].class, Blocks.water); + icons[0] = newTextures[0]; + icons[1] = newTextures[1]; + } + } +} diff --git a/src/codechicken/core/featurehack/RenderEntityRenderHook.java b/src/codechicken/core/featurehack/RenderEntityRenderHook.java new file mode 100644 index 0000000..d72a2cb --- /dev/null +++ b/src/codechicken/core/featurehack/RenderEntityRenderHook.java @@ -0,0 +1,26 @@ +package codechicken.core.featurehack; + +import org.lwjgl.opengl.GL11; + +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraft.client.renderer.entity.Render; +import net.minecraftforge.client.MinecraftForgeClient; + +public class RenderEntityRenderHook extends Render +{ + @Override + public void doRender(Entity entity, double x, double y, double z, float f, float frame) + { + EntityRenderHook hook = (EntityRenderHook)entity; + GL11.glTranslated(x-hook.posX, y-hook.posY, z-hook.posZ); + ((EntityRenderHook)entity).callback.render(frame, MinecraftForgeClient.getRenderPass()); + GL11.glTranslated(hook.posX-x, hook.posY-y, hook.posZ-z); + } + + @Override + protected ResourceLocation getEntityTexture(Entity entity) + { + return null; + } +} diff --git a/src/codechicken/core/featurehack/RenderNull.java b/src/codechicken/core/featurehack/RenderNull.java new file mode 100644 index 0000000..e6fc568 --- /dev/null +++ b/src/codechicken/core/featurehack/RenderNull.java @@ -0,0 +1,19 @@ +package codechicken.core.featurehack; + +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; + +public class RenderNull extends Render +{ + @Override + public void doRender(Entity var1, double var2, double var4, double var6, float var8, float var9) + { + } + + @Override + protected ResourceLocation getEntityTexture(Entity entity) + { + return null; + } +} diff --git a/src/codechicken/core/featurehack/TweakTransformerHelper.java b/src/codechicken/core/featurehack/TweakTransformerHelper.java new file mode 100644 index 0000000..816320b --- /dev/null +++ b/src/codechicken/core/featurehack/TweakTransformerHelper.java @@ -0,0 +1,39 @@ +package codechicken.core.featurehack; + +import java.util.Random; + +import net.minecraft.block.Block; +import net.minecraft.init.Blocks; +import net.minecraft.world.World; + +import static net.minecraftforge.common.util.ForgeDirection.UP; + +public class TweakTransformerHelper +{ + public static void quenchFireTick(World world, int x, int y, int z, Random rand) + { + Block base = world.getBlock(x, y - 1, z); + boolean supported = (base != null && base.isFireSource(world, x, y - 1, z, UP)); + + if (!Blocks.fire.canPlaceBlockAt(world, x, y, z) || + !supported && world.isRaining() && (world.canLightningStrikeAt(x, y, z) || world.canLightningStrikeAt(x - 1, y, z) || world.canLightningStrikeAt(x + 1, y, z) || world.canLightningStrikeAt(x, y, z - 1) || world.canLightningStrikeAt(x, y, z + 1))) + world.setBlockToAir(x, y, z); + else + { + int meta = world.getBlockMetadata(x, y, z); + if(meta < 15) + world.setBlockMetadataWithNotify(x, y, z, meta+rand.nextInt(3)/2, 0); + + world.scheduleBlockUpdate(x, y, z, Blocks.fire, Blocks.fire.tickRate(world) + rand.nextInt(10)); + + if(!supported && !Blocks.fire.canCatchFire(world, x, y - 1, z, UP) && meta == 15 && rand.nextInt(4) == 0) + world.setBlockToAir(x, y, z); + } + } + + public static boolean canPlaceBlockAt(World world, int x, int y, int z) + { + Block block = world.getBlock(x, y, z); + return block.isAir(world, x, y, z) || block.isReplaceable(world, x, y, z); + } +} diff --git a/src/codechicken/core/featurehack/mc/TextureLavaFX.java b/src/codechicken/core/featurehack/mc/TextureLavaFX.java new file mode 100644 index 0000000..dabbbe3 --- /dev/null +++ b/src/codechicken/core/featurehack/mc/TextureLavaFX.java @@ -0,0 +1,113 @@ +package codechicken.core.featurehack.mc; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.TextureFX; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.util.MathHelper; + +@SideOnly(Side.CLIENT) +public class TextureLavaFX extends TextureFX +{ + protected float[] field_76876_g = new float[256]; + protected float[] field_76878_h = new float[256]; + protected float[] field_76879_i = new float[256]; + protected float[] field_76877_j = new float[256]; + + public TextureLavaFX() + { + super(16, "lava_still_fx"); + setup(); + } + + @Override + public void setup() + { + super.setup(); + field_76876_g = new float[tileSizeSquare]; + field_76878_h = new float[tileSizeSquare]; + field_76879_i = new float[tileSizeSquare]; + field_76877_j = new float[tileSizeSquare]; + } + + public void onTick() + { + int var2; + float var3; + int var5; + int var6; + int var7; + int var8; + int var9; + + for (int var1 = 0; var1 < tileSizeBase; ++var1) + { + for (var2 = 0; var2 < tileSizeBase; ++var2) + { + var3 = 0.0F; + int var4 = (int)(MathHelper.sin(var2 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); + var5 = (int)(MathHelper.sin(var1 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); + + for (var6 = var1 - 1; var6 <= var1 + 1; ++var6) + { + for (var7 = var2 - 1; var7 <= var2 + 1; ++var7) + { + var8 = var6 + var4 & tileSizeMask; + var9 = var7 + var5 & tileSizeMask; + var3 += this.field_76876_g[var8 + var9 * tileSizeBase]; + } + } + + this.field_76878_h[var1 + var2 * tileSizeBase] = var3 / 10.0F + (this.field_76879_i[(var1 + 0 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76879_i[(var1 + 1 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76879_i[(var1 + 1 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase] + this.field_76879_i[(var1 + 0 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase]) / 4.0F * 0.8F; + this.field_76879_i[var1 + var2 * tileSizeBase] += this.field_76877_j[var1 + var2 * tileSizeBase] * 0.01F; + + if (this.field_76879_i[var1 + var2 * tileSizeBase] < 0.0F) + { + this.field_76879_i[var1 + var2 * tileSizeBase] = 0.0F; + } + + this.field_76877_j[var1 + var2 * tileSizeBase] -= 0.06F; + + if (Math.random() < 0.005D) + { + this.field_76877_j[var1 + var2 * tileSizeBase] = 1.5F; + } + } + } + + float[] var11 = this.field_76878_h; + this.field_76878_h = this.field_76876_g; + this.field_76876_g = var11; + + for (var2 = 0; var2 < tileSizeSquare; ++var2) + { + var3 = this.field_76876_g[var2] * 2.0F; + + if (var3 > 1.0F) + { + var3 = 1.0F; + } + + if (var3 < 0.0F) + { + var3 = 0.0F; + } + + var5 = (int)(var3 * 100.0F + 155.0F); + var6 = (int)(var3 * var3 * 255.0F); + var7 = (int)(var3 * var3 * var3 * var3 * 128.0F); + + if (this.anaglyphEnabled) + { + var8 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; + var9 = (var5 * 30 + var6 * 70) / 100; + int var10 = (var5 * 30 + var7 * 70) / 100; + var5 = var8; + var6 = var9; + var7 = var10; + } + + imageData[var2] = new ColourRGBA(var5, var6, var7, -1).argb(); + } + } +} diff --git a/src/codechicken/core/featurehack/mc/TextureLavaFlowFX.java b/src/codechicken/core/featurehack/mc/TextureLavaFlowFX.java new file mode 100644 index 0000000..b1912d7 --- /dev/null +++ b/src/codechicken/core/featurehack/mc/TextureLavaFlowFX.java @@ -0,0 +1,131 @@ +package codechicken.core.featurehack.mc; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.TextureFX; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.util.MathHelper; + +@SideOnly(Side.CLIENT) +public class TextureLavaFlowFX extends TextureFX +{ + protected float[] field_76871_g = new float[256]; + protected float[] field_76874_h = new float[256]; + protected float[] field_76875_i = new float[256]; + protected float[] field_76872_j = new float[256]; + int field_76873_k = 0; + + //shadow + public int tileSizeBase = 16; + public int tileSizeSquare = 256; + public int tileSizeMask = 15; + public int tileSizeSquareMask = 255; + + public TextureLavaFlowFX() + { + super(32, "lava_flow_fx"); + } + + @Override + public void setup() + { + super.setup(); + field_76871_g = new float[tileSizeSquare]; + field_76874_h = new float[tileSizeSquare]; + field_76875_i = new float[tileSizeSquare]; + field_76872_j = new float[tileSizeSquare]; + field_76873_k = 0; + } + + public void onTick() + { + ++this.field_76873_k; + int var2; + float var3; + int var5; + int var6; + int var7; + int var8; + int var9; + + for (int var1 = 0; var1 < tileSizeBase; ++var1) + { + for (var2 = 0; var2 < tileSizeBase; ++var2) + { + var3 = 0.0F; + int var4 = (int)(MathHelper.sin(var2 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); + var5 = (int)(MathHelper.sin(var1 * (float)Math.PI * 2.0F / 16.0F) * 1.2F); + + for (var6 = var1 - 1; var6 <= var1 + 1; ++var6) + { + for (var7 = var2 - 1; var7 <= var2 + 1; ++var7) + { + var8 = var6 + var4 & tileSizeMask; + var9 = var7 + var5 & tileSizeMask; + var3 += this.field_76871_g[var8 + var9 * tileSizeBase]; + } + } + + this.field_76874_h[var1 + var2 * tileSizeBase] = var3 / 10.0F + (this.field_76875_i[(var1 + 0 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76875_i[(var1 + 1 & tileSizeMask) + (var2 + 0 & tileSizeMask) * tileSizeBase] + this.field_76875_i[(var1 + 1 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase] + this.field_76875_i[(var1 + 0 & tileSizeMask) + (var2 + 1 & tileSizeMask) * tileSizeBase]) / 4.0F * 0.8F; + this.field_76875_i[var1 + var2 * tileSizeBase] += this.field_76872_j[var1 + var2 * tileSizeBase] * 0.01F; + + if (this.field_76875_i[var1 + var2 * tileSizeBase] < 0.0F) + { + this.field_76875_i[var1 + var2 * tileSizeBase] = 0.0F; + } + + this.field_76872_j[var1 + var2 * tileSizeBase] -= 0.06F; + + if (Math.random() < 0.005D) + { + this.field_76872_j[var1 + var2 * tileSizeBase] = 1.5F; + } + } + } + + float[] var11 = this.field_76874_h; + this.field_76874_h = this.field_76871_g; + this.field_76871_g = var11; + + for (var2 = 0; var2 < tileSizeSquare; ++var2) + { + var3 = this.field_76871_g[(var2 - this.field_76873_k / 3 * tileSizeBase) & tileSizeSquareMask] * 2.0F; + + if (var3 > 1.0F) + { + var3 = 1.0F; + } + + if (var3 < 0.0F) + { + var3 = 0.0F; + } + + var5 = (int)(var3 * 100.0F + 155.0F); + var6 = (int)(var3 * var3 * 255.0F); + var7 = (int)(var3 * var3 * var3 * var3 * 128.0F); + + if (this.anaglyphEnabled) + { + var8 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; + var9 = (var5 * 30 + var6 * 70) / 100; + int var10 = (var5 * 30 + var7 * 70) / 100; + var5 = var8; + var6 = var9; + var7 = var10; + } + + int px = var2&tileSizeMask; + int py = var2/tileSizeBase; + writeColour(px, py, var5, var6, var7, -1); + writeColour(px+16, py, var5, var6, var7, -1); + writeColour(px, py+16, var5, var6, var7, -1); + writeColour(px+16, py+16, var5, var6, var7, -1); + } + } + + private void writeColour(int px, int py, int var5, int var6, int var7, int var8) + { + imageData[py*32+px] = new ColourRGBA(var5, var6, var7, var8).argb(); + } +} diff --git a/src/codechicken/core/featurehack/mc/TextureWaterFX.java b/src/codechicken/core/featurehack/mc/TextureWaterFX.java new file mode 100644 index 0000000..bb62c42 --- /dev/null +++ b/src/codechicken/core/featurehack/mc/TextureWaterFX.java @@ -0,0 +1,120 @@ +package codechicken.core.featurehack.mc; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.TextureFX; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TextureWaterFX extends TextureFX +{ + /** red RGB value for water texture */ + protected float[] red = new float[256]; + + /** green RGB value for water texture */ + protected float[] green = new float[256]; + + /** blue RGB value for water texture */ + protected float[] blue = new float[256]; + + /** alpha RGB value for water texture */ + protected float[] alpha = new float[256]; + public TextureWaterFX() + { + super(16, "water_still_fx"); + setup(); + } + + @Override + public void setup() + { + super.setup(); + red = new float[tileSizeSquare]; + green = new float[tileSizeSquare]; + blue = new float[tileSizeSquare]; + alpha = new float[tileSizeSquare]; + } + + public void onTick() + { + int var1; + int var2; + float var3; + int var5; + int var6; + + for (var1 = 0; var1 < tileSizeBase; ++var1) + { + for (var2 = 0; var2 < tileSizeBase; ++var2) + { + var3 = 0.0F; + + for (int var4 = var1 - 1; var4 <= var1 + 1; ++var4) + { + var5 = var4 & tileSizeMask; + var6 = var2 & tileSizeMask; + var3 += this.red[var5 + var6 * tileSizeBase]; + } + + this.green[var1 + var2 * tileSizeBase] = var3 / 3.3F + this.blue[var1 + var2 * tileSizeBase] * 0.8F; + } + } + + for (var1 = 0; var1 < tileSizeBase; ++var1) + { + for (var2 = 0; var2 < tileSizeBase; ++var2) + { + this.blue[var1 + var2 * tileSizeBase] += this.alpha[var1 + var2 * tileSizeBase] * 0.05F; + + if (this.blue[var1 + var2 * tileSizeBase] < 0.0F) + { + this.blue[var1 + var2 * tileSizeBase] = 0.0F; + } + + this.alpha[var1 + var2 * tileSizeBase] -= 0.1F; + + if (Math.random() < 0.05D) + { + this.alpha[var1 + var2 * tileSizeBase] = 0.5F; + } + } + } + + float[] var12 = this.green; + this.green = this.red; + this.red = var12; + + for (var2 = 0; var2 < tileSizeSquare; ++var2) + { + var3 = this.red[var2]; + + if (var3 > 1.0F) + { + var3 = 1.0F; + } + + if (var3 < 0.0F) + { + var3 = 0.0F; + } + + float var13 = var3 * var3; + var5 = (int)(32.0F + var13 * 32.0F); + var6 = (int)(50.0F + var13 * 64.0F); + int var7 = 255; + int var8 = (int)(146.0F + var13 * 50.0F); + + if (this.anaglyphEnabled) + { + int var9 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; + int var10 = (var5 * 30 + var6 * 70) / 100; + int var11 = (var5 * 30 + var7 * 70) / 100; + var5 = var9; + var6 = var10; + var7 = var11; + } + + imageData[var2] = new ColourRGBA(var5, var6, var7, var8).argb(); + } + } +} diff --git a/src/codechicken/core/featurehack/mc/TextureWaterFlowFX.java b/src/codechicken/core/featurehack/mc/TextureWaterFlowFX.java new file mode 100644 index 0000000..d6b92cd --- /dev/null +++ b/src/codechicken/core/featurehack/mc/TextureWaterFlowFX.java @@ -0,0 +1,133 @@ +package codechicken.core.featurehack.mc; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.TextureFX; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TextureWaterFlowFX extends TextureFX +{ + protected float[] field_76880_g = new float[256]; + protected float[] field_76883_h = new float[256]; + protected float[] field_76884_i = new float[256]; + protected float[] field_76881_j = new float[256]; + private int tickCounter = 0; + + //shadow + public int tileSizeBase = 16; + public int tileSizeSquare = 256; + public int tileSizeMask = 15; + public int tileSizeSquareMask = 255; + + public TextureWaterFlowFX() + { + super(32, "water_flow_fx"); + } + + @Override + public void setup() + { + super.setup(); + field_76880_g = new float[tileSizeSquare]; + field_76883_h = new float[tileSizeSquare]; + field_76884_i = new float[tileSizeSquare]; + field_76881_j = new float[tileSizeSquare]; + tickCounter = 0; + } + + public void onTick() + { + ++this.tickCounter; + int var1; + int var2; + float var3; + int var5; + int var6; + + for (var1 = 0; var1 < tileSizeBase; ++var1) + { + for (var2 = 0; var2 < tileSizeBase; ++var2) + { + var3 = 0.0F; + + for (int var4 = var2 - 2; var4 <= var2; ++var4) + { + var5 = var1 & tileSizeMask; + var6 = var4 & tileSizeMask; + var3 += this.field_76880_g[var5 + var6 * tileSizeBase]; + } + + this.field_76883_h[var1 + var2 * tileSizeBase] = var3 / 3.2F + this.field_76884_i[var1 + var2 * tileSizeBase] * 0.8F; + } + } + + for (var1 = 0; var1 < tileSizeBase; ++var1) + { + for (var2 = 0; var2 < tileSizeBase; ++var2) + { + this.field_76884_i[var1 + var2 * tileSizeBase] += this.field_76881_j[var1 + var2 * tileSizeBase] * 0.05F; + + if (this.field_76884_i[var1 + var2 * tileSizeBase] < 0.0F) + { + this.field_76884_i[var1 + var2 * tileSizeBase] = 0.0F; + } + + this.field_76881_j[var1 + var2 * tileSizeBase] -= 0.3F; + + if (Math.random() < 0.2D) + { + this.field_76881_j[var1 + var2 * tileSizeBase] = 0.5F; + } + } + } + + float[] var12 = this.field_76883_h; + this.field_76883_h = this.field_76880_g; + this.field_76880_g = var12; + + for (var2 = 0; var2 < tileSizeSquare; ++var2) + { + var3 = this.field_76880_g[var2 - this.tickCounter * tileSizeBase & tileSizeSquareMask]; + + if (var3 > 1.0F) + { + var3 = 1.0F; + } + + if (var3 < 0.0F) + { + var3 = 0.0F; + } + + float var13 = var3 * var3; + var5 = (int)(32.0F + var13 * 32.0F); + var6 = (int)(50.0F + var13 * 64.0F); + int var7 = 255; + int var8 = (int)(146.0F + var13 * 50.0F); + + if (this.anaglyphEnabled) + { + int var9 = (var5 * 30 + var6 * 59 + var7 * 11) / 100; + int var10 = (var5 * 30 + var6 * 70) / 100; + int var11 = (var5 * 30 + var7 * 70) / 100; + var5 = var9; + var6 = var10; + var7 = var11; + } + + + int px = var2&tileSizeMask; + int py = var2/tileSizeBase; + writeColour(px, py, var5, var6, var7, var8); + writeColour(px+16, py, var5, var6, var7, var8); + writeColour(px, py+16, var5, var6, var7, var8); + writeColour(px+16, py+16, var5, var6, var7, var8); + } + } + + private void writeColour(int px, int py, int var5, int var6, int var7, int var8) + { + imageData[py*32+px] = new ColourRGBA(var5, var6, var7, var8).argb(); + } +} diff --git a/src/codechicken/core/fluid/ExtendedFluidTank.java b/src/codechicken/core/fluid/ExtendedFluidTank.java new file mode 100644 index 0000000..f16129d --- /dev/null +++ b/src/codechicken/core/fluid/ExtendedFluidTank.java @@ -0,0 +1,118 @@ +package codechicken.core.fluid; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidTankInfo; +import net.minecraftforge.fluids.IFluidTank; + +public class ExtendedFluidTank implements IFluidTank +{ + private FluidStack fluid; + private boolean changeType; + private int capacity; + + public ExtendedFluidTank(FluidStack type, int capacity) + { + if(type == null) + { + fluid = new FluidStack(0, 0); + changeType = true; + } + else + fluid = FluidUtils.copy(type, 0); + this.capacity = capacity; + } + + public ExtendedFluidTank(int capacity) + { + this(null, capacity); + } + + @Override + public FluidStack getFluid() + { + return fluid.copy(); + } + + @Override + public int getCapacity() + { + return capacity; + } + + public boolean canAccept(FluidStack type) + { + return type == null || type.fluidID <= 0 || (fluid.amount == 0 && changeType) || fluid.isFluidEqual(type); + } + + @Override + public int fill(FluidStack resource, boolean doFill) + { + if(resource == null || resource.fluidID <= 0) + return 0; + + if(!canAccept(resource)) + return 0; + + int tofill = Math.min(getCapacity()-fluid.amount, resource.amount); + if(doFill && tofill > 0) + { + if(!fluid.isFluidEqual(resource)) + fluid = FluidUtils.copy(resource, fluid.amount+tofill); + else + fluid.amount+=tofill; + onLiquidChanged(); + } + + return tofill; + } + + @Override + public FluidStack drain(int maxDrain, boolean doDrain) + { + if(fluid.amount == 0 || maxDrain <= 0) + return null; + + int todrain = Math.min(maxDrain, fluid.amount); + if(doDrain && todrain > 0) + { + fluid.amount-=todrain; + onLiquidChanged(); + } + return FluidUtils.copy(fluid, todrain); + } + + public FluidStack drain(FluidStack resource, boolean doDrain) + { + if (resource == null || !resource.isFluidEqual(fluid)) + return null; + + return drain(resource.amount, doDrain); + } + + public void onLiquidChanged() + { + } + + public void fromTag(NBTTagCompound tag) + { + fluid = FluidUtils.read(tag); + } + + public NBTTagCompound toTag() + { + return FluidUtils.write(fluid, new NBTTagCompound()); + } + + @Override + public int getFluidAmount() + { + return fluid.amount; + } + + @Override + public FluidTankInfo getInfo() + { + return new FluidTankInfo(this); + } +} diff --git a/src/codechicken/core/fluid/FluidUtils.java b/src/codechicken/core/fluid/FluidUtils.java new file mode 100644 index 0000000..95bdcde --- /dev/null +++ b/src/codechicken/core/fluid/FluidUtils.java @@ -0,0 +1,98 @@ +package codechicken.core.fluid; + +import codechicken.core.CommonUtils; +import codechicken.lib.inventory.InventoryUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidContainerRegistry; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidHandler; + +public class FluidUtils +{ + public static int B = FluidContainerRegistry.BUCKET_VOLUME; + public static FluidStack water = new FluidStack(FluidRegistry.WATER, 1000); + public static FluidStack lava = new FluidStack(FluidRegistry.LAVA, 1000); + + public static boolean fillTankWithContainer(IFluidHandler tank, EntityPlayer player) + { + ItemStack stack = player.getCurrentEquippedItem(); + FluidStack liquid = FluidContainerRegistry.getFluidForFilledItem(stack); + + if(liquid == null) + return false; + + if(tank.fill(ForgeDirection.UNKNOWN, liquid, false) != liquid.amount && !player.capabilities.isCreativeMode) + return false; + + tank.fill(ForgeDirection.UNKNOWN, liquid, true); + + if(!player.capabilities.isCreativeMode) + InventoryUtils.consumeItem(player.inventory, player.inventory.currentItem); + + player.inventoryContainer.detectAndSendChanges(); + return true; + } + + public static boolean emptyTankIntoContainer(IFluidHandler tank, EntityPlayer player, FluidStack tankLiquid) + { + ItemStack stack = player.getCurrentEquippedItem(); + + if(!FluidContainerRegistry.isEmptyContainer(stack)) + return false; + + ItemStack filled = FluidContainerRegistry.fillFluidContainer(tankLiquid, stack); + FluidStack liquid = FluidContainerRegistry.getFluidForFilledItem(filled); + + if(liquid == null || filled == null) + return false; + + tank.drain(ForgeDirection.UNKNOWN, liquid.amount, true); + + if(!player.capabilities.isCreativeMode) + { + if(stack.stackSize == 1) + player.inventory.setInventorySlotContents(player.inventory.currentItem, filled); + else if(player.inventory.addItemStackToInventory(filled)) + stack.stackSize--; + else + return false; + } + + player.inventoryContainer.detectAndSendChanges(); + return true; + } + + public static FluidStack copy(FluidStack liquid, int quantity) + { + liquid = liquid.copy(); + liquid.amount = quantity; + return liquid; + } + + public static FluidStack read(NBTTagCompound tag) + { + FluidStack stack = FluidStack.loadFluidStackFromNBT(tag); + return stack != null ? stack : new FluidStack(0, 0); + } + + public static NBTTagCompound write(FluidStack fluid, NBTTagCompound tag) + { + return fluid == null || fluid.getFluid() == null ? new NBTTagCompound() : fluid.writeToNBT(new NBTTagCompound()); + } + + public static int getLuminosity(FluidStack stack, double density) + { + Fluid fluid = stack.getFluid(); + if(fluid == null) + return 0; + int light = fluid.getLuminosity(stack); + if(fluid.isGaseous()) + light=(int)(light*density); + return light; + } +} diff --git a/src/codechicken/core/fluid/TankAccess.java b/src/codechicken/core/fluid/TankAccess.java new file mode 100644 index 0000000..8cbdcf1 --- /dev/null +++ b/src/codechicken/core/fluid/TankAccess.java @@ -0,0 +1,32 @@ +package codechicken.core.fluid; + +import net.minecraftforge.common.util.ForgeDirection; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.IFluidHandler; + +public class TankAccess +{ + public IFluidHandler tank; + public ForgeDirection side; + + public TankAccess(IFluidHandler tank, ForgeDirection side) + { + this.tank = tank; + this.side = side; + } + + public TankAccess(IFluidHandler tank, int side) + { + this(tank, ForgeDirection.getOrientation(side)); + } + + public int fill(FluidStack resource, boolean doFill) + { + return tank.fill(side, resource, doFill); + } + + public FluidStack drain(int maxDrain, boolean doDrain) + { + return tank.drain(side, maxDrain, doDrain); + } +} diff --git a/src/codechicken/core/gui/GuiCCButton.java b/src/codechicken/core/gui/GuiCCButton.java new file mode 100644 index 0000000..5f6e7b4 --- /dev/null +++ b/src/codechicken/core/gui/GuiCCButton.java @@ -0,0 +1,75 @@ +package codechicken.core.gui; + +import net.minecraft.client.Minecraft; + +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.opengl.GL11; + +public class GuiCCButton extends GuiWidget +{ + public String text; + public String actionCommand; + private boolean isEnabled = true; + public boolean visible = true; + + public GuiCCButton(int x, int y, int width, int height, String text) + { + super(x, y, width, height); + this.text = text; + } + + public void setText(String s) + { + text = s; + } + + public boolean isEnabled() + { + return isEnabled; + } + + public void setEnabled(boolean b) + { + isEnabled = b; + } + + @Override + public void mouseClicked(int x, int y, int button) + { + if(isEnabled && pointInside(x, y) && actionCommand != null) + { + sendAction(actionCommand, button); + Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.func_147674_a(new ResourceLocation("gui.button.press"), 1.0F)); + } + } + + @Override + public void draw(int mousex, int mousey, float frame) + { + if(!visible) + return; + + renderEngine.bindTexture(guiTex); + GL11.glColor4f(1, 1, 1, 1); + boolean mouseover = pointInside(mousex, mousey); + int state = !isEnabled ? 0 : mouseover ? 2 : 1; + drawTexturedModalRect(x, y, 0, 46 + state * 20, width / 2, height / 2);//top left + drawTexturedModalRect(x + width / 2, y, 200 - width / 2, 46 + state * 20, width / 2, height / 2);//top right + drawTexturedModalRect(x, y + height / 2, 0, 46 + state * 20 + 20 - height / 2, width / 2, height / 2);//bottom left + drawTexturedModalRect(x + width / 2, y + height / 2, 200 - width / 2, 46 + state * 20 + 20 - height / 2, width / 2, height / 2);//bottom right + + drawCenteredString(fontRenderer, text, x + width / 2, y + (height - 8) / 2, getTextColour(mousex, mousey)); + } + + public int getTextColour(int mousex, int mousey) + { + return !isEnabled ? 0xFFA0A0A0 : pointInside(mousex, mousey) ? 0xFFFFFFA0 : 0xFFE0E0E0; + } + + public GuiCCButton setActionCommand(String string) + { + actionCommand = string; + return this; + } +} diff --git a/src/codechicken/core/gui/GuiCCTextField.java b/src/codechicken/core/gui/GuiCCTextField.java new file mode 100644 index 0000000..84463b4 --- /dev/null +++ b/src/codechicken/core/gui/GuiCCTextField.java @@ -0,0 +1,189 @@ +package codechicken.core.gui; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.util.ChatAllowedCharacters; + +import org.lwjgl.input.Keyboard; + +public class GuiCCTextField extends GuiWidget +{ + private String text; + private boolean isFocused; + private boolean isEnabled; + + public int maxStringLength; + public int cursorCounter; + public String actionCommand; + + private String allowedCharacters; + + public GuiCCTextField(int x, int y, int width, int height, String text) + { + super(x, y, width, height); + isFocused = false; + isEnabled = true; + this.text = text; + } + + public GuiCCTextField setActionCommand(String s) + { + actionCommand = s; + return this; + } + + public void setText(String s) + { + if(s.equals(text)) + return; + + String oldText = text; + text = s; + onTextChanged(oldText); + } + + public void onTextChanged(String oldText) + { + } + + public final String getText() + { + return text; + } + + public final boolean isEnabled() + { + return isEnabled; + } + + public void setEnabled(boolean b) + { + isEnabled = b; + if(!isEnabled && isFocused) + setFocused(false); + } + + public final boolean isFocused() + { + return isFocused; + } + + @Override + public void update() + { + cursorCounter++; + } + + @Override + public void keyTyped(char c, int keycode) + { + if(!isEnabled || !isFocused) + return; + + /*if(c == '\t')//tab + { + parentGuiScreen.selectNextField(); + }*/ + if(c == '\026')//paste + { + String s = GuiScreen.getClipboardString(); + if(s == null || s.equals("")) + return; + + for(int i = 0; i < s.length(); i++) + { + if(text.length() == maxStringLength) + return; + + char tc = s.charAt(i); + if(canAddChar(tc)) + setText(text + tc); + } + } + if(keycode == Keyboard.KEY_RETURN) + { + setFocused(false); + sendAction(actionCommand, getText()); + } + + if(keycode == Keyboard.KEY_BACK && text.length() > 0) + setText(text.substring(0, text.length() - 1)); + + if((text.length() < maxStringLength || maxStringLength == 0) && canAddChar(c)) + setText(text + c); + } + + public boolean canAddChar(char c) + { + return allowedCharacters == null ? ChatAllowedCharacters.isAllowedCharacter(c) : allowedCharacters.indexOf(c) >= 0; + } + + @Override + public void mouseClicked(int x, int y, int button) + { + if(isEnabled && pointInside(x, y)) + { + setFocused(true); + if(button == 1) + setText(""); + } + else + setFocused(false); + } + + public void setFocused(boolean focus) + { + if(focus == isFocused) + return; + isFocused = focus; + onFocusChanged(); + } + + public void onFocusChanged() + { + if(isFocused) + cursorCounter = 0; + } + + @Override + public void draw(int i, int j, float f) + { + drawBackground(); + drawText(); + } + + public void drawBackground() + { + drawRect(x - 1, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); + drawRect(x, y, x + width, y + height, 0xff000000); + } + + public String getDrawText() + { + String s = getText(); + if(isEnabled && isFocused && (cursorCounter / 6) % 2 == 0) + s+="_"; + return s; + } + + public void drawText() + { + drawString(fontRenderer, getDrawText(), x + 4, y + height/2 - 4, getTextColour()); + } + + public int getTextColour() + { + return isEnabled ? 0xe0e0e0 : 0x707070; + } + + public GuiCCTextField setMaxStringLength(int i) + { + maxStringLength = i; + return this; + } + + public GuiCCTextField setAllowedCharacters(String s) + { + allowedCharacters = s; + return this; + } +} diff --git a/src/codechicken/core/gui/GuiScreenWidget.java b/src/codechicken/core/gui/GuiScreenWidget.java new file mode 100644 index 0000000..1bc7b88 --- /dev/null +++ b/src/codechicken/core/gui/GuiScreenWidget.java @@ -0,0 +1,141 @@ +package codechicken.core.gui; + +import java.awt.Point; +import java.util.ArrayList; + +import codechicken.lib.gui.GuiDraw; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; + +public class GuiScreenWidget extends GuiScreen implements IGuiActionListener +{ + public ArrayList widgets = new ArrayList(); + public int xSize, ySize, guiTop, guiLeft; + + public GuiScreenWidget() + { + this(176, 166); + } + + public GuiScreenWidget(int xSize, int ySize) + { + super(); + this.xSize = xSize; + this.ySize = ySize; + } + + @Override + public void initGui() + { + guiTop = (height - ySize) / 2; + guiLeft = (width - xSize) / 2; + } + + public void reset() + { + initGui(); + widgets.clear(); + addWidgets(); + } + + @Override + public void setWorldAndResolution(Minecraft mc, int i, int j) + { + boolean init = this.mc == null; + super.setWorldAndResolution(mc, i, j); + if(init) + addWidgets(); + } + + public void add(GuiWidget widget) + { + widgets.add(widget); + widget.onAdded(this); + } + + @Override + public void drawScreen(int mousex, int mousey, float f) + { + GL11.glTranslated(guiLeft, guiTop, 0); + drawBackground(); + for(GuiWidget widget : widgets) + widget.draw(mousex-guiLeft, mousey-guiTop, f); + drawForeground(); + GL11.glTranslated(-guiLeft, -guiTop, 0); + } + + public void drawBackground() + { + } + + public void drawForeground() + { + } + + @Override + protected void mouseClicked(int x, int y, int button) + { + super.mouseClicked(x, y, button); + for(GuiWidget widget : widgets) + widget.mouseClicked(x-guiLeft, y-guiTop, button); + } + + @Override + protected void mouseMovedOrUp(int x, int y, int button) + { + super.mouseMovedOrUp(x, y, button); + for(GuiWidget widget : widgets) + widget.mouseMovedOrUp(x-guiLeft, y-guiTop, button); + } + + @Override + protected void mouseClickMove(int x, int y, int button, long time) + { + super.mouseClickMove(x, y, button, time); + for(GuiWidget widget : widgets) + widget.mouseDragged(x-guiLeft, y-guiTop, button, time); + } + + @Override + public void updateScreen() + { + super.updateScreen(); + if(mc.currentScreen == this) + for(GuiWidget widget : widgets) + widget.update(); + } + + @Override + public void keyTyped(char c, int keycode) + { + super.keyTyped(c, keycode); + for(GuiWidget widget : widgets) + widget.keyTyped(c, keycode); + } + + @Override + public void handleMouseInput() + { + super.handleMouseInput(); + int i = Mouse.getEventDWheel(); + if(i != 0) + { + Point p = GuiDraw.getMousePosition(); + int scroll = i > 0 ? 1 : -1; + for(GuiWidget widget : widgets) + widget.mouseScrolled(p.x, p.y, scroll); + } + } + + @Override + public void actionPerformed(String ident, Object... params) + { + } + + public void addWidgets() + { + } +} diff --git a/src/codechicken/core/gui/GuiScrollSlot.java b/src/codechicken/core/gui/GuiScrollSlot.java new file mode 100644 index 0000000..5fc848b --- /dev/null +++ b/src/codechicken/core/gui/GuiScrollSlot.java @@ -0,0 +1,361 @@ +package codechicken.core.gui; + +import net.minecraft.client.gui.GuiButton; + +import org.lwjgl.input.Keyboard; + +import codechicken.lib.math.MathHelper; + +public abstract class GuiScrollSlot extends GuiWidget +{ + protected GuiButton scrollupbutton; + protected GuiButton scrolldownbutton; + protected String actionCommand; + + protected int scrollclicky = -1; + protected float scrollpercent; + protected int scrollmousey; + protected float percentscrolled; + + protected int lastslotclicked = -1; + protected int lastbuttonclicked = -1; + protected long lastslotclicktime; + + public boolean focused; + + public int contentx; + public int contenty; + public int contentheight; + public boolean smoothScroll = true; + + public GuiScrollSlot(int x, int y, int width, int height) + { + super(x, y, width, height); + setContentSize(x+3, y+2, height-2); + } + + public GuiScrollSlot setActionCommand(String s) + { + actionCommand = s; + return this; + } + + public void setSmoothScroll(boolean b) + { + smoothScroll = b; + } + + public void setSize(int x, int y, int width, int height) + { + int cx = contentx-this.x; + int cy = contenty-this.y; + int ch = height-contentheight; + super.setSize(x, y, width, height); + + setContentSize(x+cx, y+cy, height-ch); + } + + public void setContentSize(int x, int y, int h) + { + this.contentx = x; + this.contenty = y; + this.contentheight = h; + } + + public void registerButtons(GuiButton up, GuiButton down, String selectionCommand) + { + scrollupbutton = up; + scrolldownbutton = down; + actionCommand = selectionCommand; + } + + public abstract int getSlotHeight(); + + protected abstract int getNumSlots(); + + public abstract void selectNext(); + + public abstract void selectPrev(); + + protected abstract void slotClicked(int slot, int button, int mousex, int mousey, boolean doubleclick); + + protected abstract boolean isSlotSelected(int slot); + + protected abstract void drawSlot(int slot, int x, int y, int mousex, int mousey, boolean selected, float frame); + + protected void unfocus() + { + } + + public void setFocused(boolean focus) + { + focused = focus; + if(!focused) + unfocus(); + } + + public void scrollUp() + { + scroll(-5); + } + + public void scrollDown() + { + scroll(5); + } + + public void scroll(int i) + { + percentscrolled += i/(float)contentheight * 100; + calculatePercentScrolled(); + } + + public int totalContentHeight() + { + return getNumSlots() * getSlotHeight(); + } + + public int getSlotY(int slot) + { + int scrolledPixels = (int)((totalContentHeight() - contentheight) * percentscrolled + 0.5); + if(!smoothScroll) + scrolledPixels = (int)(scrolledPixels/(double)getSlotHeight()+0.5)*getSlotHeight(); + return contenty-scrolledPixels+slot*getSlotHeight(); + } + + public int getClickedSlot(int mousey) + { + if(mousey < contenty || mousey >= contenty+contentheight) + return -1; + + for(int slot = 0; slot < getNumSlots(); slot++) + { + int sloty = getSlotY(slot); + if(mousey >= sloty && mousey < sloty + getSlotHeight()) + return slot; + } + return -1; + } + + public int getScrollBarWidth() + { + return 5; + } + + public int getScrollBarHeight() + { + int sbarh = (int)((contentheight / (float)totalContentHeight()) * height); + if(sbarh > height) + { + return height; + } + else if(sbarh < height / 15) + { + return height / 15; + } + else + { + return sbarh; + } + } + + public void calculatePercentScrolled() + { + int barempty = height - getScrollBarHeight(); + if(scrollclicky >= 0) + { + int scrolldiff = scrollmousey - scrollclicky; + percentscrolled = scrolldiff / (float)barempty + scrollpercent; + } + percentscrolled = (float) MathHelper.clip(percentscrolled, 0, 1); + } + + public void showSlot(int slot) + { + int sloty = getSlotY(slot); + if(sloty+getSlotHeight() > contenty+contentheight) + { + int diff = sloty-(contenty+contentheight-getSlotHeight()); + percentscrolled += diff/(double)(totalContentHeight() - contentheight); + calculatePercentScrolled(); + } + else if(sloty < contenty) + { + int diff = contenty-sloty; + percentscrolled -= diff/(double)(totalContentHeight() - contentheight); + calculatePercentScrolled(); + } + } + + public void processMouse(int mousex, int mousey) + { + if(scrollclicky >= 0) + { + int scrolldiff = mousey - scrollclicky; + int barupallowed = (int)((height - getScrollBarHeight()) * scrollpercent + 0.5); + int bardownallowed = (height - getScrollBarHeight()) - barupallowed; + + if(-scrolldiff > barupallowed) + scrollmousey = scrollclicky - barupallowed; + else if(scrolldiff > bardownallowed) + scrollmousey = scrollclicky + bardownallowed; + else + scrollmousey = mousey; + + calculatePercentScrolled(); + } + } + + public void actionPerformed(GuiButton guibutton) + { + if(!guibutton.enabled) + { + return; + } + if(scrollupbutton != null && guibutton.id == scrollupbutton.id) + { + scrollUp(); + } + else if(scrolldownbutton != null && guibutton.id == scrolldownbutton.id) + { + scrollDown(); + } + } + + @Override + public void mouseClicked(int mousex, int mousey, int button) + { + boolean needsfocus = (mousex >= x && mousex < x + width && + mousey >= y && mousey <= y + height); + if(needsfocus != focused) + { + setFocused(needsfocus); + } + + int barempty = height - getScrollBarHeight(); + int sbarx = x + width - getScrollBarWidth(); + int sbary = y + (int)(barempty * percentscrolled + 0.5); + + if(button == 0 && + getScrollBarHeight() < height && //the scroll bar can move (not full length) + mousex >= sbarx && mousex <= x + width && + mousey >= y && mousey <= y + height)//in the scroll pane + { + if(mousey < sbary) + { + percentscrolled = (mousey - y) / (float)barempty; + calculatePercentScrolled(); + } + else if(mousey > sbary + getScrollBarHeight()) + { + percentscrolled = (mousey - y - getScrollBarHeight() + 1) / (float)barempty; + calculatePercentScrolled(); + } + else + { + scrollclicky = mousey; + scrollpercent = percentscrolled; + scrollmousey = mousey; + } + } + else if(mousex >= contentx && mousex < sbarx && + mousey >= contenty && mousey <= contenty + contentheight)//in the box + { + int slot = getClickedSlot(mousey); + if(slot >= 0) + slotClicked(slot, button, mousex-contentx, mousey-getSlotY(slot), + slot == lastslotclicked && button == lastbuttonclicked && System.currentTimeMillis() - lastslotclicktime < 500); + + lastslotclicked = slot; + lastbuttonclicked = button; + lastslotclicktime = System.currentTimeMillis(); + } + } + + @Override + public void mouseMovedOrUp(int mousex, int mousey, int button) + { + if(scrollclicky >= 0 && button == 0)//we were scrolling and we released mouse + scrollclicky = -1; + } + + @Override + public void keyTyped(char c, int keycode) + { + if(!focused) + return; + + if(keycode == Keyboard.KEY_UP) + selectPrev(); + if(keycode == Keyboard.KEY_DOWN) + selectNext(); + if(keycode == Keyboard.KEY_RETURN && actionCommand != null) + sendAction(actionCommand); + } + + public void drawSlotBox(float frame) + { + drawRect(x, y, x + width, y + height, 0xff000000); + } + + public void drawBackground(float frame) + { + + } + + public void drawOverlay(float frame) + { + //outlines + drawRect(x, y - 1, x + width, y, 0xffa0a0a0);//top + drawRect(x, y + height, x + width, y + height + 1, 0xffa0a0a0);//bottom + drawRect(x - 1, y - 1, x, y + height + 1, 0xffa0a0a0);//left + drawRect(x + width, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0);//right + } + + public void drawScrollBar(float frame) + { + int sbarw = getScrollBarWidth(); + int sbarh = getScrollBarHeight(); + int sbarx = x + width - sbarw; + int sbary = y + (int)((height - sbarh) * percentscrolled + 0.4999); + + drawRect(sbarx, sbary, sbarx+sbarw, sbary+sbarh, 0xFF8B8B8B);//corners + drawRect(sbarx, sbary, sbarx+sbarw-1, sbary+sbarh - 1, 0xFFF0F0F0);//topleft up + drawRect(sbarx + 1, sbary + 1, sbarx+sbarw, sbary+sbarh, 0xFF555555);//bottom right down + drawRect(sbarx + 1, sbary + 1, sbarx+sbarw-1, sbary+sbarh - 1, 0xFFC6C6C6);//scrollbar + + if(drawLineGuide()) + drawRect(sbarx - 1, y, sbarx, y + height, 0xFF808080);//lineguide + } + + public boolean drawLineGuide() + { + return true; + } + + public void drawSlots(int mousex, int mousey, float frame) + { + for(int slot = 0; slot < getNumSlots(); slot++) + { + int sloty = getSlotY(slot); + if(sloty > contenty - getSlotHeight() && sloty < contenty + contentheight) + drawSlot(slot, contentx, sloty, mousex-contentx, mousey-sloty, isSlotSelected(slot), frame); + } + } + + @Override + public void mouseDragged(int x, int y, int button, long time) + { + processMouse(x, y); + } + + @Override + public void draw(int mousex, int mousey, float frame) + { + drawBackground(frame); + drawSlotBox(frame); + drawSlots(mousex, mousey, frame); + drawOverlay(frame); + drawScrollBar(frame); + } +} diff --git a/src/codechicken/core/gui/GuiWidget.java b/src/codechicken/core/gui/GuiWidget.java new file mode 100644 index 0000000..b73d1ee --- /dev/null +++ b/src/codechicken/core/gui/GuiWidget.java @@ -0,0 +1,87 @@ +package codechicken.core.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.util.ResourceLocation; + +public class GuiWidget extends Gui +{ + protected static final ResourceLocation guiTex = new ResourceLocation("textures/gui/widgets.png"); + + public GuiScreen parentScreen; + public TextureManager renderEngine; + public FontRenderer fontRenderer; + + public int x; + public int y; + public int width; + public int height; + + public GuiWidget(int x, int y, int width, int height) + { + setSize(x, y, width, height); + } + + public void setSize(int x, int y, int width, int height) + { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public boolean pointInside(int px, int py) + { + return px >= x && px < x + width && py >= y && py < y + height; + } + + public void sendAction(String actionCommand, Object... params) + { + sendAction(parentScreen, actionCommand, params); + } + + public static void sendAction(GuiScreen screen, String actionCommand, Object... params) + { + if(actionCommand != null && screen instanceof IGuiActionListener) + ((IGuiActionListener) screen).actionPerformed(actionCommand, params); + } + + public void mouseClicked(int x, int y, int button) + { + } + + public void mouseMovedOrUp(int x, int y, int button) + { + } + + public void mouseDragged(int x, int y, int button, long time) + { + } + + public void update() + { + } + + public void draw(int mousex, int mousey, float frame) + { + } + + public void keyTyped(char c, int keycode) + { + } + + public void mouseScrolled(int x, int y, int scroll) + { + } + + public void onAdded(GuiScreen s) + { + Minecraft mc = Minecraft.getMinecraft(); + parentScreen = s; + renderEngine = mc.renderEngine; + fontRenderer = mc.fontRenderer; + } +} diff --git a/src/codechicken/core/gui/IGuiActionListener.java b/src/codechicken/core/gui/IGuiActionListener.java new file mode 100644 index 0000000..8540251 --- /dev/null +++ b/src/codechicken/core/gui/IGuiActionListener.java @@ -0,0 +1,6 @@ +package codechicken.core.gui; + +public interface IGuiActionListener +{ + public void actionPerformed(String actionCommand, Object... params); +} \ No newline at end of file diff --git a/src/codechicken/core/internal/CCCEventHandler.java b/src/codechicken/core/internal/CCCEventHandler.java new file mode 100644 index 0000000..c547515 --- /dev/null +++ b/src/codechicken/core/internal/CCCEventHandler.java @@ -0,0 +1,27 @@ +package codechicken.core.internal; + +import codechicken.core.CCUpdateChecker; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.common.gameevent.TickEvent.Phase; + +public class CCCEventHandler +{ + public static int renderTime; + public static float renderFrame; + + @SubscribeEvent + public void clientTick(TickEvent.ClientTickEvent event) { + if(event.phase == Phase.END) { + CCUpdateChecker.tick(); + renderTime++; + } + } + + @SubscribeEvent + public void renderTick(TickEvent.RenderTickEvent event) { + if(event.phase == Phase.START) + renderFrame = event.renderTickTime; + } +} diff --git a/src/codechicken/core/inventory/ContainerExtended.java b/src/codechicken/core/inventory/ContainerExtended.java new file mode 100644 index 0000000..88f45ef --- /dev/null +++ b/src/codechicken/core/inventory/ContainerExtended.java @@ -0,0 +1,267 @@ +package codechicken.core.inventory; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import codechicken.lib.packet.PacketCustom; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.ICrafting; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.inventory.Slot; + +public abstract class ContainerExtended extends Container implements ICrafting +{ + public LinkedList playerCrafters = new LinkedList(); + + public ContainerExtended() + { + crafters.add(this); + } + + @Override + public void addCraftingToCrafters(ICrafting icrafting) + { + if(icrafting instanceof EntityPlayerMP) + { + playerCrafters.add((EntityPlayerMP) icrafting); + sendContainerAndContentsToPlayer(this, getInventory(), Arrays.asList((EntityPlayerMP) icrafting)); + detectAndSendChanges(); + } + else + super.addCraftingToCrafters(icrafting); + } + + @Override + public void removeCraftingFromCrafters(ICrafting icrafting) + { + if(icrafting instanceof EntityPlayerMP) + playerCrafters.remove(icrafting); + else + super.removeCraftingFromCrafters(icrafting); + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list) + { + sendContainerAndContentsToPlayer(container, list, playerCrafters); + } + + public void sendContainerAndContentsToPlayer(Container container, List list, List playerCrafters) + { + LinkedList largeStacks = new LinkedList(); + for(int i = 0; i < list.size(); i++) + { + ItemStack stack = list.get(i); + if(stack != null && stack.stackSize > Byte.MAX_VALUE) + { + list.set(i, null); + largeStacks.add(stack); + } + else + largeStacks.add(null); + } + + for(EntityPlayerMP player : playerCrafters) + player.sendContainerAndContentsToPlayer(container, list); + + for(int i = 0; i < largeStacks.size(); i++) + { + ItemStack stack = largeStacks.get(i); + if(stack != null) + sendLargeStack(stack, i, playerCrafters); + } + } + + public void sendLargeStack(ItemStack stack, int slot, List players) + { + } + + @Override + public void sendProgressBarUpdate(Container container, int i, int j) + { + for(EntityPlayerMP player : playerCrafters) + player.sendProgressBarUpdate(container, i, j); + } + + @Override + public void sendSlotContents(Container container, int slot, ItemStack stack) + { + if(stack != null && stack.stackSize > Byte.MAX_VALUE) + sendLargeStack(stack, slot, playerCrafters); + else + for(EntityPlayerMP player : playerCrafters) + player.sendSlotContents(container, slot, stack); + } + + @Override + public ItemStack slotClick(int par1, int par2, int par3, EntityPlayer player) + { + if(par1 >= 0 && par1 < inventorySlots.size()) + { + Slot slot = getSlot(par1); + if(slot instanceof SlotHandleClicks) + return ((SlotHandleClicks) slot).slotClick(this, player, par2, par3); + } + return super.slotClick(par1, par2, par3, player); + } + + @Override + public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int slotIndex) + { + ItemStack transferredStack = null; + Slot slot = (Slot)inventorySlots.get(slotIndex); + + if (slot != null && slot.getHasStack()) + { + ItemStack stack = slot.getStack(); + transferredStack = stack.copy(); + + if(!doMergeStackAreas(slotIndex, stack)) + return null; + + if (stack.stackSize == 0) + slot.putStack(null); + else + slot.onSlotChanged(); + } + + return transferredStack; + } + + @Override + public boolean mergeItemStack(ItemStack stack, int startIndex, int endIndex, boolean reverse) + { + boolean merged = false; + int slotIndex = reverse ? endIndex - 1 : startIndex; + + if(stack == null) + return false; + + if (stack.isStackable())//search for stacks to increase + { + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) + { + Slot slot = (Slot)inventorySlots.get(slotIndex); + ItemStack slotStack = slot.getStack(); + + if (slotStack != null && slotStack.getItem() == stack.getItem() && + (!stack.getHasSubtypes() || stack.getItemDamage() == slotStack.getItemDamage()) && + ItemStack.areItemStackTagsEqual(stack, slotStack)) + { + int totalStackSize = slotStack.stackSize + stack.stackSize; + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (totalStackSize <= maxStackSize) + { + stack.stackSize = 0; + slotStack.stackSize = totalStackSize; + slot.onSlotChanged(); + merged = true; + } + else if (slotStack.stackSize < maxStackSize) + { + stack.stackSize -= maxStackSize - slotStack.stackSize; + slotStack.stackSize = maxStackSize; + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + if (stack.stackSize > 0)//normal transfer :) + { + slotIndex = reverse ? endIndex - 1 : startIndex; + + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) + { + Slot slot = (Slot)this.inventorySlots.get(slotIndex); + + if (!slot.getHasStack() && slot.isItemValid(stack)) + { + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (stack.stackSize <= maxStackSize) + { + slot.putStack(stack.copy()); + slot.onSlotChanged(); + stack.stackSize = 0; + merged = true; + } + else + { + slot.putStack(stack.splitStack(maxStackSize)); + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + return merged; + } + + public boolean doMergeStackAreas(int slotIndex, ItemStack stack) + { + return false; + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer) + { + bindPlayerInventory(inventoryPlayer, 8, 84); + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer, int x, int y) + { + for(int row = 0; row < 3; row++) + for(int col = 0; col < 9; col++) + addSlotToContainer(new Slot(inventoryPlayer, col + row * 9 + 9, x + col * 18, y + row * 18)); + for(int slot = 0; slot < 9; slot++) + addSlotToContainer(new Slot(inventoryPlayer, slot, x + slot * 18, y + 58)); + } + + @Override + public boolean canInteractWith(EntityPlayer var1) + { + return true; + } + + public void sendContainerPacket(PacketCustom packet) + { + for(EntityPlayerMP player : playerCrafters) + packet.sendToPlayer(player); + } + + /** + * May be called from a client packet handler to handle additional info + */ + public void handleOutputPacket(PacketCustom packet) + { + } + + /** + * May be called from a server packet handler to handle additional info + */ + public void handleInputPacket(PacketCustom packet) + { + } + + /** + * May be called from a server packet handler to handle client input + */ + public void handleGuiChange(int ID, int value) + { + } + + public void sendProgressBarUpdate(int barID, int value) + { + for(ICrafting crafting : (List)crafters) + crafting.sendProgressBarUpdate(this, barID, value); + } +} diff --git a/src/codechicken/core/inventory/ContainerSynchronised.java b/src/codechicken/core/inventory/ContainerSynchronised.java new file mode 100644 index 0000000..b1f64fa --- /dev/null +++ b/src/codechicken/core/inventory/ContainerSynchronised.java @@ -0,0 +1,69 @@ +package codechicken.core.inventory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.item.ItemStack; + +import codechicken.lib.packet.PacketCustom; + +public abstract class ContainerSynchronised extends ContainerExtended +{ + private ArrayList syncVars = new ArrayList(); + + public abstract PacketCustom createSyncPacket(); + + @Override + public final void detectAndSendChanges() + { + super.detectAndSendChanges(); + + for(int i = 0; i < syncVars.size(); i++) + { + IContainerSyncVar var = syncVars.get(i); + if(var.changed()) + { + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + sendContainerPacket(packet); + var.reset(); + } + } + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list, List playerCrafters) + { + super.sendContainerAndContentsToPlayer(container, list, playerCrafters); + for(int i = 0; i < syncVars.size(); i++) + { + IContainerSyncVar var = syncVars.get(i); + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + var.reset(); + for(EntityPlayerMP player : playerCrafters) + packet.sendToPlayer(player); + } + } + + public void addSyncVar(IContainerSyncVar var) + { + syncVars.add(var); + } + + @Override + public final void handleOutputPacket(PacketCustom packet) + { + syncVars.get(packet.readUByte()).readChange(packet); + } + + public List getSyncedVars() + { + return Collections.unmodifiableList(syncVars); + } +} diff --git a/src/codechicken/core/inventory/GuiContainerWidget.java b/src/codechicken/core/inventory/GuiContainerWidget.java new file mode 100644 index 0000000..e917c58 --- /dev/null +++ b/src/codechicken/core/inventory/GuiContainerWidget.java @@ -0,0 +1,133 @@ +package codechicken.core.inventory; + +import java.awt.Point; +import java.util.ArrayList; + +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +import codechicken.lib.gui.GuiDraw; +import codechicken.core.gui.GuiWidget; +import codechicken.core.gui.IGuiActionListener; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.inventory.Container; + +public class GuiContainerWidget extends GuiContainer implements IGuiActionListener +{ + public ArrayList widgets = new ArrayList(); + + public GuiContainerWidget(Container inventorySlots) + { + this(inventorySlots, 176, 166); + } + + public GuiContainerWidget(Container inventorySlots, int xSize, int ySize) + { + super(inventorySlots); + this.xSize = xSize; + this.ySize = ySize; + } + + @Override + public void setWorldAndResolution(Minecraft mc, int i, int j) + { + boolean init = this.mc == null; + super.setWorldAndResolution(mc, i, j); + if(init) + addWidgets(); + } + + public void add(GuiWidget widget) + { + widgets.add(widget); + widget.onAdded(this); + } + + @Override + protected void drawGuiContainerBackgroundLayer(float f, int mousex, int mousey) + { + GL11.glTranslated(guiLeft, guiTop, 0); + drawBackground(); + for(GuiWidget widget : widgets) + widget.draw(mousex-guiLeft, mousey-guiTop, f); + + GL11.glTranslated(-guiLeft, -guiTop, 0); + } + + public void drawBackground() + { + } + + @Override + protected void mouseClicked(int x, int y, int button) + { + super.mouseClicked(x, y, button); + for(GuiWidget widget : widgets) + widget.mouseClicked(x-guiLeft, y-guiTop, button); + } + + @Override + protected void mouseMovedOrUp(int x, int y, int button) + { + super.mouseMovedOrUp(x, y, button); + for(GuiWidget widget : widgets) + widget.mouseMovedOrUp(x-guiLeft, y-guiTop, button); + } + + @Override + protected void mouseClickMove(int x, int y, int button, long time) + { + super.mouseClickMove(x, y, button, time); + for(GuiWidget widget : widgets) + widget.mouseDragged(x-guiLeft, y-guiTop, button, time); + } + + @Override + public void updateScreen() + { + super.updateScreen(); + if(mc.currentScreen == this) + for(GuiWidget widget : widgets) + widget.update(); + } + + @Override + public void keyTyped(char c, int keycode) + { + super.keyTyped(c, keycode); + for(GuiWidget widget : widgets) + widget.keyTyped(c, keycode); + } + + @Override + public void handleMouseInput() + { + super.handleMouseInput(); + int i = Mouse.getEventDWheel(); + if(i != 0) + { + Point p = GuiDraw.getMousePosition(); + int scroll = i > 0 ? 1 : -1; + for(GuiWidget widget : widgets) + widget.mouseScrolled(p.x, p.y, scroll); + } + } + + @Override + public void actionPerformed(String ident, Object... params) + { + } + + public void addWidgets() + { + } + + @Override + public void onGuiClosed() { + super.onGuiClosed(); + mc = null; + widgets.clear(); + } +} diff --git a/src/codechicken/core/inventory/IContainerSyncVar.java b/src/codechicken/core/inventory/IContainerSyncVar.java new file mode 100644 index 0000000..040a443 --- /dev/null +++ b/src/codechicken/core/inventory/IContainerSyncVar.java @@ -0,0 +1,14 @@ +package codechicken.core.inventory; + +import codechicken.lib.packet.PacketCustom; + +public interface IContainerSyncVar +{ + public boolean changed(); + + public void reset(); + + public void writeChange(PacketCustom packet); + + public void readChange(PacketCustom packet); +} diff --git a/src/codechicken/core/inventory/IntegerSync.java b/src/codechicken/core/inventory/IntegerSync.java new file mode 100644 index 0000000..8e54113 --- /dev/null +++ b/src/codechicken/core/inventory/IntegerSync.java @@ -0,0 +1,34 @@ +package codechicken.core.inventory; + +import codechicken.lib.packet.PacketCustom; + +public abstract class IntegerSync implements IContainerSyncVar +{ + public int c_value; + + @Override + public boolean changed() + { + return getValue() != c_value; + } + + @Override + public void reset() + { + c_value = getValue(); + } + + @Override + public void writeChange(PacketCustom packet) + { + packet.writeInt(getValue()); + } + + @Override + public void readChange(PacketCustom packet) + { + c_value = packet.readInt(); + } + + public abstract int getValue(); +} diff --git a/src/codechicken/core/inventory/MappedInventoryAccess.java b/src/codechicken/core/inventory/MappedInventoryAccess.java new file mode 100644 index 0000000..e4c9fd8 --- /dev/null +++ b/src/codechicken/core/inventory/MappedInventoryAccess.java @@ -0,0 +1,122 @@ +package codechicken.core.inventory; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class MappedInventoryAccess implements IInventory +{ + public static interface InventoryAccessor + { + public boolean canAccessSlot(int slot); + } + + public static final InventoryAccessor fullAccess = new InventoryAccessor() + { + public boolean canAccessSlot(int slot) { + return true; + } + }; + + private ArrayList slotMap = new ArrayList(); + private IInventory inv; + private ArrayList accessors = new ArrayList(); + + public MappedInventoryAccess(IInventory inv, InventoryAccessor... accessors) { + this.inv = inv; + + for (InventoryAccessor a : accessors) + this.accessors.add(a); + + reset(); + } + + public void reset() { + slotMap.clear(); + nextslot: + for (int i = 0; i < inv.getSizeInventory(); i++) { + for (InventoryAccessor a : accessors) + if (!a.canAccessSlot(i)) + continue nextslot; + + slotMap.add(i); + } + } + + @Override + public int getSizeInventory() { + return slotMap.size(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return inv.getStackInSlot(slotMap.get(slot)); + } + + @Override + public ItemStack decrStackSize(int slot, int amount) { + return inv.decrStackSize(slotMap.get(slot), amount); + } + + @Override + public ItemStack getStackInSlotOnClosing(int slot) { + return inv.getStackInSlotOnClosing(slotMap.get(slot)); + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack) { + inv.setInventorySlotContents(slotMap.get(slot), stack); + } + + @Override + public String getInventoryName() { + return inv.getInventoryName(); + } + + @Override + public int getInventoryStackLimit() { + return inv.getInventoryStackLimit(); + } + + @Override + public void markDirty() { + inv.markDirty(); + } + + @Override + public boolean isUseableByPlayer(EntityPlayer player) { + return inv.isUseableByPlayer(player); + } + + @Override + public void openInventory() { + inv.openInventory(); + } + + @Override + public void closeInventory() { + inv.closeInventory(); + } + + public void addAccessor(InventoryAccessor accessor) { + accessors.add(accessor); + reset(); + } + + @Override + public boolean isItemValidForSlot(int slot, ItemStack stack) { + return inv.isItemValidForSlot(slotMap.get(slot), stack); + } + + @Override + public boolean hasCustomInventoryName() { + return true; + } + + public List accessors() { + return accessors; + } +} diff --git a/src/codechicken/core/inventory/SlotDummy.java b/src/codechicken/core/inventory/SlotDummy.java new file mode 100644 index 0000000..bc5e837 --- /dev/null +++ b/src/codechicken/core/inventory/SlotDummy.java @@ -0,0 +1,73 @@ +package codechicken.core.inventory; + +import codechicken.lib.inventory.InventoryUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummy extends SlotHandleClicks +{ + public final int stackLimit; + public SlotDummy(IInventory inv, int slot, int x, int y) + { + this(inv, slot, x, y, 64); + } + + public SlotDummy(IInventory inv, int slot, int x, int y, int limit) + { + super(inv, slot, x, y); + stackLimit = limit; + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) + { + ItemStack held = player.inventory.getItemStack(); + boolean shift = modifier == 1; + slotClick(held, button, shift); + return null; + } + + public void slotClick(ItemStack held, int button, boolean shift) + { + ItemStack tstack = getStack(); + if(held != null && (tstack == null || !InventoryUtils.canStack(held, tstack))) + { + int quantity = Math.min(held.stackSize, stackLimit); + if(shift) + quantity = Math.min(stackLimit, held.getMaxStackSize()*16); + if(button == 1) + quantity = 1; + putStack(InventoryUtils.copyStack(held, quantity)); + } + else if(tstack != null) + { + int inc; + if(held != null) + { + inc = button == 1 ? -held.stackSize : held.stackSize; + if(shift) + inc *= 16; + } + else + { + inc = button == 1 ? -1 : 1; + if(shift) + inc *= 16; + } + int quantity = tstack.stackSize+inc; + if(quantity <= 0) + putStack(null); + else + putStack(InventoryUtils.copyStack(tstack, quantity)); + } + } + + @Override + public void putStack(ItemStack stack) + { + if(stack != null && stack.stackSize > stackLimit) + stack = InventoryUtils.copyStack(stack, stackLimit); + super.putStack(stack); + } +} diff --git a/src/codechicken/core/inventory/SlotDummyOutput.java b/src/codechicken/core/inventory/SlotDummyOutput.java new file mode 100644 index 0000000..2204cea --- /dev/null +++ b/src/codechicken/core/inventory/SlotDummyOutput.java @@ -0,0 +1,19 @@ +package codechicken.core.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummyOutput extends SlotHandleClicks +{ + public SlotDummyOutput(IInventory inv, int slot, int x, int y) + { + super(inv, slot, x, y); + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) + { + return null; + } +} diff --git a/src/codechicken/core/inventory/SlotHandleClicks.java b/src/codechicken/core/inventory/SlotHandleClicks.java new file mode 100644 index 0000000..b628a26 --- /dev/null +++ b/src/codechicken/core/inventory/SlotHandleClicks.java @@ -0,0 +1,16 @@ +package codechicken.core.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +public abstract class SlotHandleClicks extends Slot +{ + public SlotHandleClicks(IInventory inv, int slot, int x, int y) + { + super(inv, slot, x, y); + } + + public abstract ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier); +} diff --git a/src/codechicken/core/launch/CodeChickenCorePlugin.java b/src/codechicken/core/launch/CodeChickenCorePlugin.java new file mode 100644 index 0000000..8431ccf --- /dev/null +++ b/src/codechicken/core/launch/CodeChickenCorePlugin.java @@ -0,0 +1,173 @@ +package codechicken.core.launch; + +import java.awt.Desktop; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import javax.swing.JEditorPane; +import javax.swing.JOptionPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +import codechicken.core.asm.*; +import cpw.mods.fml.relauncher.CoreModManager; +import net.minecraft.launchwrapper.Launch; + +import cpw.mods.fml.common.versioning.DefaultArtifactVersion; +import cpw.mods.fml.common.versioning.VersionParser; +import cpw.mods.fml.relauncher.FMLInjectionData; +import cpw.mods.fml.relauncher.IFMLCallHook; +import cpw.mods.fml.relauncher.IFMLLoadingPlugin; +import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@TransformerExclusions(value = {"codechicken.core.asm", "codechicken.obfuscator"}) +public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook +{ + public static final String mcVersion = "[1.7.2]"; + + public static File minecraftDir; + public static String currentMcVersion; + public static Logger logger = LogManager.getLogger("CodeChickenCore"); + + public CodeChickenCorePlugin() { + if (minecraftDir != null) + return;//get called twice, once for IFMLCallHook + + minecraftDir = (File) FMLInjectionData.data()[6]; + currentMcVersion = (String) FMLInjectionData.data()[4]; + + DepLoader.load(); + injectDeobfPlugin(); + } + + private void injectDeobfPlugin() { + try { + Class wrapperClass = Class.forName("cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper"); + Constructor wrapperConstructor = wrapperClass.getConstructor(String.class, IFMLLoadingPlugin.class, File.class, Integer.TYPE, String[].class); + Field f_loadPlugins = CoreModManager.class.getDeclaredField("loadPlugins"); + wrapperConstructor.setAccessible(true); + f_loadPlugins.setAccessible(true); + ((List)f_loadPlugins.get(null)).add(2, wrapperConstructor.newInstance("CCCDeobfPlugin", new MCPDeobfuscationTransformer.LoadPlugin(), null, 0, new String[0])); + } catch (Exception e) { + System.err.println("Failed to inject MCPDeobfuscation Transformer"); + e.printStackTrace(); + } + } + + public static void versionCheck(String reqVersion, String mod) { + String mcVersion = (String) FMLInjectionData.data()[4]; + if (!VersionParser.parseRange(reqVersion).containsVersion(new DefaultArtifactVersion(mcVersion))) { + String err = "This version of " + mod + " does not support minecraft version " + mcVersion; + System.err.println(err); + + JEditorPane ep = new JEditorPane("text/html", + "" + + err + + "
Remove it from your coremods folder and check here for updates" + + ""); + + ep.setEditable(false); + ep.setOpaque(false); + ep.addHyperlinkListener(new HyperlinkListener() + { + @Override + public void hyperlinkUpdate(HyperlinkEvent event) { + try { + if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) + Desktop.getDesktop().browse(event.getURL().toURI()); + } catch (Exception e) {} + } + }); + + JOptionPane.showMessageDialog(null, ep, "Fatal error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + } + + @Override + public String[] getASMTransformerClass() { + versionCheck(mcVersion, "CodeChickenCore"); + return new String[]{ + "codechicken.lib.asm.ClassHeirachyManager", + "codechicken.core.asm.InterfaceDependancyTransformer", + "codechicken.core.asm.TweakTransformer", + "codechicken.core.asm.DelegatedTransformer", + "codechicken.core.asm.DefaultImplementationTransformer"}; + } + + @Override + public String getAccessTransformerClass() { + return "codechicken.core.asm.CodeChickenAccessTransformer"; + } + + @Override + public String getModContainerClass() { + return "codechicken.core.asm.CodeChickenCoreModContainer"; + } + + @Override + public String getSetupClass() { + return getClass().getName(); + } + + @Override + public void injectData(Map data) { + } + + @Override + public Void call() { + CodeChickenCoreModContainer.loadConfig(); + TweakTransformer.load(); + scanCodeChickenMods(); + + return null; + } + + private void scanCodeChickenMods() { + File modsDir = new File(minecraftDir, "mods"); + for (File file : modsDir.listFiles()) + scanMod(file); + File versionModsDir = new File(minecraftDir, "mods/" + currentMcVersion); + if (versionModsDir.exists()) + for (File file : versionModsDir.listFiles()) + scanMod(file); + } + + private void scanMod(File file) { + if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) + return; + + try { + JarFile jar = new JarFile(file); + try { + Manifest manifest = jar.getManifest(); + if (manifest == null) + return; + Attributes attr = manifest.getMainAttributes(); + if (attr == null) + return; + + String mapFile = attr.getValue("AccessTransformer"); + if (mapFile != null) + CodeChickenAccessTransformer.addTransformerMap(mapFile); + String transformer = attr.getValue("CCTransformer"); + if (transformer != null) + DelegatedTransformer.addTransformer(transformer, jar, file); + } finally { + jar.close(); + } + } catch (Exception e) { + e.printStackTrace(); + System.err.println("CodeChickenCore: Failed to read jar file: " + file.getName()); + } + } +} diff --git a/src/codechicken/core/launch/DepLoader.java b/src/codechicken/core/launch/DepLoader.java new file mode 100644 index 0000000..af578d6 --- /dev/null +++ b/src/codechicken/core/launch/DepLoader.java @@ -0,0 +1,601 @@ +package codechicken.core.launch; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import cpw.mods.fml.common.versioning.ComparableVersion; +import cpw.mods.fml.relauncher.FMLInjectionData; +import cpw.mods.fml.relauncher.FMLLaunchHandler; +import cpw.mods.fml.relauncher.IFMLCallHook; +import cpw.mods.fml.relauncher.IFMLLoadingPlugin; +import net.minecraft.launchwrapper.LaunchClassLoader; +import sun.misc.URLClassPath; +import sun.net.util.URLUtil; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import java.awt.*; +import java.awt.Dialog.ModalityType; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.*; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * For autodownloading stuff. + * This is really unoriginal, mostly ripped off FML, credits to cpw. + */ +public class DepLoader implements IFMLLoadingPlugin, IFMLCallHook { + private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 23); + private static final String owner = "CB's DepLoader"; + private static DepLoadInst inst; + + public interface IDownloadDisplay { + void resetProgress(int sizeGuess); + + void setPokeThread(Thread currentThread); + + void updateProgress(int fullLength); + + boolean shouldStopIt(); + + void updateProgressString(String string, Object... data); + + Object makeDialog(); + + void showErrorDialog(String name, String url); + } + + @SuppressWarnings("serial") + public static class Downloader extends JOptionPane implements IDownloadDisplay { + private JDialog container; + private JLabel currentActivity; + private JProgressBar progress; + boolean stopIt; + Thread pokeThread; + + private Box makeProgressPanel() { + Box box = Box.createVerticalBox(); + box.add(Box.createRigidArea(new Dimension(0, 10))); + JLabel welcomeLabel = new JLabel("" + owner + " is setting up your minecraft environment"); + box.add(welcomeLabel); + welcomeLabel.setAlignmentY(LEFT_ALIGNMENT); + welcomeLabel = new JLabel("Please wait, " + owner + " has some tasks to do before you can play"); + welcomeLabel.setAlignmentY(LEFT_ALIGNMENT); + box.add(welcomeLabel); + box.add(Box.createRigidArea(new Dimension(0, 10))); + currentActivity = new JLabel("Currently doing ..."); + box.add(currentActivity); + box.add(Box.createRigidArea(new Dimension(0, 10))); + progress = new JProgressBar(0, 100); + progress.setStringPainted(true); + box.add(progress); + box.add(Box.createRigidArea(new Dimension(0, 30))); + return box; + } + + @Override + public JDialog makeDialog() { + if (container != null) + return container; + + setMessageType(JOptionPane.INFORMATION_MESSAGE); + setMessage(makeProgressPanel()); + setOptions(new Object[]{"Stop"}); + addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getSource() == Downloader.this && evt.getPropertyName() == VALUE_PROPERTY) { + requestClose("This will stop minecraft from launching\nAre you sure you want to do this?"); + } + } + }); + container = new JDialog(null, "Hello", ModalityType.MODELESS); + container.setResizable(false); + container.setLocationRelativeTo(null); + container.add(this); + this.updateUI(); + container.pack(); + container.setMinimumSize(container.getPreferredSize()); + container.setVisible(true); + container.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + container.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + requestClose("Closing this window will stop minecraft from launching\nAre you sure you wish to do this?"); + } + }); + return container; + } + + protected void requestClose(String message) { + int shouldClose = JOptionPane.showConfirmDialog(container, message, "Are you sure you want to stop?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + if (shouldClose == JOptionPane.YES_OPTION) + container.dispose(); + + stopIt = true; + if (pokeThread != null) + pokeThread.interrupt(); + } + + @Override + public void updateProgressString(String progressUpdate, Object... data) { + //FMLLog.finest(progressUpdate, data); + if (currentActivity != null) + currentActivity.setText(String.format(progressUpdate, data)); + } + + @Override + public void resetProgress(int sizeGuess) { + if (progress != null) + progress.getModel().setRangeProperties(0, 0, 0, sizeGuess, false); + } + + @Override + public void updateProgress(int fullLength) { + if (progress != null) + progress.getModel().setValue(fullLength); + } + + @Override + public void setPokeThread(Thread currentThread) { + this.pokeThread = currentThread; + } + + @Override + public boolean shouldStopIt() { + return stopIt; + } + + @Override + public void showErrorDialog(String name, String url) { + JEditorPane ep = new JEditorPane("text/html", + "" + + owner + " was unable to download required library " + name + + "
Check your internet connection and try restarting or download it manually from" + + "
" + url + " and put it in your mods folder" + + ""); + + ep.setEditable(false); + ep.setOpaque(false); + ep.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent event) { + try { + if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) + Desktop.getDesktop().browse(event.getURL().toURI()); + } catch (Exception e) { + } + } + }); + + JOptionPane.showMessageDialog(null, ep, "A download error has occured", JOptionPane.ERROR_MESSAGE); + } + } + + public static class DummyDownloader implements IDownloadDisplay { + @Override + public void resetProgress(int sizeGuess) { + } + + @Override + public void setPokeThread(Thread currentThread) { + } + + @Override + public void updateProgress(int fullLength) { + } + + @Override + public boolean shouldStopIt() { + return false; + } + + @Override + public void updateProgressString(String string, Object... data) { + } + + @Override + public Object makeDialog() { + return null; + } + + @Override + public void showErrorDialog(String name, String url) { + } + } + + public static class Dependancy { + public String url; + public String[] filesplit; + public ComparableVersion version; + + public String existing; + /** + * Flag set to add this dep to the classpath immediately because it is required for a coremod. + */ + public boolean coreLib; + + public Dependancy(String url, String[] filesplit, boolean coreLib) { + this.url = url; + this.filesplit = filesplit; + this.coreLib = coreLib; + version = new ComparableVersion(filesplit[1]); + } + + public String getName() { + return filesplit[0]; + } + + public String fileName() { + return filesplit[0] + filesplit[1] + filesplit[2]; + } + } + + public static class DepLoadInst { + private File modsDir; + private File v_modsDir; + private IDownloadDisplay downloadMonitor; + private JDialog popupWindow; + + private Map depMap = new HashMap(); + private HashSet depSet = new HashSet(); + + public DepLoadInst() { + String mcVer = (String) FMLInjectionData.data()[4]; + File mcDir = (File) FMLInjectionData.data()[6]; + + modsDir = new File(mcDir, "mods"); + v_modsDir = new File(mcDir, "mods/" + mcVer); + if (!v_modsDir.exists()) + v_modsDir.mkdirs(); + } + + private void addClasspath(String name) { + try { + ((LaunchClassLoader) DepLoader.class.getClassLoader()).addURL(new File(v_modsDir, name).toURI().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + private void deleteMod(File mod) { + if (mod.delete()) + return; + + try { + ClassLoader cl = DepLoader.class.getClassLoader(); + URL url = mod.toURI().toURL(); + Field f_ucp = URLClassLoader.class.getDeclaredField("ucp"); + Field f_loaders = URLClassPath.class.getDeclaredField("loaders"); + Field f_lmap = URLClassPath.class.getDeclaredField("lmap"); + f_ucp.setAccessible(true); + f_loaders.setAccessible(true); + f_lmap.setAccessible(true); + + URLClassPath ucp = (URLClassPath) f_ucp.get(cl); + Closeable loader = ((Map) f_lmap.get(ucp)).remove(URLUtil.urlNoFragString(url)); + if (loader != null) { + loader.close(); + ((List) f_loaders.get(ucp)).remove(loader); + } + } catch (Exception e) { + e.printStackTrace(); + } + + if (!mod.delete()) { + mod.deleteOnExit(); + String msg = owner + " was unable to delete file " + mod.getPath() + " the game will now try to delete it on exit. If this dialog appears again, delete it manually."; + System.err.println(msg); + if (!GraphicsEnvironment.isHeadless()) + JOptionPane.showMessageDialog(null, msg, "An update error has occured", JOptionPane.ERROR_MESSAGE); + + System.exit(1); + } + } + + private void download(Dependancy dep) { + popupWindow = (JDialog) downloadMonitor.makeDialog(); + File libFile = new File(v_modsDir, dep.fileName()); + try { + URL libDownload = new URL(dep.url + '/' + dep.fileName()); + downloadMonitor.updateProgressString("Downloading file %s", libDownload.toString()); + System.out.format("Downloading file %s\n", libDownload.toString()); + URLConnection connection = libDownload.openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.setRequestProperty("User-Agent", "" + owner + " Downloader"); + int sizeGuess = connection.getContentLength(); + download(connection.getInputStream(), sizeGuess, libFile); + downloadMonitor.updateProgressString("Download complete"); + System.out.println("Download complete"); + + scanDepInfo(libFile); + } catch (Exception e) { + libFile.delete(); + if (downloadMonitor.shouldStopIt()) { + System.err.println("You have stopped the downloading operation before it could complete"); + System.exit(1); + return; + } + downloadMonitor.showErrorDialog(dep.fileName(), dep.url + '/' + dep.fileName()); + throw new RuntimeException("A download error occured", e); + } + } + + private void download(InputStream is, int sizeGuess, File target) throws Exception { + if (sizeGuess > downloadBuffer.capacity()) + throw new Exception(String.format("The file %s is too large to be downloaded by " + owner + " - the download is invalid", target.getName())); + + downloadBuffer.clear(); + + int bytesRead, fullLength = 0; + + downloadMonitor.resetProgress(sizeGuess); + try { + downloadMonitor.setPokeThread(Thread.currentThread()); + byte[] smallBuffer = new byte[1024]; + while ((bytesRead = is.read(smallBuffer)) >= 0) { + downloadBuffer.put(smallBuffer, 0, bytesRead); + fullLength += bytesRead; + if (downloadMonitor.shouldStopIt()) { + break; + } + downloadMonitor.updateProgress(fullLength); + } + is.close(); + downloadMonitor.setPokeThread(null); + downloadBuffer.limit(fullLength); + downloadBuffer.position(0); + } catch (InterruptedIOException e) { + // We were interrupted by the stop button. We're stopping now.. clear interruption flag. + Thread.interrupted(); + throw new Exception("Stop"); + } catch (IOException e) { + throw e; + } + + try { + /*String cksum = generateChecksum(downloadBuffer); + if (cksum.equals(validationHash)) + {*/ + if (!target.exists()) + target.createNewFile(); + + + downloadBuffer.position(0); + FileOutputStream fos = new FileOutputStream(target); + fos.getChannel().write(downloadBuffer); + fos.close(); + /*} + else + { + throw new RuntimeException(String.format("The downloaded file %s has an invalid checksum %s (expecting %s). The download did not succeed correctly and the file has been deleted. Please try launching again.", target.getName(), cksum, validationHash)); + }*/ + } catch (Exception e) { + throw e; + } + } + + private String checkExisting(String[] dependency) { + for (File f : modsDir.listFiles()) { + String[] split = splitFileName(f.getName()); + if (split == null || !split[0].equals(dependency[0])) + continue; + + if (f.renameTo(new File(v_modsDir, f.getName()))) + continue; + + deleteMod(f); + } + + for (File f : v_modsDir.listFiles()) { + String[] split = splitFileName(f.getName()); + if (split == null || !split[0].equals(dependency[0])) + continue; + + ComparableVersion found = new ComparableVersion(split[1]); + ComparableVersion requested = new ComparableVersion(dependency[1]); + + int cmp = found.compareTo(requested); + if (cmp < 0) { + System.out.println("Deleted old version " + f.getName()); + deleteMod(f); + return null; + } + if (cmp > 0) { + System.err.println("Warning: version of " + dependency[0] + ", " + split[1] + " is newer than request " + dependency[1]); + return f.getName(); + } + return f.getName();//found dependency + } + return null; + } + + public void load() { + scanDepInfos(); + if (depMap.isEmpty()) + return; + + loadDeps(); + activateDeps(); + } + + private void activateDeps() { + for (Dependancy dep : depMap.values()) + if (dep.coreLib) + addClasspath(dep.existing); + } + + private void loadDeps() { + downloadMonitor = FMLLaunchHandler.side().isClient() ? new Downloader() : new DummyDownloader(); + try { + while (!depSet.isEmpty()) { + Iterator it = depSet.iterator(); + Dependancy dep = depMap.get(it.next()); + it.remove(); + load(dep); + } + } finally { + if (popupWindow != null) { + popupWindow.setVisible(false); + popupWindow.dispose(); + } + } + } + + private void load(Dependancy dep) { + dep.existing = checkExisting(dep.filesplit); + if (dep.existing == null)//download dep + { + download(dep); + dep.existing = dep.fileName(); + } + } + + private List modFiles() { + List list = new LinkedList(); + list.addAll(Arrays.asList(modsDir.listFiles())); + list.addAll(Arrays.asList(v_modsDir.listFiles())); + return list; + } + + private void scanDepInfos() { + for (File file : modFiles()) { + if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) + continue; + + scanDepInfo(file); + } + } + + private void scanDepInfo(File file) { + try { + ZipFile zip = new ZipFile(file); + ZipEntry e = zip.getEntry("dependancies.info"); + if (e == null) e = zip.getEntry("dependencies.info"); + if (e != null) + loadJSon(zip.getInputStream(e)); + zip.close(); + } catch (Exception e) { + System.err.println("Failed to load dependencies.info from " + file.getName() + " as JSON"); + e.printStackTrace(); + } + } + + private void loadJSon(InputStream input) throws IOException { + InputStreamReader reader = new InputStreamReader(input); + JsonElement root = new JsonParser().parse(reader); + if (root.isJsonArray()) + loadJSonArr(root); + else + loadJson(root.getAsJsonObject()); + reader.close(); + } + + private void loadJSonArr(JsonElement root) throws IOException { + for (JsonElement node : root.getAsJsonArray()) + loadJson(node.getAsJsonObject()); + } + + private void loadJson(JsonObject node) throws IOException { + boolean obfuscated = ((LaunchClassLoader) DepLoader.class.getClassLoader()) + .getClassBytes("net.minecraft.world.World") == null; + + String testClass = node.get("class").getAsString(); + if (DepLoader.class.getResource("/" + testClass.replace('.', '/') + ".class") != null) + return; + + String repo = node.get("repo").getAsString(); + String file = node.get("file").getAsString(); + if (!obfuscated && node.has("dev")) + file = node.get("dev").getAsString(); + + boolean coreLib = node.has("coreLib") && node.get("coreLib").getAsBoolean(); + + String[] split = splitFileName(file); + if (split == null) + throw new RuntimeException("Invalid filename format for dependency: " + file); + + addDep(new Dependancy(repo, split, coreLib)); + } + + private void addDep(Dependancy newDep) { + if (mergeNew(depMap.get(newDep.getName()), newDep)) { + depMap.put(newDep.getName(), newDep); + depSet.add(newDep.getName()); + } + } + + private boolean mergeNew(Dependancy oldDep, Dependancy newDep) { + if (oldDep == null) + return true; + + Dependancy newest = newDep.version.compareTo(oldDep.version) > 0 ? newDep : oldDep; + newest.coreLib = newDep.coreLib || oldDep.coreLib; + + return newest == newDep; + } + } + + public static void load() { + if (inst == null) { + inst = new DepLoadInst(); + inst.load(); + } + } + + private static String[] splitFileName(String filename) { + Pattern p = Pattern.compile("(.+?)([\\d\\.\\w]+)(\\.[^\\d]+)"); + Matcher m = p.matcher(filename); + if (!m.matches()) + return null; + + return new String[]{m.group(1), m.group(2), m.group(3)}; + } + + @Override + public String[] getASMTransformerClass() { + return null; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Override + public String getSetupClass() { + return getClass().getName(); + } + + @Override + public void injectData(Map data) { + } + + @Override + public Void call() { + load(); + + return null; + } + + @Override + public String getAccessTransformerClass() { + return null; + } +} diff --git a/src/codechicken/obfuscator/ConstantObfuscator.java b/src/codechicken/obfuscator/ConstantObfuscator.java new file mode 100644 index 0000000..bb090c5 --- /dev/null +++ b/src/codechicken/obfuscator/ConstantObfuscator.java @@ -0,0 +1,83 @@ +package codechicken.obfuscator; + +import java.util.LinkedList; +import java.util.List; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.lib.asm.ObfMapping; + +import static org.objectweb.asm.tree.AbstractInsnNode.*; + +public class ConstantObfuscator implements Opcodes +{ + public ObfRemapper obf; + public List descCalls = new LinkedList(); + public List classCalls = new LinkedList(); + + public ConstantObfuscator(ObfRemapper obf, String[] a_classCalls, String[] a_descCalls) + { + this.obf = obf; + for(String callDesc : a_classCalls) + classCalls.add(ObfMapping.fromDesc(callDesc)); + + for(String callDesc : a_descCalls) + descCalls.add(ObfMapping.fromDesc(callDesc)); + } + + public void transform(ClassNode cnode) + { + for(MethodNode method : cnode.methods) + for(AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) + obfuscateInsnSeq(insn); + } + + private void obfuscateInsnSeq(AbstractInsnNode insn) + { + if(matchesClass(insn)) + { + LdcInsnNode node1 = (LdcInsnNode) insn; + node1.cst = obf.map((String) node1.cst); + } + if(matchesDesc(insn)) + { + LdcInsnNode node1 = (LdcInsnNode) insn; + LdcInsnNode node2 = (LdcInsnNode) node1.getNext(); + LdcInsnNode node3 = (LdcInsnNode) node2.getNext(); + ObfMapping mapping = new ObfMapping((String) node1.cst, (String) node2.cst, (String) node3.cst).map(obf); + node1.cst = mapping.s_owner; + node2.cst = mapping.s_name; + node3.cst = mapping.s_desc; + } + } + + private boolean matchesClass(AbstractInsnNode insn) + { + if(insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if(insn == null || insn.getType() != METHOD_INSN) return false; + for(ObfMapping m : classCalls) + if(m.matches((MethodInsnNode) insn)) + return true; + return false; + } + + private boolean matchesDesc(AbstractInsnNode insn) + { + if(insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if(insn == null || insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if(insn == null || insn.getType() != LDC_INSN) return false; + insn = insn.getNext(); + if(insn == null || insn.getType() != METHOD_INSN) return false; + for(ObfMapping m : descCalls) + if(m.matches((MethodInsnNode) insn)) + return true; + return false; + } +} diff --git a/src/codechicken/obfuscator/DummyOutputStream.java b/src/codechicken/obfuscator/DummyOutputStream.java new file mode 100644 index 0000000..50093ef --- /dev/null +++ b/src/codechicken/obfuscator/DummyOutputStream.java @@ -0,0 +1,13 @@ +package codechicken.obfuscator; + +import java.io.OutputStream; + +public class DummyOutputStream extends OutputStream +{ + public static DummyOutputStream instance = new DummyOutputStream(); + + @Override + public void write(int b) + { + } +} diff --git a/src/codechicken/obfuscator/IHeirachyEvaluator.java b/src/codechicken/obfuscator/IHeirachyEvaluator.java new file mode 100644 index 0000000..63b838f --- /dev/null +++ b/src/codechicken/obfuscator/IHeirachyEvaluator.java @@ -0,0 +1,20 @@ +package codechicken.obfuscator; + +import java.util.List; + +import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; + +public interface IHeirachyEvaluator +{ + /** + * @param desc The mapping descriptor of the class to evaluate heirachy for + * @return A list of parents (srg or obf names) + */ + public List getParents(ObfuscationEntry desc); + + /** + * @param srg_class The srg name of the class in question + * @return True if this class does not inherit from any obfuscated class. + */ + public boolean isLibClass(ObfuscationEntry desc); +} diff --git a/src/codechicken/obfuscator/ILogStreams.java b/src/codechicken/obfuscator/ILogStreams.java new file mode 100644 index 0000000..0992103 --- /dev/null +++ b/src/codechicken/obfuscator/ILogStreams.java @@ -0,0 +1,9 @@ +package codechicken.obfuscator; + +import java.io.PrintStream; + +public interface ILogStreams +{ + public PrintStream err(); + public PrintStream out(); +} diff --git a/src/codechicken/obfuscator/ObfDirection.java b/src/codechicken/obfuscator/ObfDirection.java new file mode 100644 index 0000000..3405be2 --- /dev/null +++ b/src/codechicken/obfuscator/ObfDirection.java @@ -0,0 +1,34 @@ +package codechicken.obfuscator; + +import codechicken.lib.asm.ObfMapping; +import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; + +public class ObfDirection +{ + public boolean obfuscate; + public boolean srg; + public boolean srg_cst; + + public ObfDirection setObfuscate(boolean obfuscate) + { + this.obfuscate = obfuscate; + return this; + } + + public ObfDirection setSearge(boolean srg) + { + this.srg = srg; + return this; + } + + public ObfDirection setSeargeConstants(boolean srg_cst) + { + this.srg_cst = srg_cst; + return this; + } + + public ObfMapping obfuscate(ObfuscationEntry map) + { + return srg ? map.srg : obfuscate ? map.obf : map.mcp; + } +} diff --git a/src/codechicken/obfuscator/ObfRemapper.java b/src/codechicken/obfuscator/ObfRemapper.java new file mode 100644 index 0000000..3cc70e7 --- /dev/null +++ b/src/codechicken/obfuscator/ObfRemapper.java @@ -0,0 +1,91 @@ +package codechicken.obfuscator; + +import org.objectweb.asm.commons.Remapper; + +import codechicken.obfuscator.ObfuscationMap.ObfuscationEntry; + +public class ObfRemapper extends Remapper +{ + public final ObfuscationMap obf; + public ObfDirection dir; + + public ObfRemapper(ObfuscationMap obf, ObfDirection dir) + { + this.obf = obf; + this.dir = dir; + } + + @Override + public String map(String name) + { + if(name.indexOf('$') >= 0) + return map(name.substring(0, name.indexOf('$')))+name.substring(name.indexOf('$')); + + ObfuscationEntry map; + if(dir.obfuscate) + map = obf.lookupMcpClass(name); + else + map = obf.lookupObfClass(name); + + if(map != null) + return dir.obfuscate(map).s_owner; + + return name; + } + + @Override + public String mapFieldName(String owner, String name, String desc) + { + ObfuscationEntry map; + if(dir.obfuscate) + map = obf.lookupMcpField(owner, name); + else + map = obf.lookupObfField(owner, name); + + if(map == null) + map = obf.lookupSrgField(owner, name); + + if(map != null) + return dir.obfuscate(map).s_name; + + return name; + } + + @Override + public String mapMethodName(String owner, String name, String desc) + { + if(owner.length() == 0 || owner.charAt(0) == '[') + return name; + + ObfuscationEntry map; + if(dir.obfuscate) + map = obf.lookupMcpMethod(owner, name, desc); + else + map = obf.lookupObfMethod(owner, name, desc); + + if(map == null) + map = obf.lookupSrg(name); + + if(map != null) + return dir.obfuscate(map).s_name; + + return name; + } + + @Override + public Object mapValue(Object cst) + { + if(cst instanceof String) + { + if(dir.srg_cst) + { + ObfuscationEntry map = obf.lookupSrg((String) cst); + if(map != null) + return dir.obfuscate(map).s_name; + } + return cst; + } + + return super.mapValue(cst); + } +} diff --git a/src/codechicken/obfuscator/ObfuscationMap.java b/src/codechicken/obfuscator/ObfuscationMap.java new file mode 100644 index 0000000..3350e5a --- /dev/null +++ b/src/codechicken/obfuscator/ObfuscationMap.java @@ -0,0 +1,343 @@ +package codechicken.obfuscator; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Function; +import com.google.common.collect.ArrayListMultimap; + +import codechicken.lib.asm.ObfMapping; + +public class ObfuscationMap +{ + public class ObfuscationEntry + { + public final ObfMapping obf; + public final ObfMapping srg; + public final ObfMapping mcp; + + public ObfuscationEntry(ObfMapping obf, ObfMapping srg, ObfMapping mcp) + { + this.obf = obf; + this.srg = srg; + this.mcp = mcp; + } + } + + private class ClassEntry extends ObfuscationEntry + { + public Map mcpMap = new HashMap(); + public Map srgMap = new HashMap(); + public Map obfMap = new HashMap(); + + public ClassEntry(String obf, String srg) + { + super(new ObfMapping(obf, "", ""), + new ObfMapping(srg, "", ""), + new ObfMapping(srg, "", "")); + } + + public ObfuscationEntry addEntry(ObfMapping obf_desc, ObfMapping srg_desc) + { + ObfuscationEntry entry = new ObfuscationEntry(obf_desc, srg_desc, srg_desc.copy()); + obfMap.put(obf_desc.s_name.concat(obf_desc.s_desc), entry); + srgMap.put(srg_desc.s_name, entry); + + if(srg_desc.s_name.startsWith("field_") || srg_desc.s_name.startsWith("func_")) + srgMemberMap.put(srg_desc.s_name, entry); + + return entry; + } + + public void inheritFrom(ClassEntry p) + { + inherit(obfMap, p.obfMap); + inherit(srgMap, p.srgMap); + inherit(mcpMap, p.mcpMap); + } + + private void inherit(Map child, Map parent) + { + for(Entry e : parent.entrySet()) + if(!child.containsKey(e.getKey())) + child.put(e.getKey(), e.getValue()); + } + } + + private Map srgMap = new HashMap(); + private Map obfMap = new HashMap(); + private ArrayListMultimap srgMemberMap = ArrayListMultimap.create(); + + private IHeirachyEvaluator heirachyEvaluator; + private HashSet mappedClasses = new HashSet(); + public ILogStreams log = SystemLogStreams.inst; + + public ObfuscationMap setHeirachyEvaluator(IHeirachyEvaluator eval) + { + heirachyEvaluator = eval; + return this; + } + + public ObfuscationMap setLog(ILogStreams log) + { + this.log = log; + return this; + } + + public ObfuscationEntry addClass(String obf, String srg) + { + return addEntry(new ObfMapping(obf, "", ""), new ObfMapping(srg, "", "")); + } + + public ObfuscationEntry addField(String obf_owner, String obf_name, String srg_owner, String srg_name) + { + return addEntry(new ObfMapping(obf_owner, obf_name, ""), + new ObfMapping(srg_owner, srg_name, "")); + } + + public ObfuscationEntry addMethod(String obf_owner, String obf_name, String obf_desc, String srg_owner, String srg_name, String srg_desc) + { + return addEntry(new ObfMapping(obf_owner, obf_name, obf_desc), + new ObfMapping(srg_owner, srg_name, srg_desc)); + } + + public ObfuscationEntry addEntry(ObfMapping obf_desc, ObfMapping srg_desc) + { + ClassEntry e = srgMap.get(srg_desc.s_owner); + if(e == null) + { + e = new ClassEntry(obf_desc.s_owner, srg_desc.s_owner); + obfMap.put(obf_desc.s_owner, e); + srgMap.put(srg_desc.s_owner, e); + } + if(obf_desc.s_name.length() > 0) + return e.addEntry(obf_desc, srg_desc); + + return e; + } + + public void addMcpName(String srg_name, String mcp_name) + { + List entries = srgMemberMap.get(srg_name); + if(entries.isEmpty()) + { + log.err().println("Tried to add mcp name ("+mcp_name+") for unknown srg key ("+srg_name+")"); + return; + } + for(ObfuscationEntry entry : entries) + { + entry.mcp.s_name = mcp_name; + srgMap.get(entry.srg.s_owner).mcpMap.put(entry.mcp.s_name.concat(entry.mcp.s_desc), entry); + } + } + + public ObfuscationEntry lookupSrg(String srg_key) + { + List list = srgMemberMap.get(srg_key); + return list.isEmpty() ? null : list.get(0); + } + + public ObfuscationEntry lookupMcpClass(String name) + { + return srgMap.get(name); + } + + public ObfuscationEntry lookupObfClass(String name) + { + return obfMap.get(name); + } + + public ObfuscationEntry lookupMcpField(String owner, String name) + { + return lookupMcpMethod(owner, name, ""); + } + + public ObfuscationEntry lookupSrgField(String owner, String name) + { + if(name.startsWith("field_")) + { + ObfuscationEntry e = lookupSrg(name); + if(e != null) + return e; + } + + evaluateHeirachy(owner); + ClassEntry e = srgMap.get(owner); + return e == null ? null : e.srgMap.get(name); + } + + public ObfuscationEntry lookupObfField(String owner, String name) + { + return lookupObfMethod(owner, name, ""); + } + + public ObfuscationEntry lookupMcpMethod(String owner, String name, String desc) + { + evaluateHeirachy(owner); + ClassEntry e = srgMap.get(owner); + return e == null ? null : e.mcpMap.get(name.concat(desc)); + } + + public ObfuscationEntry lookupObfMethod(String owner, String name, String desc) + { + evaluateHeirachy(owner); + ClassEntry e = obfMap.get(owner); + return e == null ? null : e.obfMap.get(name.concat(desc)); + } + + private boolean isMapped(ObfuscationEntry desc) + { + return mappedClasses.contains(desc.srg.s_owner); + } + + private ObfuscationEntry getOrCreateClassEntry(String name) + { + ObfuscationEntry e = lookupObfClass(name); + if(e == null) + e = lookupMcpClass(name); + if(e == null) + e = addClass(name, name);//if the class isn't in obf or srg maps, it must be a custom mod class with no name change. + return e; + } + + public ObfuscationEntry evaluateHeirachy(String name) + { + ObfuscationEntry desc = getOrCreateClassEntry(name); + if(isMapped(desc)) + return desc; + + mappedClasses.add(desc.srg.s_owner); + if(heirachyEvaluator == null) + throw new IllegalArgumentException("Cannot call method/field mappings if a heirachy evaluator is not set."); + + if(!heirachyEvaluator.isLibClass(desc)) + { + List parents = heirachyEvaluator.getParents(desc); + if(parents == null) + log.err().println("Could not find class: "+desc.srg.s_owner); + else + for(String parent : parents) + inherit(desc, evaluateHeirachy(parent)); + } + + return desc; + } + + public void inherit(ObfuscationEntry desc, ObfuscationEntry p_desc) + { + inherit(desc.srg.s_owner, p_desc.srg.s_owner); + } + + public void inherit(String name, String parent) + { + ClassEntry e = srgMap.get(name); + if(e == null) + throw new IllegalStateException("Tried to inerit to an undefined class: "+name+" extends "+parent); + ClassEntry p = srgMap.get(parent); + if(p == null) + throw new IllegalStateException("Tried to inerit from undefired parent: "+name+" extends "+parent); + e.inheritFrom(p); + } + + public void parseMappings(File[] mappings) + { + parseSRGS(mappings[0]); + parseCSV(mappings[1]); + parseCSV(mappings[2]); + } + + public static String[] splitLast(String s, char c) + { + int i = s.lastIndexOf(c); + return new String[]{s.substring(0, i), s.substring(i+1)}; + } + + public static String[] split4(String s, char c) + { + String[] split = new String[4]; + int i2 = s.indexOf(c); + split[0] = s.substring(0, i2); + int i = i2+1; + i2 = s.indexOf(c, i); + split[1] = s.substring(i, i2); + i = i2+1; + i2 = s.indexOf(c, i); + split[2] = s.substring(i, i2); + i = i2+1; + i2 = s.indexOf(c, i); + split[3] = s.substring(i); + return split; + } + + private void parseSRGS(File srgs) + { + log.out().println("Parsing "+srgs.getName()); + + Function function = new Function() + { + @Override + public Void apply(String line) + { + int hpos = line.indexOf('#'); + if(hpos > 0) + line = line.substring(0, hpos).trim(); + if(line.startsWith("CL: ")) + { + String[] params = splitLast(line.substring(4), ' '); + addClass(params[0], params[1]); + } + else if(line.startsWith("FD: ")) + { + String[] params = splitLast(line.substring(4), ' '); + String[] p1 = splitLast(params[0], '/'); + String[] p2 = splitLast(params[1], '/'); + addField(p1[0], p1[1], + p2[0], p2[1]); + return null; + } + else if(line.startsWith("MD: ")) + { + String[] params = split4(line.substring(4), ' '); + String[] p1 = splitLast(params[0], '/'); + String[] p2 = splitLast(params[2], '/'); + addMethod(p1[0], p1[1], params[1], + p2[0], p2[1], params[3]); + return null; + } + return null; + } + }; + + ObfuscationRun.processLines(srgs, function); + } + + private void parseCSV(File csv) + { + log.out().println("Parsing "+csv.getName()); + + Function function = new Function() + { + @Override + public Void apply(String line) + { + if(line.startsWith("func_") || line.startsWith("field_")) + { + int i = line.indexOf(','); + String srg = line.substring(0, i); + int i2 = i+1; + i = line.indexOf(',', i2); + String mcp = line.substring(i2, i); + + addMcpName(srg, mcp); + } + return null; + } + }; + + ObfuscationRun.processLines(csv, function); + } +} diff --git a/src/codechicken/obfuscator/ObfuscationRun.java b/src/codechicken/obfuscator/ObfuscationRun.java new file mode 100644 index 0000000..67e2178 --- /dev/null +++ b/src/codechicken/obfuscator/ObfuscationRun.java @@ -0,0 +1,253 @@ +package codechicken.obfuscator; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.PrintStream; +import java.text.DecimalFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.commons.RemappingClassAdapter; +import org.objectweb.asm.tree.ClassNode; + +import com.google.common.base.Function; + +public class ObfuscationRun implements ILogStreams +{ + public final ObfDirection obfDir; + public final ObfuscationMap obf; + public final ObfRemapper obfMapper; + public final ConstantObfuscator cstMappper; + + public File[] mappings; + public Map config; + + private PrintStream out = System.out; + private PrintStream err = System.err; + private PrintStream quietStream = new PrintStream(DummyOutputStream.instance); + + private boolean verbose; + private boolean quiet; + + public boolean clean; + private long startTime; + private boolean finished; + + public ObfuscationRun(boolean obfuscate, File[] mappings, Map config) + { + obfDir = new ObfDirection().setObfuscate(obfuscate); + this.mappings = mappings; + this.config = config; + + obf = new ObfuscationMap().setLog(this); + obfMapper = new ObfRemapper(obf, obfDir); + cstMappper = new ConstantObfuscator(obfMapper, + config.get("classConstantCalls").split(","), + config.get("descConstantCalls").split(",")); + } + + public ObfuscationRun setClean() + { + clean = true; + return this; + } + + public ObfuscationRun setVerbose() + { + verbose = true; + return this; + } + + public ObfuscationRun setQuiet() + { + quiet = true; + return this; + } + + public ObfuscationRun setOut(PrintStream p) + { + out = p; + return this; + } + + public PrintStream out() + { + return quiet ? quietStream : out; + } + + public PrintStream fine() + { + return verbose ? out : quietStream; + } + + public ObfuscationRun setErr(PrintStream p) + { + err = p; + return this; + } + + public PrintStream err() + { + return quiet ? quietStream : err; + } + + public ObfuscationRun setSearge() + { + obfDir.setSearge(true); + return this; + } + + public ObfuscationRun setSeargeConstants() + { + obfDir.setSeargeConstants(true); + return this; + } + + public void start() + { + startTime = System.currentTimeMillis(); + } + + public static Map fillDefaults(Map config) + { + if(!config.containsKey("excludedPackages")) + config.put("excludedPackages", "java/;sun/;javax/;scala/;" + + "argo/;org/lwjgl/;org/objectweb/;org/bouncycastle/;com/google/"); + if(!config.containsKey("ignore")) + config.put("ignore", "."); + if(!config.containsKey("classConstantCalls")) + config.put("classConstantCalls", + "codechicken/lib/asm/ObfMapping.(Ljava/lang/String;)V," + + "codechicken/lib/asm/ObfMapping.subclass(Ljava/lang/String;)Lcodechicken/lib/asm/ObfMapping;," + + "codechicken/lib/asm/ObfMapping.(Lcodechicken/lib/asm/ObfMapping;Ljava/lang/String;)V"); + if(!config.containsKey("descConstantCalls")) + config.put("descConstantCalls", + "codechicken/lib/asm/ObfMapping.(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/MethodVisitor.visitFieldInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/MethodNode.visitFieldInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/MethodVisitor.visitMethodInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/MethodNode.visitMethodInsn(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/MethodInsnNode.(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V," + + "org/objectweb/asm/tree/FieldInsnNode.(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + return config; + } + + public static void processLines(File file, Function function) + { + try + { + BufferedReader reader = new BufferedReader(new FileReader(file)); + String line; + while((line = reader.readLine()) != null) + function.apply(line); + reader.close(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + public static void processFiles(File dir, Function function, boolean recursive) + { + for(File file : dir.listFiles()) + if(file.isDirectory() && recursive) + processFiles(file, function, recursive); + else + function.apply(file); + } + + public static void deleteDir(File directory, boolean remove) + { + if(!directory.exists()) + { + if(!remove)directory.mkdirs(); + return; + } + for(File file : directory.listFiles()) + { + if(file.isDirectory()) + { + deleteDir(file, true); + } + else + { + if(!file.delete()) + throw new RuntimeException("Delete Failed: "+file); + } + } + if(remove) + { + if(!directory.delete()) + throw new RuntimeException("Delete Failed: "+directory); + } + } + + public static File[] parseConfDir(File confDir) { + File srgDir = new File(confDir, "conf"); + if(!srgDir.exists()) + srgDir = confDir; + + File srgs = new File(srgDir, "packaged.srg"); + if(!srgs.exists()) + srgs = new File(srgDir, "joined.srg"); + if(!srgs.exists()) + throw new RuntimeException("Could not find packaged.srg or joined.srg"); + + File mapDir = new File(confDir, "mappings"); + if(!mapDir.exists()) + mapDir = confDir; + + File methods = new File(mapDir, "methods.csv"); + if(!methods.exists()) + throw new RuntimeException("Could not find methods.csv"); + File fields = new File(mapDir, "fields.csv"); + if(!fields.exists()) + throw new RuntimeException("Could not find fields.csv"); + + return new File[]{srgs, methods, fields}; + } + + public long startTime() + { + return startTime; + } + + public void remap(ClassNode cnode, ClassVisitor cv) + { + cstMappper.transform(cnode); + cnode.accept(new RemappingClassAdapter(cv, obfMapper)); + } + + public static List getParents(ClassNode cnode) + { + List parents = new LinkedList(); + if(cnode.superName != null) + parents.add(cnode.superName); + + for(String s_interface : cnode.interfaces) + parents.add(s_interface); + + return parents; + } + + public void finish(boolean errored) + { + long millis = System.currentTimeMillis()-startTime; + out().println((errored ? "Errored after" : "Done in ")+new DecimalFormat("0.00").format(millis/1000D)+"s"); + finished = true; + } + + public boolean finished() + { + return finished; + } + + public void parseMappings() + { + obf.parseMappings(mappings); + } +} diff --git a/src/codechicken/obfuscator/SystemLogStreams.java b/src/codechicken/obfuscator/SystemLogStreams.java new file mode 100644 index 0000000..1da29e2 --- /dev/null +++ b/src/codechicken/obfuscator/SystemLogStreams.java @@ -0,0 +1,20 @@ +package codechicken.obfuscator; + +import java.io.PrintStream; + +public class SystemLogStreams implements ILogStreams +{ + public static SystemLogStreams inst = new SystemLogStreams(); + + @Override + public PrintStream err() + { + return System.err; + } + + @Override + public PrintStream out() + { + return System.out; + } +}