commit 0d94e9ac7f3c92f112a78655cd0a78db71cbf85d Author: Chicken-Bomes Date: Tue Apr 29 00:35:55 2014 +1000 Initial commit 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 0000000..3c7abdf Binary files /dev/null and b/build/gradle/wrapper/gradle-wrapper.jar differ 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; + } +}