Compare commits

..
30 Commits
Author SHA1 Message Date
Worive fd7d5c269f Bound compressed packet decoding (#58) 2026-11-13 18:20:48 +01:00
UltraProdigy 89dac36ebc ci: run build and test on release branches 2026-11-09 02:19:02 -05:00
Nikolay S. 18bac36c10 Deprecate index-based field selection (#52) 2026-11-01 14:15:34 +01:00
Jarnexis a9643dba1c Add initial Ukrainian (uk_UA) localization for CodeChickenCore (#55) 2026-09-19 20:15:54 +02:00
Martin Robertz 31d26db5b4 update+gradle+bs+deps+bs+gitignore
fix curse auto uploader
2026-09-17 22:32:16 +02:00
Algent 2077a8da86 Fix missing colour tint on block particles (#57) 2026-09-14 10:51:15 +02:00
Ryan Nasers ba757e4237 Fixing silly read write issue (#56) 2026-09-02 16:59:45 +02:00
DrParadox7 b738e8e073 NPE check on onChunkUnload (#51) 2026-08-21 17:39:04 +02:00
Jason Mitchell a385d29ab3 Additional thread saftey for CCL (#50) 2026-07-22 15:28:54 -07:00
Jason Mitchell 60d44f3c7b Skip MR class files (#49) 2026-06-16 10:35:36 -07:00
Ranzu 6209831d08 Revert #47 (#48) 2026-06-07 19:36:40 +00:00
Ranzu 7ae8b15810 Convert colors to ColorUtils (#47) 2026-06-07 19:17:10 +02:00
Alexdoru 08f3e4d42a intern superclass and interfaces of ClassHeirachyManager data (#46) 2026-05-24 15:19:20 +02:00
Martin Robertz 9bd4c63b00 update gradle+deps+bs 2026-03-10 19:37:26 +01:00
Ranzu 6e90633d31 Added color override helper (#43) 2026-03-10 13:59:10 +01:00
danyadevandAlexdoru f02dbc956c optimize ClassHeirachyManager (#42)
Co-authored-by: Alexdoru <[email protected]>
2026-03-05 17:41:52 +00:00
boubou19 63f36ca383 Reapply "Fix ItemStack packet id limited by short" (#23) (#37) (#41) 2025-12-07 16:17:57 +00:00
Alexdoru bb008a1265 [Memory-opti:static allocations] Reduce allocations and speedup ClassHeirarchyManager (#39) 2025-12-05 22:21:23 +01:00
TacoMagickandRecursivePineapple bb98bdc6d4 Revert "Fix ItemStack packet id limited by short" (#23) (#37)
Co-authored-by: RecursivePineapple <[email protected]>
2025-11-11 17:21:20 +00:00
Alexdoru 4e5bcf829c ASM Housekeeping (#36) 2025-11-11 12:13:45 -05:00
Alexdoru 678970e48b Delete DepLoader entirely (#35) 2025-08-11 16:52:51 +02:00
AlexdoruandMartin Robertz 5ad7ebe1ac further empty DepLoader (#34)
Co-authored-by: Martin Robertz <[email protected]>
2025-08-09 23:51:19 +02:00
Dream Master 033009fed5 update 2025-08-08 18:58:12 +07:00
Alexdoru acd863e748 Speedup ClassDiscoverer by using ClassReader.SKIP_CODE (#33) 2025-08-07 22:10:04 +02:00
Martin Robertz 850e2b9368 update 2025-05-29 22:09:14 +02:00
FalsePattern 4713f7aebd modern java compat (#31) 2025-05-29 19:26:51 +00:00
Jason Mitchell b999279656 More thread safe, again. (#30) 2025-05-04 08:08:02 +09:00
mts2200 1879ba2071 Improve NPE checks (#26) 2025-01-08 18:51:06 +00:00
Martin Robertz 9d600169ac update 2024-12-22 01:38:26 +01:00
charagarlnad 4e7eb37f3f use ZipFile instead of ZipInputStream in ClassDiscoverer (#25) 2024-12-20 15:18:04 +01:00
51 changed files with 1331 additions and 692 deletions

No files matched your search

+2 -2
View File
@@ -3,9 +3,9 @@ name: Build and test
on: on:
pull_request: pull_request:
branches: [ master, main ] branches: [ master, main, release/** ]
push: push:
branches: [ master, main ] branches: [ master, main, release/** ]
jobs: jobs:
build-and-test: build-and-test:
+3
View File
@@ -27,3 +27,6 @@ whitelist.json
*.iws *.iws
src/main/resources/mixins.*.json src/main/resources/mixins.*.json
*.bat *.bat
.DS_Store
*.sh
+7
View File
@@ -6,3 +6,10 @@ minecraft {
} }
} }
} }
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
+5
View File
@@ -3,3 +3,8 @@
plugins { plugins {
id 'com.gtnewhorizons.gtnhconvention' id 'com.gtnewhorizons.gtnhconvention'
} }
minecraft {
//extraRunJvmArguments.add("-Dccc.dev.runtimePublic=true")
//extraRunJvmArguments.add("-Dccc.dev.deobfuscate=true")
}
+3
View File
@@ -1,4 +1,7 @@
// Add your dependencies here // Add your dependencies here
dependencies { dependencies {
testImplementation(platform('org.junit:junit-bom:5.14.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
} }
+46 -6
View File
@@ -14,7 +14,7 @@ modId = CodeChickenCore
modGroup = codechicken modGroup = codechicken
# Whether to use modGroup as the maven publishing group. # Whether to use modGroup as the maven publishing group.
# Due to a history of using JitPack, the default is com.github.GTNewHorizons for all mods. # When false, com.github.GTNewHorizons is used.
useModGroupForPublishing = false useModGroupForPublishing = false
# Updates your build.gradle and settings.gradle automatically whenever an update is available. # Updates your build.gradle and settings.gradle automatically whenever an update is available.
@@ -39,9 +39,33 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE. # `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
developmentEnvironmentUserName = Developer developmentEnvironmentUserName = Developer
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8. # Enables modern Java syntax support. Valid values:
# See https://github.com/bsideup/jabel for details on how this works. # - false: No modern syntax, Java 8 only
enableModernJavaSyntax = true # - jabel: Jabel syntax-only support, compiles to J8 bytecode
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
# - modern: Native modern Java bytecode, no downgrading
enableModernJavaSyntax = jabel
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
# and should *not* be used in most situations. -1 disables this.
# forceToolchainVersion = -1
# Target JVM version for JVM Downgrader bytecode downgrading.
# Only used when enableModernJavaSyntax = jvmDowngrader
# downgradeTargetVersion = 8
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
# Classes will be available in META-INF/versions/N/ for each version N in this list.
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
# jvmDowngraderMultiReleaseVersions = 21,25
# Specifies how JVM Downgrader API stubs are provided. Options:
# - shade: Shade minimized stubs into the jar
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
# - external: Another dependency provides stubs (no constraint, no warning)
# - (empty): Warning reminding you to configure stubs
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
# jvmDowngraderStubsProvider =
# Enables injecting missing generics into the decompiled source code for a better coding experience. # Enables injecting missing generics into the decompiled source code for a better coding experience.
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types. # Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
@@ -90,7 +114,9 @@ usesMixinDebug = false
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise. # Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
mixinPlugin = mixinPlugin =
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail! # Specify the package that contains all of your Mixins. The package must exist or
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
# it must match with this or the build will fail.
mixinsPackage = mixinsPackage =
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin! # Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
@@ -145,7 +171,7 @@ modrinthProjectId = codechickencore-unofficial
# type can be one of [project, version], # type can be one of [project, version],
# and the name is the Modrinth project or version slug/id of the other mod. # and the name is the Modrinth project or version slug/id of the other mod.
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech # Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true # Note: UniMixins is automatically set as a required dependency if usesMixins = true.
modrinthRelations = modrinthRelations =
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens. # Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
@@ -154,6 +180,11 @@ modrinthRelations =
# Leave this empty if you don't want to publish on CurseForge. # Leave this empty if you don't want to publish on CurseForge.
curseForgeProjectId = 746279 curseForgeProjectId = 746279
# Comma-separated supported CurseForge environments: client, server (case-insensitive).
# Use client or server for mods that only support one environment. Defaults to client,server.
# These tags describe where the mod can run, not whether installation is required on both sides.
curseForgeEnvironments = client,server
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge. # The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
# Syntax: type1:name1;type2:name2;... # Syntax: type1:name1;type2:name2;...
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible], # Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
@@ -166,6 +197,12 @@ curseForgeRelations =
# projects. New projects should not use this parameter. # projects. New projects should not use this parameter.
# customArchiveBaseName = # customArchiveBaseName =
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
# runClientWorkingDirectory = run/client
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
# runServerWorkingDirectory = run/server
# Optional parameter to have the build automatically fail if an illegal version is used. # Optional parameter to have the build automatically fail if an illegal version is used.
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'. # This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
# The check is ONLY performed if the version is a git tag. # The check is ONLY performed if the version is a git tag.
@@ -195,3 +232,6 @@ curseForgeRelations =
# This is meant to be set in $HOME/.gradle/gradle.properties. # This is meant to be set in $HOME/.gradle/gradle.properties.
# ideaCheckSpotlessOnBuild = true # ideaCheckSpotlessOnBuild = true
# Non-GTNH properties
org.gradle.configuration-cache = true
org.gradle.parallel = true
+12
View File
@@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9064f22734b42ac03e85df04b632e167/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/4d0277c49991306ac78048580fe34bcf/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a9fa3b809e484a1e1ac4308a5dda661a/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/aa99913fa6d767d899ffc96082da01ca/redirect
toolchainVersion=25
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+7 -11
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
############################################################################## ##############################################################################
# #
# Gradle start up script for POSIX generated by Gradle. # gradlew start up script for POSIX generated by Gradle.
# #
# Important for running: # Important for running:
# #
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole # bash, then to run this script, type that shell name before the whole
# command line, like: # command line, like:
# #
# ksh Gradle # ksh gradlew
# #
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@@ -115,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -173,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -206,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.
Vendored
+12 -24
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem gradlew startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables, and ensure extensions are enabled
if "%OS%"=="Windows_NT" setlocal setlocal EnableExtensions
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,30 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute gradlew
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end :exitWithErrorLevel
@rem End local scope for the variables with windows NT shell @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
if %ERRORLEVEL% equ 0 goto mainEnd "%COMSPEC%" /c exit %ERRORLEVEL%
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+1
View File
@@ -1,4 +1,5 @@
// Add any additional repositiroes for your dependencies here // Add any additional repositiroes for your dependencies here
repositories { repositories {
mavenLocal()
} }
+1 -1
View File
@@ -17,7 +17,7 @@ pluginManagement {
} }
plugins { plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.29' id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.30'
} }
@@ -1,16 +1,17 @@
package codechicken.core; package codechicken.core;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipFile;
import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.Launch;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -22,6 +23,7 @@ import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModClassLoader; import cpw.mods.fml.common.ModClassLoader;
import cpw.mods.fml.relauncher.CoreModManager; import cpw.mods.fml.relauncher.CoreModManager;
@SuppressWarnings("unused")
public class ClassDiscoverer { public class ClassDiscoverer {
public IStringMatcher matcher; public IStringMatcher matcher;
@@ -35,17 +37,12 @@ public class ClassDiscoverer {
for (int i = 0; i < superclasses.length; i++) for (int i = 0; i < superclasses.length; i++)
this.superclasses[i] = superclasses[i].getName().replace('.', '/'); this.superclasses[i] = superclasses[i].getName().replace('.', '/');
classes = new ArrayList<Class<?>>(); classes = new ArrayList<>();
modClassLoader = (ModClassLoader) Loader.instance().getModClassLoader(); modClassLoader = (ModClassLoader) Loader.instance().getModClassLoader();
} }
public ClassDiscoverer(Class<?>... superclasses) { public ClassDiscoverer(Class<?>... superclasses) {
this(new IStringMatcher() { this(test -> true, superclasses);
public boolean matches(String test) {
return true;
}
}, superclasses);
} }
public ArrayList<Class<?>> findClasses() { public ArrayList<Class<?>> findClasses() {
@@ -63,7 +60,7 @@ public class ClassDiscoverer {
byte[] bytes = Launch.classLoader.getClassBytes(classname); byte[] bytes = Launch.classLoader.getClassBytes(classname);
if (bytes == null) return; if (bytes == null) return;
ClassNode cnode = ASMHelper.createClassNode(bytes); ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.SKIP_CODE);
for (String superclass : superclasses) for (String superclass : superclasses)
if (!cnode.interfaces.contains(superclass) && !cnode.superName.equals(superclass)) return; if (!cnode.interfaces.contains(superclass) && !cnode.superName.equals(superclass)) return;
@@ -86,7 +83,7 @@ public class ClassDiscoverer {
List<String> knownLibraries = ImmutableList.<String>builder().addAll(modClassLoader.getDefaultLibraries()) List<String> knownLibraries = ImmutableList.<String>builder().addAll(modClassLoader.getDefaultLibraries())
.addAll(CoreModManager.getLoadedCoremods()).build(); .addAll(CoreModManager.getLoadedCoremods()).build();
File[] minecraftSources = modClassLoader.getParentSources(); File[] minecraftSources = modClassLoader.getParentSources();
HashSet<String> searchedSources = new HashSet<String>(); HashSet<String> searchedSources = new HashSet<>();
for (File minecraftSource : minecraftSources) { for (File minecraftSource : minecraftSources) {
if (searchedSources.contains(minecraftSource.getAbsolutePath())) continue; if (searchedSources.contains(minecraftSource.getAbsolutePath())) continue;
searchedSources.add(minecraftSource.getAbsolutePath()); searchedSources.add(minecraftSource.getAbsolutePath());
@@ -114,21 +111,19 @@ public class ClassDiscoverer {
} }
private void readFromZipFile(File file) throws IOException { private void readFromZipFile(File file) throws IOException {
FileInputStream fileinputstream = new FileInputStream(file); try (ZipFile zipFile = new ZipFile(file)) {
ZipInputStream zipinputstream = new ZipInputStream(fileinputstream); Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
do { while (zipEntries.hasMoreElements()) {
ZipEntry zipentry = zipinputstream.getNextEntry(); ZipEntry zipentry = zipEntries.nextElement();
if (zipentry == null) { String fullname = zipentry.getName().replace('\\', '/');
break; if (fullname.startsWith("META-INF/versions/")) continue;
int pos = fullname.lastIndexOf('/');
String name = pos == -1 ? fullname : fullname.substring(pos + 1);
if (!zipentry.isDirectory() && matcher.matches(name)) {
checkAddClass(fullname);
}
} }
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) { private void readFromDirectory(File directory, File basedirectory) {
@@ -2,12 +2,16 @@ package codechicken.core;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.network.NetworkManager; import net.minecraft.network.NetworkManager;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.EnumChatFormatting;
import net.minecraft.world.World; import net.minecraft.world.World;
import codechicken.core.internal.CCCEventHandler; import codechicken.core.internal.CCCEventHandler;
import codechicken.lib.colour.LocalizedColours;
import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.Side;
@@ -65,4 +69,16 @@ public class ClientUtils extends CommonUtils {
.replace("Supporters:", EnumChatFormatting.AQUA + "Supporters:"); .replace("Supporters:", EnumChatFormatting.AQUA + "Supporters:");
GuiModListScroll.register(mod); GuiModListScroll.register(mod);
} }
@SideOnly(Side.CLIENT)
public static void registerLocalizedColourReloadListener() {
((IReloadableResourceManager) mc().getResourceManager())
.registerReloadListener(new IResourceManagerReloadListener() {
@Override
public void onResourceManagerReload(IResourceManager resourceManager) {
LocalizedColours.reloadLocalizedColours();
}
});
}
} }
@@ -36,6 +36,7 @@ import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import codechicken.core.launch.CodeChickenCorePlugin; import codechicken.core.launch.CodeChickenCorePlugin;
import codechicken.lib.colour.LocalizedColours;
import codechicken.lib.gui.GuiDraw; import codechicken.lib.gui.GuiDraw;
import codechicken.lib.vec.Rectangle4i; import codechicken.lib.vec.Rectangle4i;
import cpw.mods.fml.client.GuiModList; import cpw.mods.fml.client.GuiModList;
@@ -148,10 +149,11 @@ public class GuiModListScroll {
double dy = scroll % (height + 20); double dy = scroll % (height + 20);
GL11.glPushMatrix(); GL11.glPushMatrix();
GL11.glTranslated(0, -dy, 0); GL11.glTranslated(0, -dy, 0);
GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, 0xDDDDDD); GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, LocalizedColours.MOD_DESCRIPTION_TEXT);
if (needsScroll) { if (needsScroll) {
GL11.glTranslated(0, height + 20, 0); GL11.glTranslated(0, height + 20, 0);
GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, 0xDDDDDD); GuiDraw.fontRenderer
.drawSplitString(description, x1, y1draw, x2 - x1, LocalizedColours.MOD_DESCRIPTION_TEXT);
} }
GL11.glPopMatrix(); GL11.glPopMatrix();
@@ -89,6 +89,22 @@ public class ReflectionManager {
} }
} }
/**
* Set a field of the object {@code instance} of type {@code class1} to {@code value}.
*
* @deprecated According to {@link Class#getDeclaredFields()}, the array of fields has no defined order, therefore
* selecting a field based on its index will result in JVM-specific behaviour. Use
* {@link #setField(Class, Object, String, Object)} instead.
*
* @param class1 The class which defines the field in question.
* @param instance The object being altered - must be an instance of {@code class1}
* @param fieldindex The index of the field being set.
* @param value The value to set the field to.
* @throws IllegalArgumentException If {@code instance} is not an instance of {@code class1}, or if {@code value}
* doesn't match the type of the field.
* @throws IllegalAccessException If unable to write to the field.
*/
@Deprecated
public static void setField(Class<?> class1, Object instance, int fieldindex, Object value) public static void setField(Class<?> class1, Object instance, int fieldindex, Object value)
throws IllegalArgumentException, IllegalAccessException { throws IllegalArgumentException, IllegalAccessException {
Field field = class1.getDeclaredFields()[fieldindex]; Field field = class1.getDeclaredFields()[fieldindex];
@@ -179,6 +195,21 @@ public class ReflectionManager {
return null; return null;
} }
/**
* Get a field of the object {@code instance} of type {@code class1}.
*
* @deprecated According to {@link Class#getDeclaredFields()}, the array of fields has no defined order, therefore
* selecting a field based on its index will result in JVM-specific behaviour. Use
* {@link #getField(Class, Class, Object, String)} instead.
*
* @param class1 The class which defines the field in question.
* @param fieldType The type of the field being read.
* @param instance The object being read from - must be an instance of {@code class1}
* @param fieldIndex The index of the field being read.
* @throws IllegalArgumentException If {@code instance} is not an instance of {@code class1}.
* @throws IllegalAccessException If unable to read from the field.
*/
@Deprecated
public static <T> T getField(Class<?> class1, Class<T> fieldType, Object instance, int fieldIndex) public static <T> T getField(Class<?> class1, Class<T> fieldType, Object instance, int fieldIndex)
throws IllegalArgumentException, IllegalAccessException { throws IllegalArgumentException, IllegalAccessException {
Field field = class1.getDeclaredFields()[fieldIndex]; Field field = class1.getDeclaredFields()[fieldIndex];
@@ -5,32 +5,19 @@ import java.lang.reflect.Field;
import com.google.common.collect.ImmutableBiMap; 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.AccessTransformer;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
// This AccessTransformer will make all minecraft classes public at runtime in MCP just as they are in modloader.
// You should ONLY use this when you are testing with a mod that relies on runtime publicity and doesn't include
// access transformers. Such mods are doing the wrong thing and should be fixed.
public class CodeChickenAccessTransformer extends AccessTransformer { public class CodeChickenAccessTransformer extends AccessTransformer {
private static boolean makeAllPublic; private static final Field f_classNameBiMap;
private static Field f_classNameBiMap; @SuppressWarnings("FieldMayBeFinal")
private static Object emptyMap = ImmutableBiMap.of(); private static Object emptyMap = ImmutableBiMap.of();
public CodeChickenAccessTransformer() throws IOException { static {
super();
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 { try {
f_classNameBiMap = FMLDeobfuscatingRemapper.class.getDeclaredField("classNameBiMap"); f_classNameBiMap = FMLDeobfuscatingRemapper.class.getDeclaredField("classNameBiMap");
f_classNameBiMap.setAccessible(true); f_classNameBiMap.setAccessible(true);
@@ -39,16 +26,18 @@ public class CodeChickenAccessTransformer extends AccessTransformer {
} }
} }
public CodeChickenAccessTransformer() throws IOException {}
@Override @Override
public byte[] transform(String name, String transformedName, byte[] bytes) { public byte[] transform(String name, String transformedName, byte[] bytes) {
boolean setPublic = makeAllPublic && name.startsWith("net.minecraft."); boolean setPublic = name.startsWith("net.minecraft.");
if (setPublic) setClassMap(name); if (setPublic) setClassMap(name);
bytes = super.transform(name, transformedName, bytes); bytes = super.transform(name, transformedName, bytes);
if (setPublic) restoreClassMap(); if (setPublic) restoreClassMap();
return bytes; return bytes;
} }
private void restoreClassMap() { private static void restoreClassMap() {
try { try {
f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, emptyMap); f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, emptyMap);
} catch (Exception e) { } catch (Exception e) {
@@ -56,7 +45,7 @@ public class CodeChickenAccessTransformer extends AccessTransformer {
} }
} }
private void setClassMap(String name) { private static void setClassMap(String name) {
try { try {
f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, ImmutableBiMap.of(name.replace('.', '/'), "")); f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, ImmutableBiMap.of(name.replace('.', '/'), ""));
} catch (Exception e) { } catch (Exception e) {
@@ -1,6 +1,5 @@
package codechicken.core.asm; package codechicken.core.asm;
import java.io.File;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -13,7 +12,6 @@ import codechicken.core.ClientUtils;
import codechicken.core.featurehack.LiquidTextures; import codechicken.core.featurehack.LiquidTextures;
import codechicken.core.internal.CCCEventHandler; import codechicken.core.internal.CCCEventHandler;
import codechicken.core.launch.CodeChickenCorePlugin; import codechicken.core.launch.CodeChickenCorePlugin;
import codechicken.lib.config.ConfigFile;
import cpw.mods.fml.common.DummyModContainer; import cpw.mods.fml.common.DummyModContainer;
import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.LoadController; import cpw.mods.fml.common.LoadController;
@@ -26,14 +24,6 @@ import cpw.mods.fml.common.versioning.VersionRange;
public class CodeChickenCoreModContainer extends DummyModContainer { 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() { public CodeChickenCoreModContainer() {
super(getModMetadata()); super(getModMetadata());
} }
@@ -75,6 +65,7 @@ public class CodeChickenCoreModContainer extends DummyModContainer {
public void init(FMLInitializationEvent event) { public void init(FMLInitializationEvent event) {
if (event.getSide().isClient()) { if (event.getSide().isClient()) {
ClientUtils.enhanceSupportersList("CodeChickenCore"); ClientUtils.enhanceSupportersList("CodeChickenCore");
ClientUtils.registerLocalizedColourReloadListener();
FMLCommonHandler.instance().bus().register(new CCCEventHandler()); FMLCommonHandler.instance().bus().register(new CCCEventHandler());
MinecraftForge.EVENT_BUS.register(new CCCEventHandler()); MinecraftForge.EVENT_BUS.register(new CCCEventHandler());
@@ -7,35 +7,50 @@ import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader; import net.minecraft.launchwrapper.Launch;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.MethodNode;
import codechicken.lib.asm.ASMHelper; import codechicken.lib.asm.ASMHelper;
import codechicken.lib.asm.ClassHeirachyManager;
import codechicken.lib.asm.ObfMapping; import codechicken.lib.asm.ObfMapping;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
public class DefaultImplementationTransformer implements IClassTransformer { public class DefaultImplementationTransformer implements IClassTransformer {
private static LaunchClassLoader cl = (LaunchClassLoader) ClassHeirachyManager.class.getClassLoader(); private static final HashMap<String, InterfaceImpl> impls = new HashMap<>();
private static ClassNode getClassNode(String name) { public static void registerDefaultImpl(String iname, String cname) {
try { if (impls.isEmpty()) {
return ASMHelper.createClassNode(cl.getClassBytes(name.replace('/', '.'))); String name = DefaultImplementationTransformer.class.getName();
} catch (IOException e) { FMLRelaunchLog.finer("Registering transformer %s", name);
throw new RuntimeException(e); Launch.classLoader.registerTransformer(name);
} }
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;
} }
static class InterfaceImpl { static class InterfaceImpl {
public final String iname; public final String iname;
public ArrayList<MethodNode> impls = new ArrayList<MethodNode>(); public ArrayList<MethodNode> impls = new ArrayList<>();
public InterfaceImpl(String iname, String cname) { public InterfaceImpl(String iname, String cname) {
this.iname = iname; this.iname = iname;
HashSet<String> names = new HashSet<String>(); HashSet<String> names = new HashSet<>();
ClassNode inode = getClassNode(iname); ClassNode inode = getClassNode(iname);
for (MethodNode method : inode.methods) names.add(method.name + method.desc); for (MethodNode method : inode.methods) names.add(method.name + method.desc);
@@ -47,7 +62,7 @@ public class DefaultImplementationTransformer implements IClassTransformer {
} }
public boolean patch(ClassNode cnode) { public boolean patch(ClassNode cnode) {
LinkedList<String> names = new LinkedList<String>(); LinkedList<String> names = new LinkedList<>();
for (MethodNode method : cnode.methods) { for (MethodNode method : cnode.methods) {
ObfMapping m = new ObfMapping(cnode.name, method.name, method.desc).toRuntime(); ObfMapping m = new ObfMapping(cnode.name, method.name, method.desc).toRuntime();
names.add(m.s_name + m.s_desc); names.add(m.s_name + m.s_desc);
@@ -69,25 +84,13 @@ public class DefaultImplementationTransformer implements IClassTransformer {
} }
return changed; return changed;
} }
}
private static HashMap<String, InterfaceImpl> impls = new HashMap<String, InterfaceImpl>(); private static ClassNode getClassNode(String name) {
try {
public static void registerDefaultImpl(String iname, String cname) { return ASMHelper.createClassNode(Launch.classLoader.getClassBytes(name.replace('/', '.')));
impls.put(iname.replace('.', '/'), new InterfaceImpl(iname, cname)); } catch (IOException e) {
} throw new RuntimeException(e);
}
@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;
} }
} }
@@ -1,7 +1,5 @@
package codechicken.core.asm; package codechicken.core.asm;
import static codechicken.core.launch.CodeChickenCorePlugin.logger;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -11,7 +9,9 @@ import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.Stack; import java.util.Stack;
import java.util.jar.Attributes;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.IClassTransformer;
@@ -21,14 +21,18 @@ import net.minecraft.launchwrapper.LaunchClassLoader;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import codechicken.core.launch.CodeChickenCorePlugin;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
public class DelegatedTransformer implements IClassTransformer { public class DelegatedTransformer implements IClassTransformer {
private static ArrayList<IClassTransformer> delegatedTransformers; private static final ArrayList<IClassTransformer> delegatedTransformers;
private static Method m_defineClass; private static final Method m_defineClass;
private static Field f_cachedClasses; private static final Field f_cachedClasses;
private static IClassTransformer[] transformers;
public DelegatedTransformer() { static {
delegatedTransformers = new ArrayList<IClassTransformer>(); delegatedTransformers = new ArrayList<>();
try { try {
m_defineClass = ClassLoader.class m_defineClass = ClassLoader.class
.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE); .getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
@@ -41,14 +45,75 @@ public class DelegatedTransformer implements IClassTransformer {
} }
@Override @Override
public byte[] transform(String name, String tname, byte[] bytes) { public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (bytes == null) return null; if (basicClass == null) return null;
for (IClassTransformer trans : delegatedTransformers) bytes = trans.transform(name, tname, bytes); for (IClassTransformer t : transformers) {
return bytes; basicClass = t.transform(name, transformedName, basicClass);
}
return basicClass;
}
public static void load() {
scanModsForDelegatedTransformers();
final int size = delegatedTransformers.size();
if (size == 0) {
CodeChickenCorePlugin.logger
.debug("No delegated transformer found, skipping registration of main DelegatedTransformer.");
return;
}
CodeChickenCorePlugin.logger
.debug("Found " + size + " delegated transformers, registering main DelegatedTransformer.");
transformers = delegatedTransformers.toArray(new IClassTransformer[0]);
String name = DelegatedTransformer.class.getName();
FMLRelaunchLog.finer("Registering transformer %s", name);
Launch.classLoader.registerTransformer(name);
}
private static void scanModsForDelegatedTransformers() {
File modsDir = new File(CodeChickenCorePlugin.minecraftDir, "mods");
if (modsDir.exists()) {
final File[] files = modsDir.listFiles();
if (files != null) {
for (File file : files) {
scanMod(file);
}
}
}
File versionModsDir = new File(
CodeChickenCorePlugin.minecraftDir,
"mods/" + CodeChickenCorePlugin.currentMcVersion);
if (versionModsDir.exists()) {
final File[] files = versionModsDir.listFiles();
if (files != null) {
for (File file : files) {
scanMod(file);
}
}
}
}
private static void scanMod(File file) {
if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) return;
try {
try (JarFile jar = new JarFile(file)) {
Manifest manifest = jar.getManifest();
if (manifest == null) return;
Attributes attr = manifest.getMainAttributes();
if (attr == null) return;
String transformer = attr.getValue("CCTransformer");
if (transformer != null) {
addTransformer(transformer, jar, file);
}
}
} catch (Exception e) {
CodeChickenCorePlugin.logger.error("CodeChickenCore: Failed to read jar file: " + file.getName(), e);
}
} }
public static void addTransformer(String transformer, JarFile jar, File jarFile) { public static void addTransformer(String transformer, JarFile jar, File jarFile) {
logger.debug("Adding CCTransformer: " + transformer); CodeChickenCorePlugin.logger.debug("Adding CCTransformer: " + transformer);
try { try {
byte[] bytes; byte[] bytes;
bytes = Launch.classLoader.getClassBytes(transformer); bytes = Launch.classLoader.getClassBytes(transformer);
@@ -107,7 +172,7 @@ public class DelegatedTransformer implements IClassTransformer {
byte[] depbytes = readFully(jar.getInputStream(entry)); byte[] depbytes = readFully(jar.getInputStream(entry));
defineDependancies(depbytes, jar, jarFile, depStack); defineDependancies(depbytes, jar, jarFile, depStack);
logger.debug("Defining dependancy: " + dependancy); CodeChickenCorePlugin.logger.debug("Defining dependancy: " + dependancy);
defineClass(dependancy.replace('/', '.'), depbytes); defineClass(dependancy.replace('/', '.'), depbytes);
} }
@@ -128,7 +193,6 @@ public class DelegatedTransformer implements IClassTransformer {
while ((r = stream.read()) != -1) { while ((r = stream.read()) != -1) {
bos.write(r); bos.write(r);
} }
return bos.toByteArray(); return bos.toByteArray();
} }
} }
@@ -24,7 +24,6 @@ import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import codechicken.lib.asm.ASMHelper; import codechicken.lib.asm.ASMHelper;
import codechicken.lib.asm.ASMInit;
import codechicken.lib.asm.CC_ClassWriter; import codechicken.lib.asm.CC_ClassWriter;
import codechicken.lib.asm.ObfMapping; import codechicken.lib.asm.ObfMapping;
import codechicken.obfuscator.IHeirachyEvaluator; import codechicken.obfuscator.IHeirachyEvaluator;
@@ -35,15 +34,11 @@ import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes, IHeirachyEvaluator { public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes, IHeirachyEvaluator {
static {
ASMInit.init();
}
public static class LoadPlugin implements IFMLLoadingPlugin { public static class LoadPlugin implements IFMLLoadingPlugin {
@Override @Override
public String[] getASMTransformerClass() { public String[] getASMTransformerClass() {
return new String[0]; return null;
} }
@Override @Override
@@ -118,26 +113,20 @@ public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes,
} }
public static void load() { public static void load() {
CodeChickenCoreModContainer.loadConfig(); run = new ObfuscationRun(
false,
ObfMapping.MCPRemapper.getConfFiles(),
ObfuscationRun.fillDefaults(new HashMap<String, String>()));
run.obf.setHeirachyEvaluator(instance);
run.setQuiet().parseMappings();
Collections.addAll(excludedPackages, run.config.get("excludedPackages").split(";"));
if (CodeChickenCoreModContainer.config.getTag("dev.deobfuscate") if (ObfMapping.obfuscated) {
.setComment("set to true to completely deobfuscate mcp names") ObfMapping.loadMCPRemapper();
.getBooleanValue(!ObfMapping.obfuscated)) { run.setSeargeConstants();
run = new ObfuscationRun( getTransformers().add(instance);
false, } else {
ObfMapping.MCPRemapper.getConfFiles(), getTransformers().add(0, instance); // insert transformer as first.
ObfuscationRun.fillDefaults(new HashMap<String, String>()));
run.obf.setHeirachyEvaluator(instance);
run.setQuiet().parseMappings();
Collections.addAll(excludedPackages, run.config.get("excludedPackages").split(";"));
if (ObfMapping.obfuscated) {
ObfMapping.loadMCPRemapper();
run.setSeargeConstants();
getTransformers().add(instance);
} else {
getTransformers().add(0, instance); // insert transformer as first.
}
} }
} }
@@ -5,14 +5,15 @@ import static codechicken.lib.asm.InsnComparator.findOnce;
import java.util.Map; import java.util.Map;
import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.Launch;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.MethodNode;
import codechicken.core.launch.CodeChickenCorePlugin;
import codechicken.lib.asm.ASMBlock; import codechicken.lib.asm.ASMBlock;
import codechicken.lib.asm.ASMInit;
import codechicken.lib.asm.ASMReader; import codechicken.lib.asm.ASMReader;
import codechicken.lib.asm.ModularASMTransformer; import codechicken.lib.asm.ModularASMTransformer;
import codechicken.lib.asm.ModularASMTransformer.MethodReplacer; import codechicken.lib.asm.ModularASMTransformer.MethodReplacer;
@@ -20,20 +21,29 @@ import codechicken.lib.asm.ModularASMTransformer.MethodTransformer;
import codechicken.lib.asm.ModularASMTransformer.MethodWriter; import codechicken.lib.asm.ModularASMTransformer.MethodWriter;
import codechicken.lib.asm.ObfMapping; import codechicken.lib.asm.ObfMapping;
import codechicken.lib.config.ConfigTag; import codechicken.lib.config.ConfigTag;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
public class TweakTransformer implements IClassTransformer, Opcodes { public class TweakTransformer implements IClassTransformer, Opcodes {
static { private static final ModularASMTransformer transformer = new ModularASMTransformer();
ASMInit.init(); private static final Map<String, ASMBlock> blocks = ASMReader
} .loadResource("/assets/codechickencore/asm/tweaks.asm");
private static ModularASMTransformer transformer = new ModularASMTransformer();
private static Map<String, ASMBlock> blocks = ASMReader.loadResource("/assets/codechickencore/asm/tweaks.asm");
public static ConfigTag tweaks; public static ConfigTag tweaks;
public static void load() { public static void load() {
CodeChickenCoreModContainer.loadConfig(); loadTransformersFromConfig();
tweaks = CodeChickenCoreModContainer.config.getTag("tweaks") if (transformer.isEmpty()) {
CodeChickenCorePlugin.logger
.debug("No tweaks parsed from config, skipping registration of TweakTransformer.");
return;
}
String name = TweakTransformer.class.getName();
FMLRelaunchLog.finer("Registering transformer %s", name);
Launch.classLoader.registerTransformer(name);
}
private static void loadTransformersFromConfig() {
tweaks = CodeChickenCorePlugin.config.getTag("tweaks")
.setComment("Various tweaks that can be applied to game mechanics.").useBraces(); .setComment("Various tweaks that can be applied to game mechanics.").useBraces();
tweaks.removeTag("persistantLava"); tweaks.removeTag("persistantLava");
@@ -103,7 +113,7 @@ public class TweakTransformer implements IClassTransformer, Opcodes {
} }
@Override @Override
public byte[] transform(String name, String tname, byte[] bytes) { public final byte[] transform(String name, String tname, byte[] bytes) {
return transformer.transform(name, bytes); return transformer.transform(name, bytes);
} }
} }
@@ -6,6 +6,8 @@ import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import codechicken.lib.colour.LocalizedColours;
public class GuiCCButton extends GuiWidget { public class GuiCCButton extends GuiWidget {
public String text; public String text;
@@ -73,7 +75,8 @@ public class GuiCCButton extends GuiWidget {
} }
public int getTextColour(int mousex, int mousey) { public int getTextColour(int mousex, int mousey) {
return !isEnabled ? 0xFFA0A0A0 : pointInside(mousex, mousey) ? 0xFFFFFFA0 : 0xFFE0E0E0; return !isEnabled ? LocalizedColours.BUTTON_TEXT_DISABLED
: pointInside(mousex, mousey) ? LocalizedColours.BUTTON_TEXT_HOVER : LocalizedColours.BUTTON_TEXT;
} }
public GuiCCButton setActionCommand(String string) { public GuiCCButton setActionCommand(String string) {
@@ -5,6 +5,8 @@ import net.minecraft.util.ChatAllowedCharacters;
import org.lwjgl.input.Keyboard; import org.lwjgl.input.Keyboard;
import codechicken.lib.colour.LocalizedColours;
public class GuiCCTextField extends GuiWidget { public class GuiCCTextField extends GuiWidget {
private String text; private String text;
@@ -120,8 +122,8 @@ public class GuiCCTextField extends GuiWidget {
} }
public void drawBackground() { public void drawBackground() {
drawRect(x - 1, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); drawRect(x - 1, y - 1, x + width + 1, y + height + 1, LocalizedColours.TEXT_FIELD_BORDER);
drawRect(x, y, x + width, y + height, 0xff000000); drawRect(x, y, x + width, y + height, LocalizedColours.TEXT_FIELD_BACKGROUND);
} }
public String getDrawText() { public String getDrawText() {
@@ -135,7 +137,7 @@ public class GuiCCTextField extends GuiWidget {
} }
public int getTextColour() { public int getTextColour() {
return isEnabled ? 0xe0e0e0 : 0x707070; return isEnabled ? LocalizedColours.TEXT_FIELD_TEXT : LocalizedColours.TEXT_FIELD_TEXT_DISABLED;
} }
public GuiCCTextField setMaxStringLength(int i) { public GuiCCTextField setMaxStringLength(int i) {
@@ -3,6 +3,7 @@ package codechicken.core.gui;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Rectangle; import java.awt.Rectangle;
import codechicken.lib.colour.LocalizedColours;
import codechicken.lib.math.MathHelper; import codechicken.lib.math.MathHelper;
import codechicken.lib.vec.Rectangle4i; import codechicken.lib.vec.Rectangle4i;
@@ -184,28 +185,29 @@ public abstract class GuiScrollPane extends GuiWidget {
} }
public void drawBackground(float frame) { public void drawBackground(float frame) {
drawRect(x, y, x + width, y + height, 0xff000000); drawRect(x, y, x + width, y + height, LocalizedColours.SCROLL_PANE_BACKGROUND);
} }
public abstract void drawContent(int mx, int my, float frame); public abstract void drawContent(int mx, int my, float frame);
public void drawOverlay(float frame) { public void drawOverlay(float frame) {
// outlines // outlines
drawRect(x, y - 1, x + width, y, 0xffa0a0a0); // top drawRect(x, y - 1, x + width, y, LocalizedColours.SCROLL_PANE_OVERLAY_TOP);
drawRect(x, y + height, x + width, y + height + 1, 0xffa0a0a0); // bottom drawRect(x, y + height, x + width, y + height + 1, LocalizedColours.SCROLL_PANE_OVERLAY_BOTTOM);
drawRect(x - 1, y - 1, x, y + height + 1, 0xffa0a0a0); // left drawRect(x - 1, y - 1, x, y + height + 1, LocalizedColours.SCROLL_PANE_OVERLAY_LEFT);
drawRect(x + width, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); // right drawRect(x + width, y - 1, x + width + 1, y + height + 1, LocalizedColours.SCROLL_PANE_OVERLAY_RIGHT);
} }
public void drawScrollbar(float frame) { public void drawScrollbar(float frame) {
Rectangle r = scrollbarBounds(); Rectangle r = scrollbarBounds();
drawRect(r.x, r.y, r.x + r.width, r.y + r.height, 0xFF8B8B8B); // corners drawRect(r.x, r.y, r.x + r.width, r.y + r.height, LocalizedColours.SCROLLBAR_CORNERS);
drawRect(r.x, r.y, r.x + r.width - 1, r.y + r.height - 1, 0xFFF0F0F0); // topleft up drawRect(r.x, r.y, r.x + r.width - 1, r.y + r.height - 1, LocalizedColours.SCROLLBAR_TOP_LEFT);
drawRect(r.x + 1, r.y + 1, r.x + r.width, r.y + r.height, 0xFF555555); // bottom right down drawRect(r.x + 1, r.y + 1, r.x + r.width, r.y + r.height, LocalizedColours.SCROLLBAR_BOTTOM_RIGHT);
drawRect(r.x + 1, r.y + 1, r.x + r.width - 1, r.y + r.height - 1, 0xFFC6C6C6); // scrollbar drawRect(r.x + 1, r.y + 1, r.x + r.width - 1, r.y + r.height - 1, LocalizedColours.SCROLLBAR_FILL);
int algn = scrollbarGuideAlignment(); int algn = scrollbarGuideAlignment();
if (algn != 0) drawRect(algn > 0 ? r.x + r.width : r.x - 1, y, r.x, y + height, 0xFF808080); // lineguide if (algn != 0)
drawRect(algn > 0 ? r.x + r.width : r.x - 1, y, r.x, y + height, LocalizedColours.SCROLLBAR_GUIDE);
} }
} }
@@ -9,9 +9,6 @@ import java.text.CharacterIterator;
import java.text.StringCharacterIterator; import java.text.StringCharacterIterator;
import java.util.List; import java.util.List;
import java.util.Map; 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.JEditorPane;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
@@ -21,48 +18,87 @@ import javax.swing.event.HyperlinkListener;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import codechicken.core.asm.CodeChickenCoreModContainer;
import codechicken.core.asm.DelegatedTransformer; import codechicken.core.asm.DelegatedTransformer;
import codechicken.core.asm.MCPDeobfuscationTransformer; import codechicken.core.asm.MCPDeobfuscationTransformer;
import codechicken.core.asm.Tags; import codechicken.core.asm.Tags;
import codechicken.core.asm.TweakTransformer; import codechicken.core.asm.TweakTransformer;
import codechicken.lib.config.ConfigFile;
import codechicken.lib.config.ConfigTag; import codechicken.lib.config.ConfigTag;
import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion; import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import cpw.mods.fml.common.versioning.VersionParser; import cpw.mods.fml.common.versioning.VersionParser;
import cpw.mods.fml.relauncher.CoreModManager; import cpw.mods.fml.relauncher.CoreModManager;
import cpw.mods.fml.relauncher.FMLInjectionData; import cpw.mods.fml.relauncher.FMLInjectionData;
import cpw.mods.fml.relauncher.IFMLCallHook;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin; import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.MCVersion;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
@TransformerExclusions(value = { "codechicken.core.asm", "codechicken.obfuscator" }) @IFMLLoadingPlugin.MCVersion("1.7.10")
@MCVersion("1.7.10") @IFMLLoadingPlugin.TransformerExclusions({ "codechicken.core.asm", "codechicken.obfuscator", "codechicken.lib.asm",
public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook { "codechicken.lib.config" })
public class CodeChickenCorePlugin implements IFMLLoadingPlugin {
public static final String mcVersion = "[1.7.10]"; public static final String mcVersion = "[1.7.10]";
@Deprecated @Deprecated
public static final String version = Tags.VERSION; public static final String version = Tags.VERSION;
public static ConfigFile config;
public static File minecraftDir; public static File minecraftDir;
public static String currentMcVersion; public static String currentMcVersion;
public static Logger logger = LogManager.getLogger("CodeChickenCore"); public static Logger logger = LogManager.getLogger("CodeChickenCore");
public CodeChickenCorePlugin() { public CodeChickenCorePlugin() {
if (minecraftDir != null) return; // get called twice, once for IFMLCallHook if (minecraftDir != null) return;
minecraftDir = (File) FMLInjectionData.data()[6]; minecraftDir = (File) FMLInjectionData.data()[6];
currentMcVersion = (String) FMLInjectionData.data()[4]; currentMcVersion = (String) FMLInjectionData.data()[4];
config = new ConfigFile(new File(minecraftDir, "config/CodeChickenCore.cfg"))
.setComment("CodeChickenCore configuration file.");
if (Boolean.getBoolean("ccc.dev.deobfuscate")) {
injectDeobfPlugin();
}
}
injectDeobfPlugin(); @Override
public String[] getASMTransformerClass() {
versionCheck(mcVersion, "CodeChickenCore");
return new String[] { "codechicken.lib.asm.ClassHeirachyManager", "codechicken.lib.asm.RedirectorTransformer" };
}
@Override
public String getAccessTransformerClass() {
if (Boolean.getBoolean("ccc.dev.runtimePublic")) {
return "codechicken.core.asm.CodeChickenAccessTransformer";
} else {
return null;
}
}
@Override
public String getModContainerClass() {
return "codechicken.core.asm.CodeChickenCoreModContainer";
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) {
ConfigTag checkRAM = CodeChickenCorePlugin.config.getTag("checks")
.setComment("Configuration options for checking various requirements for a modpack.").useBraces();
if (checkRAM.getTag("checkRAM")
.setComment("If set to true, check RAM available for Minecraft before continuing to load")
.getBooleanValue(false)) {
systemCheck(checkRAM);
}
TweakTransformer.load();
DelegatedTransformer.load();
} }
private void injectDeobfPlugin() { private void injectDeobfPlugin() {
try { try {
Class<?> wrapperClass = Class.forName("cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper"); Class<?> wrapperClass = Class.forName("cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper");
Constructor wrapperConstructor = wrapperClass Constructor<?> wrapperConstructor = wrapperClass
.getConstructor(String.class, IFMLLoadingPlugin.class, File.class, Integer.TYPE, String[].class); .getConstructor(String.class, IFMLLoadingPlugin.class, File.class, Integer.TYPE, String[].class);
Field f_loadPlugins = CoreModManager.class.getDeclaredField("loadPlugins"); Field f_loadPlugins = CoreModManager.class.getDeclaredField("loadPlugins");
wrapperConstructor.setAccessible(true); wrapperConstructor.setAccessible(true);
@@ -76,7 +112,7 @@ public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook {
0, 0,
new String[0])); new String[0]));
} catch (Exception e) { } catch (Exception e) {
logger.error("Failed to inject MCPDeobfuscation Transformer", e); logger.error("Failed to inject FMLPluginWrapper for MCPDeobfuscation Transformer", e);
} }
} }
@@ -186,74 +222,4 @@ public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook {
FMLCommonHandler.instance().exitJava(-98, true); FMLCommonHandler.instance().exitJava(-98, true);
} }
} }
@Override
public String[] getASMTransformerClass() {
versionCheck(mcVersion, "CodeChickenCore");
return new String[] { "codechicken.lib.asm.ClassHeirachyManager", "codechicken.core.asm.TweakTransformer",
"codechicken.core.asm.DelegatedTransformer", "codechicken.core.asm.DefaultImplementationTransformer",
"codechicken.lib.asm.RedirectorTransformer" };
}
@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<String, Object> data) {}
@Override
public Void call() {
CodeChickenCoreModContainer.loadConfig();
ConfigTag checkRAM;
checkRAM = CodeChickenCoreModContainer.config.getTag("checks")
.setComment("Configuration options for checking various requirements for a modpack.").useBraces();
if (checkRAM.getTag("checkRAM")
.setComment("If set to true, check RAM available for Minecraft before continuing to load")
.getBooleanValue(false))
systemCheck(checkRAM);
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 transformer = attr.getValue("CCTransformer");
if (transformer != null) DelegatedTransformer.addTransformer(transformer, jar, file);
} finally {
jar.close();
}
} catch (Exception e) {
logger.error("CodeChickenCore: Failed to read jar file: " + file.getName(), e);
}
}
} }
@@ -1,186 +0,0 @@
package codechicken.core.launch;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.Box;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import cpw.mods.fml.common.versioning.ComparableVersion;
import cpw.mods.fml.relauncher.IFMLCallHook;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.MCVersion;
/**
* For autodownloading stuff. This is really unoriginal, mostly ripped off FML, credits to cpw.
*/
@MCVersion("1.7.10")
public class DepLoader implements IFMLLoadingPlugin, IFMLCallHook {
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 {
boolean stopIt;
Thread pokeThread;
private Box makeProgressPanel() {
return null;
}
@Override
public JDialog makeDialog() {
return null;
}
protected void requestClose(String message) {}
@Override
public void updateProgressString(String progressUpdate, Object... data) {}
@Override
public void resetProgress(int sizeGuess) {}
@Override
public void updateProgress(int fullLength) {}
@Override
public void setPokeThread(Thread currentThread) {}
@Override
public boolean shouldStopIt() {
return true;
}
@Override
public void showErrorDialog(String name, String url) {}
}
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 VersionedFile {
public final Pattern pattern;
public final String filename;
public final ComparableVersion version;
public final String name;
public VersionedFile(String filename, Pattern pattern) {
this.pattern = pattern;
this.filename = filename;
Matcher m = pattern.matcher(filename);
if (m.matches()) {
name = m.group(1);
version = new ComparableVersion(m.group(2));
} else {
name = null;
version = null;
}
}
public boolean matches() {
return name != null;
}
}
public static class Dependency {
public String url;
public VersionedFile file;
public String existing;
/**
* Flag set to add this dep to the classpath immediately because it is required for a coremod.
*/
public boolean coreLib;
public Dependency(String url, VersionedFile file, boolean coreLib) {
this.url = url;
this.file = file;
this.coreLib = coreLib;
}
}
public static class DepLoadInst {
public DepLoadInst() {}
public void load() {}
}
public static void load() {}
@Override
public String[] getASMTransformerClass() {
return null;
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return getClass().getName();
}
@Override
public void injectData(Map<String, Object> data) {}
@Override
public Void call() {
load();
return null;
}
@Override
public String getAccessTransformerClass() {
return null;
}
}
@@ -1,14 +1,13 @@
package codechicken.lib.asm; package codechicken.lib.asm;
import java.util.ArrayList; import java.util.Map;
import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.HashSet; import java.util.function.Function;
import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.ClassReader;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
@@ -18,47 +17,60 @@ import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
*/ */
public class ClassHeirachyManager implements IClassTransformer { public class ClassHeirachyManager implements IClassTransformer {
private static final Map<String, ClassInfo> superclasses = new ConcurrentHashMap<>(1000);
static { static {
ASMInit.init(); superclasses.put("java.lang.Object", ClassInfo.OBJECT);
} }
public static class SuperCache { @Override
public byte[] transform(String name, String transformedName, byte[] bytes) {
String superclass; if (bytes == null) return null;
public HashSet<String> parents = new HashSet<String>(); if (!superclasses.containsKey(transformedName)) {
private boolean flattened; declareASM(transformedName, bytes);
public void add(String parent) {
parents.add(parent);
} }
return bytes;
}
public void flatten() { private static ClassInfo declareASM(String name, byte[] bytes) {
if (flattened) return; final ClassReader classReader = new ClassReader(bytes);
final ClassInfo classInfo = new ClassInfo(classReader);
superclasses.put(name, classInfo);
return classInfo;
}
for (String s : new ArrayList<String>(parents)) { private static ClassInfo declareReflection(String name) throws ClassNotFoundException {
SuperCache c = declareClass(s); final Class<?> aclass = Class.forName(name);
if (c != null) { final ClassInfo classInfo = new ClassInfo(aclass);
c.flatten(); superclasses.put(name, classInfo);
parents.addAll(c.parents); return classInfo;
} }
private static ClassInfo declareClass(String name) {
try {
byte[] bytes = Launch.classLoader.getClassBytes(unKey(name));
if (bytes != null) {
return declareASM(name, bytes);
} }
flattened = true; } catch (Exception ignored) {}
try {
return declareReflection(name);
} catch (ClassNotFoundException ignored) {}
return null;
}
private static String toKey(String name) {
if (ObfMapping.obfuscated) {
return FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.', '/')).replace('/', '.');
} }
return name.replace('/', '.');
} }
public static HashMap<String, SuperCache> superclasses = new HashMap<String, SuperCache>(); private static String unKey(String name) {
private static LaunchClassLoader cl = Launch.classLoader; if (ObfMapping.obfuscated) {
return FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/', '.');
public static String toKey(String name) { }
if (ObfMapping.obfuscated) return name.replace('/', '.');
name = FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.', '/')).replace('/', '.');
return name;
}
public static String unKey(String name) {
if (ObfMapping.obfuscated)
name = FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/', '.');
return name;
} }
/** /**
@@ -72,83 +84,99 @@ public class ClassHeirachyManager implements IClassTransformer {
if (name.equals(superclass)) return true; if (name.equals(superclass)) return true;
SuperCache cache = declareClass(name); ClassInfo classInfo = superclasses.get(name);
if (cache == null) // just can't handle this if (classInfo == null) classInfo = declareClass(name);
return false; if (classInfo == null) return false;
cache.flatten(); return classInfo.hasSuper(superclass);
return cache.parents.contains(superclass);
}
private static SuperCache declareClass(String name) {
name = toKey(name);
SuperCache cache = superclasses.get(name);
if (cache != null) return cache;
try {
byte[] bytes = cl.getClassBytes(unKey(name));
if (bytes != null) cache = declareASM(bytes);
} catch (Exception e) {}
if (cache != null) return cache;
try {
cache = declareReflection(name);
} catch (ClassNotFoundException e) {}
return cache;
}
private static SuperCache declareReflection(String name) throws ClassNotFoundException {
Class<?> aclass = Class.forName(name);
SuperCache cache = getOrCreateCache(name);
if (aclass.isInterface()) cache.superclass = "java.lang.Object";
else if (name.equals("java.lang.Object")) return cache;
else cache.superclass = toKey(aclass.getSuperclass().getName());
cache.add(cache.superclass);
for (Class<?> iclass : aclass.getInterfaces()) cache.add(toKey(iclass.getName()));
return cache;
}
private static SuperCache declareASM(byte[] bytes) {
ClassNode node = ASMHelper.createClassNode(bytes);
String name = toKey(node.name);
SuperCache cache = getOrCreateCache(name);
cache.superclass = toKey(node.superName.replace('/', '.'));
cache.add(cache.superclass);
for (String iclass : node.interfaces) cache.add(toKey(iclass.replace('/', '.')));
return cache;
}
@Override
public byte[] transform(String name, String tname, byte[] bytes) {
if (bytes == null) return null;
if (!superclasses.containsKey(tname)) declareASM(bytes);
return bytes;
}
public static SuperCache getOrCreateCache(String name) {
SuperCache cache = superclasses.get(name);
if (cache == null) superclasses.put(name, cache = new SuperCache());
return cache;
} }
public static String getSuperClass(String name, boolean runtime) { public static String getSuperClass(String name, boolean runtime) {
name = toKey(name); name = toKey(name);
SuperCache cache = declareClass(name);
if (cache == null) return "java.lang.Object";
cache.flatten(); ClassInfo classInfo = superclasses.get(name);
String s = cache.superclass; if (classInfo == null) classInfo = declareClass(name);
if (!runtime) s = FMLDeobfuscatingRemapper.INSTANCE.unmap(s); if (classInfo == null || classInfo.superclass == null) return "java.lang.Object";
final String s = classInfo.superclass;
if (!runtime) {
return FMLDeobfuscatingRemapper.INSTANCE.unmap(s);
}
return s; return s;
} }
private static class ClassInfo {
public static final ClassInfo OBJECT = new ClassInfo();
private static final Map<String, String> STRING_POOL = new ConcurrentHashMap<>(256);
public final String superclass;
public final String[] interfaces;
public ClassInfo parent;
private ClassInfo() {
this.superclass = null;
this.interfaces = null;
}
public ClassInfo(ClassReader classReader) {
final String superName = classReader.getSuperName();
final String[] interfaces = classReader.getInterfaces();
if (superName == null || "java/lang/Object".equals(superName)) {
superclass = "java.lang.Object";
parent = OBJECT;
} else {
superclass = pool(toKey(superName));
}
if (interfaces == null || interfaces.length == 0) {
this.interfaces = null;
} else {
this.interfaces = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
this.interfaces[i] = pool(toKey(interfaces[i]));
}
}
}
public ClassInfo(Class<?> aclass) {
if (aclass.isInterface()) {
this.superclass = "java.lang.Object";
this.parent = OBJECT;
} else {
final Class<?> superclass = aclass.getSuperclass();
if (superclass == null || "java.lang.Object".equals(superclass.getName())) {
this.superclass = "java.lang.Object";
this.parent = OBJECT;
} else {
this.superclass = pool(toKey(superclass.getName()));
}
}
final Class<?>[] interfaces = aclass.getInterfaces();
if (interfaces.length == 0) {
this.interfaces = null;
} else {
this.interfaces = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
this.interfaces[i] = pool(toKey(interfaces[i].getName()));
}
}
}
public boolean hasSuper(String superclass) {
if (this.superclass == null) return false;
if (this.superclass.equals(superclass)) return true;
ClassInfo parentInfo = this.parent;
if (parentInfo == null) parentInfo = superclasses.get(this.superclass);
if (parentInfo == null) parentInfo = declareClass(this.superclass);
if (parentInfo == null) return false;
this.parent = parentInfo;
return parentInfo.hasSuper(superclass);
}
private static String pool(String s) {
return STRING_POOL.computeIfAbsent(s, Function.identity());
}
}
} }
@@ -275,17 +275,22 @@ public class ModularASMTransformer {
} }
} }
public HashMap<String, ClassNodeTransformerList> transformers = new HashMap<String, ClassNodeTransformerList>(); public HashMap<String, ClassNodeTransformerList> transformers = new HashMap<>();
public void add(ClassNodeTransformer t) { public void add(ClassNodeTransformer t) {
ClassNodeTransformerList list = transformers.get(t.className()); ClassNodeTransformerList list = transformers.get(t.className());
if (list == null) transformers.put(t.className(), list = new ClassNodeTransformerList()); if (list == null) {
transformers.put(t.className(), list = new ClassNodeTransformerList());
}
list.add(t); list.add(t);
} }
public boolean isEmpty() {
return transformers.isEmpty();
}
public byte[] transform(String name, byte[] bytes) { public byte[] transform(String name, byte[] bytes) {
if (bytes == null) return null; if (bytes == null) return null;
ClassNodeTransformerList list = transformers.get(name); ClassNodeTransformerList list = transformers.get(name);
return list == null ? bytes : list.transform(bytes); return list == null ? bytes : list.transform(bytes);
} }
@@ -59,7 +59,12 @@ public class RedirectorTransformer implements IClassTransformer, Opcodes {
"hasBrightness", "hasBrightness",
"brightness", "brightness",
"side", "side",
"lc" "lc",
"normalAttrib",
"colourAttrib",
"lightCoordAttrib",
"sideAttrib",
"lightingAttrib"
); );
Collections.addAll(redirectedSimpleMethods, "reset", "pullLightmap", "pushLightmap", "setDynamic", "draw"); Collections.addAll(redirectedSimpleMethods, "reset", "pullLightmap", "pushLightmap", "setDynamic", "draw");
@@ -0,0 +1,86 @@
package codechicken.lib.colour;
import net.minecraft.util.StatCollector;
public final class LocalizedColours {
private static final String KEY_PREFIX = "codechickencore.color.";
public static int BUTTON_TEXT_DISABLED = 0xFFA0A0A0;
public static int BUTTON_TEXT_HOVER = 0xFFFFFFA0;
public static int BUTTON_TEXT = 0xFFE0E0E0;
public static int TEXT_FIELD_BORDER = 0xFFA0A0A0;
public static int TEXT_FIELD_BACKGROUND = 0xFF000000;
public static int TEXT_FIELD_TEXT = 0xE0E0E0;
public static int TEXT_FIELD_TEXT_DISABLED = 0x707070;
public static int SCROLL_PANE_BACKGROUND = 0xFF000000;
public static int SCROLL_PANE_OVERLAY_TOP = 0xFFA0A0A0;
public static int SCROLL_PANE_OVERLAY_BOTTOM = 0xFFA0A0A0;
public static int SCROLL_PANE_OVERLAY_LEFT = 0xFFA0A0A0;
public static int SCROLL_PANE_OVERLAY_RIGHT = 0xFFA0A0A0;
public static int SCROLLBAR_CORNERS = 0xFF8B8B8B;
public static int SCROLLBAR_TOP_LEFT = 0xFFF0F0F0;
public static int SCROLLBAR_BOTTOM_RIGHT = 0xFF555555;
public static int SCROLLBAR_FILL = 0xFFC6C6C6;
public static int SCROLLBAR_GUIDE = 0xFF808080;
public static int TOOLTIP_BG_START = 0xF0100010;
public static int TOOLTIP_BG_END = 0xF0100010;
public static int TOOLTIP_BORDER_START = 0x505000FF;
public static int TOOLTIP_BORDER_END = 0x5028007F;
public static int TOOLTIP_TEXT = 0xFFFFFFFF;
public static int ITEM_QUANTITY_TEXT = 0xFFFFFF;
public static int MOD_DESCRIPTION_TEXT = 0xDDDDDD;
private LocalizedColours() {}
public static void reloadLocalizedColours() {
BUTTON_TEXT_DISABLED = getLocalizedColor("buttonTextDisabled", BUTTON_TEXT_DISABLED);
BUTTON_TEXT_HOVER = getLocalizedColor("buttonTextHover", BUTTON_TEXT_HOVER);
BUTTON_TEXT = getLocalizedColor("buttonText", BUTTON_TEXT);
TEXT_FIELD_BORDER = getLocalizedColor("textFieldBorder", TEXT_FIELD_BORDER);
TEXT_FIELD_BACKGROUND = getLocalizedColor("textFieldBackground", TEXT_FIELD_BACKGROUND);
TEXT_FIELD_TEXT = getLocalizedColor("textFieldText", TEXT_FIELD_TEXT);
TEXT_FIELD_TEXT_DISABLED = getLocalizedColor("textFieldTextDisabled", TEXT_FIELD_TEXT_DISABLED);
SCROLL_PANE_BACKGROUND = getLocalizedColor("background", SCROLL_PANE_BACKGROUND);
SCROLL_PANE_OVERLAY_TOP = getLocalizedColor("overlayTop", SCROLL_PANE_OVERLAY_TOP);
SCROLL_PANE_OVERLAY_BOTTOM = getLocalizedColor("overlayBottom", SCROLL_PANE_OVERLAY_BOTTOM);
SCROLL_PANE_OVERLAY_LEFT = getLocalizedColor("overlayLeft", SCROLL_PANE_OVERLAY_LEFT);
SCROLL_PANE_OVERLAY_RIGHT = getLocalizedColor("overlayRight", SCROLL_PANE_OVERLAY_RIGHT);
SCROLLBAR_CORNERS = getLocalizedColor("scrollbarCorners", SCROLLBAR_CORNERS);
SCROLLBAR_TOP_LEFT = getLocalizedColor("scrollbarTopLeft", SCROLLBAR_TOP_LEFT);
SCROLLBAR_BOTTOM_RIGHT = getLocalizedColor("scrollbarBottomRight", SCROLLBAR_BOTTOM_RIGHT);
SCROLLBAR_FILL = getLocalizedColor("scrollbarFill", SCROLLBAR_FILL);
SCROLLBAR_GUIDE = getLocalizedColor("scrollbarGuide", SCROLLBAR_GUIDE);
TOOLTIP_BG_START = getLocalizedColor("tooltipBgStart", TOOLTIP_BG_START);
TOOLTIP_BG_END = getLocalizedColor("tooltipBgEnd", TOOLTIP_BG_END);
TOOLTIP_BORDER_START = getLocalizedColor("tooltipBorderStart", TOOLTIP_BORDER_START);
TOOLTIP_BORDER_END = getLocalizedColor("tooltipBorderEnd", TOOLTIP_BORDER_END);
TOOLTIP_TEXT = getLocalizedColor("tooltipText", TOOLTIP_TEXT);
ITEM_QUANTITY_TEXT = getLocalizedColor("itemQuantityText", ITEM_QUANTITY_TEXT);
MOD_DESCRIPTION_TEXT = getLocalizedColor("modDescriptionText", MOD_DESCRIPTION_TEXT);
}
/**
* Resolve a optional localized ARGB color from lang files. Expected value format: AARRGGBB (for example FF555555).
*/
private static int getLocalizedColor(String key, int defaultColor) {
String fullKey = KEY_PREFIX + key;
if (!StatCollector.canTranslate(fullKey)) {
return defaultColor;
}
String raw = StatCollector.translateToLocal(fullKey);
String hex = raw.trim();
if (hex.startsWith("0x") || hex.startsWith("0X")) {
hex = hex.substring(2);
} else if (hex.startsWith("#")) {
hex = hex.substring(1);
}
try {
return (int) Long.parseLong(hex, 16);
} catch (NumberFormatException ignored) {
return defaultColor;
}
}
}
+20 -3
View File
@@ -18,6 +18,7 @@ import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL12;
import codechicken.lib.colour.LocalizedColours;
import codechicken.lib.math.MathHelper; import codechicken.lib.math.MathHelper;
import codechicken.lib.render.CCRenderState; import codechicken.lib.render.CCRenderState;
@@ -162,7 +163,15 @@ public class GuiDraw {
} }
public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list) { public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list) {
drawMultilineTip(font, x, y, list, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F); drawMultilineTip(
font,
x,
y,
list,
LocalizedColours.TOOLTIP_BG_START,
LocalizedColours.TOOLTIP_BG_END,
LocalizedColours.TOOLTIP_BORDER_START,
LocalizedColours.TOOLTIP_BORDER_END);
} }
public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list, int bgStart, int bgEnd, public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list, int bgStart, int bgEnd,
@@ -201,7 +210,7 @@ public class GuiDraw {
line.draw(x, y); line.draw(x, y);
y += line.getSize().height; y += line.getSize().height;
} else { } else {
font.drawStringWithShadow(s, x, y, -1); font.drawStringWithShadow(s, x, y, LocalizedColours.TOOLTIP_TEXT);
y += s.endsWith(TOOLTIP_LINESPACE) ? 12 : 10; y += s.endsWith(TOOLTIP_LINESPACE) ? 12 : 10;
} }
} }
@@ -215,7 +224,15 @@ public class GuiDraw {
} }
public static void drawTooltipBox(int x, int y, int w, int h) { public static void drawTooltipBox(int x, int y, int w, int h) {
drawTooltipBox(x, y, w, h, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F); drawTooltipBox(
x,
y,
w,
h,
LocalizedColours.TOOLTIP_BG_START,
LocalizedColours.TOOLTIP_BG_END,
LocalizedColours.TOOLTIP_BORDER_START,
LocalizedColours.TOOLTIP_BORDER_END);
} }
public static void drawTooltipBox(int x, int y, int w, int h, int bgStart, int bgEnd, int borderStart, public static void drawTooltipBox(int x, int y, int w, int h, int bgStart, int bgEnd, int borderStart,
@@ -81,8 +81,8 @@ public class LightModel implements CCRenderState.IVertexOperation {
public boolean load(CCRenderState state) { public boolean load(CCRenderState state) {
if (!state.computeLighting) return false; if (!state.computeLighting) return false;
state.pipeline.addDependency(CCRenderState.normalAttrib); state.pipeline.addDependency(CCRenderState.normalAttrib());
state.pipeline.addDependency(CCRenderState.colourAttrib); state.pipeline.addDependency(CCRenderState.colourAttrib());
return true; return true;
} }
@@ -40,7 +40,7 @@ public class PlanarLightMatrix extends PlanarLightModel {
@Override @Override
public boolean load(CCRenderState state) { public boolean load(CCRenderState state) {
state.pipeline.addDependency(CCRenderState.sideAttrib); state.pipeline.addDependency(CCRenderState.sideAttrib());
return true; return true;
} }
@@ -20,8 +20,8 @@ public class PlanarLightModel implements CCRenderState.IVertexOperation {
public boolean load(CCRenderState state) { public boolean load(CCRenderState state) {
if (!state.computeLighting) return false; if (!state.computeLighting) return false;
state.pipeline.addDependency(CCRenderState.sideAttrib); state.pipeline.addDependency(CCRenderState.sideAttrib());
state.pipeline.addDependency(CCRenderState.colourAttrib); state.pipeline.addDependency(CCRenderState.colourAttrib());
return true; return true;
} }
@@ -12,7 +12,12 @@ import codechicken.lib.vec.BlockCoord;
public class SimpleBrightnessModel implements CCRenderState.IVertexOperation { public class SimpleBrightnessModel implements CCRenderState.IVertexOperation {
public static final int operationIndex = CCRenderState.registerOperation(); public static final int operationIndex = CCRenderState.registerOperation();
public static SimpleBrightnessModel instance = new SimpleBrightnessModel(); private static final ThreadLocal<SimpleBrightnessModel> instances = ThreadLocal
.withInitial(SimpleBrightnessModel::new);
public static SimpleBrightnessModel instance() {
return instances.get();
}
public IBlockAccess access; public IBlockAccess access;
public BlockCoord pos = new BlockCoord(); public BlockCoord pos = new BlockCoord();
@@ -40,7 +45,7 @@ public class SimpleBrightnessModel implements CCRenderState.IVertexOperation {
@Override @Override
public boolean load(CCRenderState state) { public boolean load(CCRenderState state) {
state.pipeline.addDependency(CCRenderState.sideAttrib); state.pipeline.addDependency(CCRenderState.sideAttrib());
return true; return true;
} }
@@ -2,6 +2,7 @@ package codechicken.lib.packet;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.Inflater; import java.util.zip.Inflater;
@@ -50,6 +51,8 @@ import io.netty.util.AttributeKey;
public final class PacketCustom implements MCDataInput, MCDataOutput { public final class PacketCustom implements MCDataInput, MCDataOutput {
private static final int MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024;
public static interface ICustomPacketHandler { public static interface ICustomPacketHandler {
} }
@@ -190,7 +193,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
byteBuf = payload; byteBuf = payload;
type = byteBuf.readUnsignedByte(); type = byteBuf.readUnsignedByte();
if (type > 0x80) decompress(); if (type > 0x80) decompressPayload();
type &= 0x7F; type &= 0x7F;
} }
@@ -204,44 +207,61 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
byteBuf.writeByte(type); byteBuf.writeByte(type);
} }
/** private void decompressPayload() {
* Decompresses the remaining ByteBuf (after type has been read) using Snappy if (byteBuf.readableBytes() < 4) throw new IllegalArgumentException("Missing compressed packet length");
*/ int declaredLength = byteBuf.readInt();
private void decompress() { if (declaredLength < 0 || declaredLength > MAX_DECOMPRESSED_SIZE)
throw new IllegalArgumentException("Invalid decompressed packet length: " + declaredLength);
Inflater inflater = new Inflater(); Inflater inflater = new Inflater();
ByteBuf decodedPayload = null;
try { try {
int len = byteBuf.readInt(); byte[] compressedInput = new byte[byteBuf.readableBytes()];
ByteBuf out = Unpooled.buffer(len); byteBuf.readBytes(compressedInput);
inflater.setInput(byteBuf.array(), byteBuf.readerIndex(), byteBuf.readableBytes()); inflater.setInput(compressedInput);
inflater.inflate(out.array()); decodedPayload = Unpooled.buffer(Math.min(declaredLength, 8192), declaredLength);
out.writerIndex(len); byte[] inflatedChunk = new byte[Math.min(declaredLength + 1, 8192)];
byteBuf = out; while (!inflater.finished()) {
} catch (Exception e) { int inflatedBytes = inflater.inflate(inflatedChunk);
throw new RuntimeException(e); if (inflatedBytes > declaredLength - decodedPayload.writerIndex())
throw new IllegalArgumentException("Decompressed packet exceeds declared length");
if (inflatedBytes == 0 && !inflater.finished())
throw new IllegalArgumentException("Incomplete or dictionary-dependent compressed packet");
decodedPayload.writeBytes(inflatedChunk, 0, inflatedBytes);
}
if (decodedPayload.writerIndex() != declaredLength || inflater.getRemaining() != 0)
throw new IllegalArgumentException(
"Compressed packet does not match declared length or has trailing data");
byteBuf = decodedPayload;
decodedPayload = null;
} catch (DataFormatException e) {
throw new IllegalArgumentException("Invalid compressed packet", e);
} finally { } finally {
if (decodedPayload != null) decodedPayload.release();
inflater.end(); inflater.end();
} }
} }
/** private void compressPayload() {
* Compresses the payload ByteBuf after the type byte if ((byteBuf.getUnsignedByte(0) & 0x80) != 0) return;
*/
private void do_compress() { int payloadLength = byteBuf.writerIndex() - 1;
if (payloadLength > MAX_DECOMPRESSED_SIZE)
throw new IllegalArgumentException("Packet payload exceeds 2 MiB decompressed limit: " + payloadLength);
Deflater deflater = new Deflater(); Deflater deflater = new Deflater();
try { try {
byteBuf.readerIndex(1); byteBuf.readerIndex(1);
int len = byteBuf.readableBytes(); deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), payloadLength);
deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), len);
deflater.finish(); deflater.finish();
ByteBuf out = Unpooled.buffer(len + 5); ByteBuf compressedPayload = Unpooled.buffer(payloadLength + 5);
int clen = deflater.deflate(out.array(), 5, len); int compressedLength = deflater.deflate(compressedPayload.array(), 5, payloadLength);
if (clen >= len - 5 || !deflater.finished()) // not worth compressing, gets larger if (compressedLength >= payloadLength - 5 || !deflater.finished()) return;
return;
out.setByte(0, type | 0x80); compressedPayload.setByte(0, type | 0x80);
out.setInt(1, len); compressedPayload.setInt(1, payloadLength);
out.writerIndex(clen + 5); compressedPayload.writerIndex(compressedLength + 5);
byteBuf = out; byteBuf = compressedPayload;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {
@@ -466,7 +486,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
public FluidStack readFluidStack() { public FluidStack readFluidStack() {
Fluid fluid = FluidRegistry.getFluid(readShort()); Fluid fluid = FluidRegistry.getFluid(readShort());
if (fluid == null) fluid = FluidRegistry.WATER; if (fluid == null) return null;
return new FluidStack(fluid, readVarInt(), readNBTTagCompound()); return new FluidStack(fluid, readVarInt(), readNBTTagCompound());
} }
@@ -474,7 +494,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
public FMLProxyPacket toPacket() { public FMLProxyPacket toPacket() {
if (incoming()) throw new IllegalStateException("Tried to write an incoming packet"); if (incoming()) throw new IllegalStateException("Tried to write an incoming packet");
if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) do_compress(); if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) compressPayload();
// FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf // FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf
return new FMLProxyPacket(byteBuf.copy(), channel); return new FMLProxyPacket(byteBuf.copy(), channel);
@@ -20,12 +20,12 @@ public class BlockRenderer {
@Override @Override
public <T> T getAttributes(CCRenderState.VertexAttribute<T> attr) { public <T> T getAttributes(CCRenderState.VertexAttribute<T> attr) {
return attr == CCRenderState.lightCoordAttrib && lcComputed ? (T) lightCoords : null; return attr == CCRenderState.lightCoordAttrib() && lcComputed ? (T) lightCoords : null;
} }
@Override @Override
public boolean hasAttribute(CCRenderState.VertexAttribute<?> attr) { public boolean hasAttribute(CCRenderState.VertexAttribute<?> attr) {
return attr == CCRenderState.sideAttrib || attr == CCRenderState.lightCoordAttrib && lcComputed; return attr == CCRenderState.sideAttrib() || attr == CCRenderState.lightCoordAttrib() && lcComputed;
} }
@Override @Override
@@ -136,12 +136,12 @@ public class BlockRenderer {
@Override @Override
public <T> T getAttributes(VertexAttribute<T> attr) { public <T> T getAttributes(VertexAttribute<T> attr) {
return attr == CCRenderState.lightCoordAttrib ? (T) lightCoords : null; return attr == CCRenderState.lightCoordAttrib() ? (T) lightCoords : null;
} }
@Override @Override
public boolean hasAttribute(VertexAttribute<?> attr) { public boolean hasAttribute(VertexAttribute<?> attr) {
return attr == CCRenderState.sideAttrib || attr == CCRenderState.lightCoordAttrib; return attr == CCRenderState.sideAttrib() || attr == CCRenderState.lightCoordAttrib();
} }
@Override @Override
@@ -164,7 +164,7 @@ public class BlockRenderer {
/** /**
* Renders faces of a block-like model based on a sideMask. Eg for side 2, verts 8-11 will be rendered * Renders faces of a block-like model based on a sideMask. Eg for side 2, verts 8-11 will be rendered
* *
* @param sideMask A mask of faces not to render * @param sideMask A mask of faces not to render
*/ */
// public static void renderFaces(int sideMask) { // public static void renderFaces(int sideMask) {
@@ -184,7 +184,7 @@ public class BlockRenderer {
/** /**
* Renders faces of a cuboid with texture coordinates mapped to match a standard minecraft block * Renders faces of a cuboid with texture coordinates mapped to match a standard minecraft block
* *
* @param bounds The bounding cuboid to render * @param bounds The bounding cuboid to render
* @param sideMask A mask of faces not to render * @param sideMask A mask of faces not to render
*/ */
@@ -68,7 +68,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
} }
public Vector3[] normals() { public Vector3[] normals() {
return getAttributes(CCRenderState.normalAttrib); return getAttributes(CCRenderState.normalAttrib());
} }
@Override @Override
@@ -102,7 +102,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Each pixel corresponds to one unit of position when generating the model * Each pixel corresponds to one unit of position when generating the model
* *
* @param i Vertex index to start generating at * @param i Vertex index to start generating at
* @param x1 The minX bound of the box * @param x1 The minX bound of the box
* @param y1 The minY bound of the box * @param y1 The minY bound of the box
@@ -195,7 +195,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Generates a box, uv mapped to be the same as a minecraft block with the same bounds * Generates a box, uv mapped to be the same as a minecraft block with the same bounds
* *
* @param i The vertex index to start generating at * @param i The vertex index to start generating at
* @param bounds The bounds of the block, 0 to 1 * @param bounds The bounds of the block, 0 to 1
* @return The generated model. When rendering an icon will need to be supplied for the UV transformation. * @return The generated model. When rendering an icon will need to be supplied for the UV transformation.
@@ -222,7 +222,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Generates a box, uv mapped to be the same as a minecraft block with the same bounds * Generates a box, uv mapped to be the same as a minecraft block with the same bounds
* *
* @param i The vertex index to start generating at * @param i The vertex index to start generating at
* @param x1 minX * @param x1 minX
* @param y1 minY * @param y1 minY
@@ -311,7 +311,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Computes the normals of all faces in the model. Uses the cross product of the vectors along 2 sides of the face * Computes the normals of all faces in the model. Uses the cross product of the vectors along 2 sides of the face
* *
* @param start The first vertex to generate normals for * @param start The first vertex to generate normals for
* @param length The number of vertices to generate normals for. Note this must be a multiple of 3 for triangles or * @param length The number of vertices to generate normals for. Note this must be a multiple of 3 for triangles or
* 4 for quads * 4 for quads
@@ -321,7 +321,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
if (length % vp != 0 || start % vp != 0) if (length % vp != 0 || start % vp != 0)
throw new IllegalArgumentException("Cannot generate normals across polygons"); throw new IllegalArgumentException("Cannot generate normals across polygons");
Vector3[] normals = getOrAllocate(CCRenderState.normalAttrib); Vector3[] normals = getOrAllocate(CCRenderState.normalAttrib());
for (int k = 0; k < length; k += vp) { for (int k = 0; k < length; k += vp) {
int i = k + start; int i = k + start;
Vector3 diff1 = verts[i + 1].vec.copy().subtract(verts[i].vec); Vector3 diff1 = verts[i + 1].vec.copy().subtract(verts[i].vec);
@@ -336,14 +336,14 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Computes lighting using the normals add a light model If the model is rotated, the lighting will no longer be * Computes lighting using the normals add a light model If the model is rotated, the lighting will no longer be
* valid * valid
* *
* @return The model * @return The model
*/ */
public CCModel computeLighting(LightModel light) { public CCModel computeLighting(LightModel light) {
Vector3[] normals = normals(); Vector3[] normals = normals();
int[] colours = getAttributes(CCRenderState.lightingAttrib); int[] colours = getAttributes(CCRenderState.lightingAttrib());
if (colours == null) { if (colours == null) {
colours = getOrAllocate(CCRenderState.lightingAttrib); colours = getOrAllocate(CCRenderState.lightingAttrib());
Arrays.fill(colours, -1); Arrays.fill(colours, -1);
} }
for (int k = 0; k < verts.length; k++) colours[k] = light.apply(colours[k], normals[k]); for (int k = 0; k < verts.length; k++) colours[k] = light.apply(colours[k], normals[k]);
@@ -351,18 +351,18 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
} }
public CCModel setColour(int c) { public CCModel setColour(int c) {
int[] colours = getOrAllocate(CCRenderState.colourAttrib); int[] colours = getOrAllocate(CCRenderState.colourAttrib());
Arrays.fill(colours, c); Arrays.fill(colours, c);
return this; return this;
} }
/** /**
* Computes the minecraft lighting coordinates for use with a LightMatrix * Computes the minecraft lighting coordinates for use with a LightMatrix
* *
* @return The model * @return The model
*/ */
public CCModel computeLightCoords() { public CCModel computeLightCoords() {
LC[] lcs = getOrAllocate(CCRenderState.lightCoordAttrib); LC[] lcs = getOrAllocate(CCRenderState.lightCoordAttrib());
Vector3[] normals = normals(); Vector3[] normals = normals();
for (int i = 0; i < verts.length; i++) lcs[i] = new LC().compute(verts[i].vec, normals[i]); for (int i = 0; i < verts.length; i++) lcs[i] = new LC().compute(verts[i].vec, normals[i]);
return this; return this;
@@ -370,7 +370,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Averages all normals at the same position to produce a smooth lighting effect. * Averages all normals at the same position to produce a smooth lighting effect.
* *
* @return The model * @return The model
*/ */
public CCModel smoothNormals() { public CCModel smoothNormals() {
@@ -461,7 +461,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Renders vertices start through start+length-1 of the model * Renders vertices start through start+length-1 of the model
* *
* @param start The first vertex index to render * @param start The first vertex index to render
* @param end The vertex index to render until * @param end The vertex index to render until
* @param ops Operations to apply * @param ops Operations to apply
@@ -520,7 +520,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Parses vertices, texture coords, normals and polygons from a WaveFront Obj file * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file
* *
* @param input An input stream to a obj file * @param input An input stream to a obj file
* @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS) * @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS)
* @param coordSystem The cooridnate system transformation to apply * @param coordSystem The cooridnate system transformation to apply
@@ -624,7 +624,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Parses vertices, texture coords, normals and polygons from a WaveFront Obj file * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file
* *
* @param res The resource for the obj file * @param res The resource for the obj file
* @return A map of group names to models * @return A map of group names to models
*/ */
@@ -634,7 +634,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Parses vertices, texture coords, normals and polygons from a WaveFront Obj file * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file
* *
* @param res The resource for the obj file * @param res The resource for the obj file
* @param coordSystem The cooridnate system transformation to apply * @param coordSystem The cooridnate system transformation to apply
* @return A map of group names to models * @return A map of group names to models
@@ -652,7 +652,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Parses vertices, texture coords, normals and polygons from a WaveFront Obj file * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file
* *
* @param res The resource for the obj file * @param res The resource for the obj file
* @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS) * @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS)
* @param coordSystem The cooridnate system transformation to apply * @param coordSystem The cooridnate system transformation to apply
@@ -678,7 +678,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
boolean hasNormals = polys.get(0)[2] > 0; boolean hasNormals = polys.get(0)[2] > 0;
CCModel model = CCModel.newModel(vertexMode, polys.size()); CCModel model = CCModel.newModel(vertexMode, polys.size());
if (hasNormals) model.getOrAllocate(CCRenderState.normalAttrib); if (hasNormals) model.getOrAllocate(CCRenderState.normalAttrib());
for (int i = 0; i < polys.size(); i++) { for (int i = 0; i < polys.size(); i++) {
int[] ai = polys.get(i); int[] ai = polys.get(i);
@@ -807,7 +807,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Generate models rotated to the other 5 sides of the block * Generate models rotated to the other 5 sides of the block
* *
* @param models An array of 6 models * @param models An array of 6 models
* @param side The side of this model * @param side The side of this model
* @param point The rotation point * @param point The rotation point
@@ -822,7 +822,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Generate models rotated to the other 3 horizontal of the block * Generate models rotated to the other 3 horizontal of the block
* *
* @param models An array of 4 models * @param models An array of 4 models
* @param side The side of this model * @param side The side of this model
* @param point The rotation point * @param point The rotation point
@@ -841,7 +841,7 @@ public class CCModel implements CCRenderState.IVertexSource, Copyable<CCModel> {
/** /**
* Generates copies of faces with clockwise vertices * Generates copies of faces with clockwise vertices
* *
* @return The model * @return The model
*/ */
public static CCModel generateBackface(CCModel src, int srcpos, CCModel dst, int destpos, int length) { public static CCModel generateBackface(CCModel src, int srcpos, CCModel dst, int destpos, int length) {
@@ -1,6 +1,7 @@
package codechicken.lib.render; package codechicken.lib.render;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.OpenGlHelper;
@@ -38,9 +39,9 @@ public class CCRenderState {
return instances.get(); return instances.get();
} }
private static int nextOperationIndex; private static volatile int nextOperationIndex;
public static int registerOperation() { public static synchronized int registerOperation() {
return nextOperationIndex++; return nextOperationIndex++;
} }
@@ -87,15 +88,52 @@ public class CCRenderState {
int operationID(); int operationID();
} }
private static ArrayList<VertexAttribute<?>> vertexAttributes = new ArrayList<>(); public static final class AttributeKey<T> {
private static int registerVertexAttribute(VertexAttribute<?> attr) { private static final HashMap<String, AttributeKey<?>> nameMap = new HashMap<>();
vertexAttributes.add(attr); private static final ArrayList<AttributeKey<?>> attributeKeys = new ArrayList<>();
return vertexAttributes.size() - 1; private static int nextLegacyId;
public final String name;
public final int attributeKeyIndex;
public final int operationIndex;
private VertexAttribute<T> canonical;
private AttributeKey(String name) {
this.name = name;
this.attributeKeyIndex = attributeKeys.size();
this.operationIndex = registerOperation();
attributeKeys.add(this);
nameMap.put(name, this);
}
public static synchronized <T> AttributeKey<T> create(String name) {
if (nameMap.containsKey(name))
throw new IllegalArgumentException("Duplicate registration of attribute with name: " + name);
return new AttributeKey<>(name);
}
static synchronized <T> AttributeKey<T> createLegacy() {
return new AttributeKey<>("legacy#" + nextLegacyId++);
}
synchronized void setCanonical(VertexAttribute<T> attr) {
if (canonical == null) canonical = attr;
}
static synchronized AttributeKey<?> get(int index) {
return attributeKeys.get(index);
}
} }
private static final AttributeKey<Vector3[]> NORMAL_KEY = AttributeKey.create("normal");
private static final AttributeKey<int[]> COLOUR_KEY = AttributeKey.create("colour");
private static final AttributeKey<int[]> LIGHTING_KEY = AttributeKey.create("lighting");
private static final AttributeKey<int[]> SIDE_KEY = AttributeKey.create("side");
private static final AttributeKey<LC[]> LC_KEY = AttributeKey.create("lc");
public static VertexAttribute<?> getAttribute(int index) { public static VertexAttribute<?> getAttribute(int index) {
return vertexAttributes.get(index); return AttributeKey.get(index).canonical;
} }
/** /**
@@ -106,14 +144,26 @@ public class CCRenderState {
*/ */
public abstract static class VertexAttribute<T> implements IVertexOperation { public abstract static class VertexAttribute<T> implements IVertexOperation {
public final int attributeIndex = registerVertexAttribute(this); public final int attributeIndex;
private final int operationIndex = registerOperation(); private final int operationIndex;
/** /**
* Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when * Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when
* constructing the pipeline * constructing the pipeline
*/ */
public boolean active = false; public boolean active = false;
protected VertexAttribute(AttributeKey<T> key) {
attributeIndex = key.attributeKeyIndex;
operationIndex = key.operationIndex;
key.setCanonical(this);
}
/** @deprecated keyless attributes get a per-construction index that is threadsafe */
@Deprecated
protected VertexAttribute() {
this(AttributeKey.createLegacy());
}
/** /**
* Construct a new array for storage of vertex attrutes in a model * Construct a new array for storage of vertex attrutes in a model
*/ */
@@ -169,7 +219,27 @@ public class CCRenderState {
} }
} }
public static VertexAttribute<Vector3[]> normalAttrib = new VertexAttribute<>() { public static VertexAttribute<Vector3[]> normalAttrib() {
return instances.get().normalAttrib;
}
public static VertexAttribute<int[]> colourAttrib() {
return instances.get().colourAttrib;
}
public static VertexAttribute<LC[]> lightCoordAttrib() {
return instances.get().lightCoordAttrib;
}
public static VertexAttribute<int[]> sideAttrib() {
return instances.get().sideAttrib;
}
public static VertexAttribute<int[]> lightingAttrib() {
return instances.get().lightingAttrib;
}
public VertexAttribute<Vector3[]> normalAttrib = new VertexAttribute<>(NORMAL_KEY) {
private Vector3[] normalRef; private Vector3[] normalRef;
@@ -197,7 +267,7 @@ public class CCRenderState {
else state.setNormalInstance(Rotation.axes[state.side]); else state.setNormalInstance(Rotation.axes[state.side]);
} }
}; };
public static VertexAttribute<int[]> colourAttrib = new VertexAttribute<>() { public VertexAttribute<int[]> colourAttrib = new VertexAttribute<>(COLOUR_KEY) {
private int[] colourRef; private int[] colourRef;
@@ -219,7 +289,7 @@ public class CCRenderState {
else state.setColourInstance(state.baseColour); else state.setColourInstance(state.baseColour);
} }
}; };
public static VertexAttribute<int[]> lightingAttrib = new VertexAttribute<>() { public VertexAttribute<int[]> lightingAttrib = new VertexAttribute<>(LIGHTING_KEY) {
private int[] colourRef; private int[] colourRef;
@@ -245,7 +315,7 @@ public class CCRenderState {
state.setColourInstance(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex])); state.setColourInstance(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex]));
} }
}; };
public static VertexAttribute<int[]> sideAttrib = new VertexAttribute<>() { public VertexAttribute<int[]> sideAttrib = new VertexAttribute<>(SIDE_KEY) {
private int[] sideRef; private int[] sideRef;
@@ -272,7 +342,7 @@ public class CCRenderState {
/** /**
* Uses the position of the lightmatrix to compute LC if not provided * Uses the position of the lightmatrix to compute LC if not provided
*/ */
public static VertexAttribute<LC[]> lightCoordAttrib = new VertexAttribute<>() { public VertexAttribute<LC[]> lightCoordAttrib = new VertexAttribute<>(LC_KEY) {
private LC[] lcRef; private LC[] lcRef;
private final Vector3 vec = new Vector3(); // for computation private final Vector3 vec = new Vector3(); // for computation
@@ -1,5 +1,7 @@
package codechicken.lib.render; package codechicken.lib.render;
import java.util.Arrays;
import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.particle.EffectRenderer;
import net.minecraft.client.particle.EntityFX; import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
@@ -11,6 +13,9 @@ import codechicken.lib.vec.Vector3;
public class EntityDigIconFX extends EntityFX { public class EntityDigIconFX extends EntityFX {
/** Tint that leaves the particle colour untouched, for materials that don't colour their texture. */
public static final int NO_TINT = 0xFFFFFF;
public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) { public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) {
super(world, x, y, z, dx, dy, dz); super(world, x, y, z, dx, dy, dz);
particleIcon = icon; particleIcon = icon;
@@ -19,6 +24,18 @@ public class EntityDigIconFX extends EntityFX {
particleScale /= 2.0F; particleScale /= 2.0F;
} }
/**
* Multiplies the particle colour by an 0xRRGGBB tint, the equivalent of vanilla
* EntityDiggingFX.applyColourMultiplier. Materials that draw a greyscale texture and colour it at render time
* (leaves, grass tops) need this, or their particles come out flat grey.
*/
public EntityDigIconFX applyColourMultiplier(int colour) {
particleRed *= (colour >> 16 & 255) / 255F;
particleGreen *= (colour >> 8 & 255) / 255F;
particleBlue *= (colour & 255) / 255F;
return this;
}
@Override @Override
public int getFXLayer() { public int getFXLayer() {
return 1; return 1;
@@ -90,8 +107,24 @@ public class EntityDigIconFX extends EntityFX {
f9); f9);
} }
/**
* @deprecated the particle is never tinted, so a material that colours a greyscale texture at render time (leaves,
* grass tops) breaks into grey particles. Use
* {@link #addBlockHitEffects(World, Cuboid6, int, IIcon, int, EffectRenderer)}, passing
* {@link #NO_TINT} for materials whose texture is already coloured.
*/
@Deprecated
public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon,
EffectRenderer effectRenderer) { EffectRenderer effectRenderer) {
addBlockHitEffects(world, bounds, side, icon, NO_TINT, effectRenderer);
}
/**
* Spawns a hit particle on side of bounds, tinted by colour (0xRRGGBB, {@link #NO_TINT} for none). The caller
* resolves the tint for the side it hit, so per-face materials like grass can tint only the faces that need it.
*/
public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, int colour,
EffectRenderer effectRenderer) {
float border = 0.1F; float border = 0.1F;
Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border); Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border);
diff.x *= world.rand.nextDouble(); diff.x *= world.rand.nextDouble();
@@ -106,13 +139,34 @@ public class EntityDigIconFX extends EntityFX {
if (side == 4) diff.x = bounds.min.x - border; if (side == 4) diff.x = bounds.min.x - border;
if (side == 5) diff.x = bounds.max.x + border; if (side == 5) diff.x = bounds.max.x + border;
effectRenderer.addEffect( EntityDigIconFX fx = new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon);
new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon).multiplyVelocity(0.2F) effectRenderer.addEffect(fx.applyColourMultiplier(colour).multiplyVelocity(0.2F).multipleParticleScaleBy(0.6F));
.multipleParticleScaleBy(0.6F));
} }
/**
* @deprecated the particles are never tinted, so a material that colours a greyscale texture at render time
* (leaves, grass tops) breaks into grey particles. Use
* {@link #addBlockDestroyEffects(World, Cuboid6, IIcon[], int[], EffectRenderer)}, passing
* {@link #NO_TINT} for the faces whose texture is already coloured.
*/
@Deprecated
public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons,
EffectRenderer effectRenderer) { EffectRenderer effectRenderer) {
int[] colours = new int[icons.length];
Arrays.fill(colours, NO_TINT);
addBlockDestroyEffects(world, bounds, icons, colours, effectRenderer);
}
/**
* Spawns break particles filling bounds, each drawn from a random face. colours holds the 0xRRGGBB tint for the
* matching entry of icons ({@link #NO_TINT} for none) and must be the same length, so a particle is tinted by the
* face it was taken from.
*/
public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, int[] colours,
EffectRenderer effectRenderer) {
if (icons.length != colours.length)
throw new IllegalArgumentException("icons and colours must have the same length");
Vector3 diff = bounds.max.copy().subtract(bounds.min); Vector3 diff = bounds.max.copy().subtract(bounds.min);
Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5); Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5);
Vector3 density = diff.copy().multiply(4); Vector3 density = diff.copy().multiply(4);
@@ -121,19 +175,20 @@ public class EntityDigIconFX extends EntityFX {
density.z = Math.ceil(density.z); density.z = Math.ceil(density.z);
for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) { for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) {
double x = bounds.min.x + (i + 0.5) * diff.x / density.x; double px = bounds.min.x + (i + 0.5) * diff.x / density.x;
double y = bounds.min.y + (j + 0.5) * diff.y / density.y; double py = bounds.min.y + (j + 0.5) * diff.y / density.y;
double z = bounds.min.z + (k + 0.5) * diff.z / density.z; double pz = bounds.min.z + (k + 0.5) * diff.z / density.z;
effectRenderer.addEffect( int iconSide = world.rand.nextInt(icons.length);
new EntityDigIconFX( EntityDigIconFX fx = new EntityDigIconFX(
world, world,
x, px,
y, py,
z, pz,
x - center.x, px - center.x,
y - center.y, py - center.y,
z - center.z, pz - center.z,
icons[world.rand.nextInt(icons.length)])); icons[iconSide]);
effectRenderer.addEffect(fx.applyColourMultiplier(colours[iconSide]));
} }
} }
} }
@@ -6,6 +6,8 @@ import net.minecraft.item.ItemStack;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import codechicken.lib.colour.LocalizedColours;
public class FontUtils { public class FontUtils {
public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer;
@@ -53,7 +55,7 @@ public class FontUtils {
GL11.glPushMatrix(); GL11.glPushMatrix();
GL11.glTranslated(x + 16 - swidth, y + 16 - sheight, 0); GL11.glTranslated(x + 16 - swidth, y + 16 - sheight, 0);
GL11.glScaled(scale, scale, 1); GL11.glScaled(scale, scale, 1);
fontRenderer.drawStringWithShadow(quantity, 0, 0, 0xFFFFFF); fontRenderer.drawStringWithShadow(quantity, 0, 0, LocalizedColours.ITEM_QUANTITY_TEXT);
GL11.glPopMatrix(); GL11.glPopMatrix();
GL11.glEnable(GL11.GL_LIGHTING); GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glEnable(GL11.GL_DEPTH_TEST);
@@ -14,14 +14,14 @@ public abstract class Transformation extends ITransformation<Vector3, Transforma
/** /**
* Applies this transformation to a normal (doesn't translate) * Applies this transformation to a normal (doesn't translate)
* *
* @param normal The normal to transform * @param normal The normal to transform
*/ */
public abstract void applyN(Vector3 normal); public abstract void applyN(Vector3 normal);
/** /**
* Applies this transformation to a matrix as a multiplication on the right hand side. * Applies this transformation to a matrix as a multiplication on the right hand side.
* *
* @param mat The matrix to combine this transformation with * @param mat The matrix to combine this transformation with
*/ */
public abstract void apply(Matrix4 mat); public abstract void apply(Matrix4 mat);
@@ -39,14 +39,14 @@ public abstract class Transformation extends ITransformation<Vector3, Transforma
@Override @Override
public boolean load(CCRenderState state) { public boolean load(CCRenderState state) {
state.pipeline.addRequirement(CCRenderState.normalAttrib.operationID()); state.pipeline.addRequirement(CCRenderState.normalAttrib().operationID());
return !isRedundant(); return !isRedundant();
} }
@Override @Override
public void operate(CCRenderState state) { public void operate(CCRenderState state) {
apply(state.vert.vec); apply(state.vert.vec);
if (CCRenderState.normalAttrib.active) applyN(state.normal); if (CCRenderState.normalAttrib().active) applyN(state.normal);
} }
@Override @Override
@@ -55,7 +55,10 @@ public class WorldExtensionManager {
public void onChunkUnLoad(ChunkEvent.Unload event) { public void onChunkUnLoad(ChunkEvent.Unload event) {
if (event.getChunk() instanceof EmptyChunk) return; if (event.getChunk() instanceof EmptyChunk) return;
for (WorldExtension extension : worldMap.get(event.world)) extension.unloadChunk(event.getChunk()); WorldExtension[] extensions = worldMap.get(event.world);
if (extensions == null) return;
for (WorldExtension extension : extensions) extension.unloadChunk(event.getChunk());
if (event.world.isRemote) removeChunk(event.world, event.getChunk()); if (event.world.isRemote) removeChunk(event.world, event.getChunk());
} }
@@ -79,17 +82,25 @@ public class WorldExtensionManager {
@SubscribeEvent @SubscribeEvent
public void onChunkWatch(Watch event) { public void onChunkWatch(Watch event) {
Chunk chunk = event.player.worldObj.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos); WorldExtension[] extensions = worldMap.get(event.player.worldObj);
for (WorldExtension extension : worldMap.get(event.player.worldObj))
extension.watchChunk(chunk, event.player); if (extensions != null) {
Chunk chunk = event.player.worldObj
.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos);
for (WorldExtension extension : extensions) extension.watchChunk(chunk, event.player);
}
} }
@SubscribeEvent @SubscribeEvent
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
public void onChunkUnWatch(UnWatch event) { public void onChunkUnWatch(UnWatch event) {
Chunk chunk = event.player.worldObj.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos); WorldExtension[] extensions = worldMap.get(event.player.worldObj);
for (WorldExtension extension : worldMap.get(event.player.worldObj))
extension.unwatchChunk(chunk, event.player); if (extensions != null) {
Chunk chunk = event.player.worldObj
.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos);
for (WorldExtension extension : extensions) extension.unwatchChunk(chunk, event.player);
}
} }
@SubscribeEvent @SubscribeEvent
@@ -110,7 +121,7 @@ public class WorldExtensionManager {
} }
private static boolean initialised; private static boolean initialised;
private static ArrayList<WorldExtensionInstantiator> extensionIntialisers = new ArrayList<WorldExtensionInstantiator>(); private static ArrayList<WorldExtensionInstantiator> extensionIntialisers = new ArrayList<>();
public static void registerWorldExtension(WorldExtensionInstantiator init) { public static void registerWorldExtension(WorldExtensionInstantiator init) {
if (!initialised) init(); if (!initialised) init();
@@ -125,7 +136,7 @@ public class WorldExtensionManager {
FMLCommonHandler.instance().bus().register(new WorldExtensionEventHandler()); FMLCommonHandler.instance().bus().register(new WorldExtensionEventHandler());
} }
private static HashMap<World, WorldExtension[]> worldMap = new HashMap<World, WorldExtension[]>(); private static HashMap<World, WorldExtension[]> worldMap = new HashMap<>();
private static void onWorldLoad(World world) { private static void onWorldLoad(World world) {
WorldExtension[] extensions = new WorldExtension[extensionIntialisers.size()]; WorldExtension[] extensions = new WorldExtension[extensionIntialisers.size()];
@@ -4,13 +4,15 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.commons.RemappingClassAdapter; import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import com.google.common.base.Function; import com.google.common.base.Function;
@@ -181,7 +183,40 @@ public class ObfuscationRun implements ILogStreams {
public void remap(ClassNode cnode, ClassVisitor cv) { public void remap(ClassNode cnode, ClassVisitor cv) {
cstMappper.transform(cnode); cstMappper.transform(cnode);
cnode.accept(new RemappingClassAdapter(cv, obfMapper)); cnode.accept(newRemapper(cv, obfMapper));
}
private static ClassVisitor newRemapper(ClassVisitor visitor, Remapper remapper) {
try {
return theConstructor.newInstance(visitor, remapper);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private static final Constructor<? extends ClassVisitor> theConstructor;
static {
Class<? extends ClassVisitor> remapperClass;
try {
// noinspection unchecked
remapperClass = (Class<? extends ClassVisitor>) Class.forName("org.objectweb.asm.commons.ClassRemapper");
} catch (ClassNotFoundException e) {
try {
// noinspection unchecked
remapperClass = (Class<? extends ClassVisitor>) Class
.forName("org.objectweb.asm.commons.RemappingClassAdapter");
} catch (ClassNotFoundException ex) {
RuntimeException err = new RuntimeException(ex);
err.addSuppressed(e);
throw err;
}
}
try {
theConstructor = remapperClass.getConstructor(ClassVisitor.class, Remapper.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
} }
public static List<String> getParents(ClassNode cnode) { public static List<String> getParents(ClassNode cnode) {
@@ -1 +1,26 @@
codechickencore.update=Version %s of %s is available codechickencore.update=Version %s of %s is available
codechickencore.color.buttonTextDisabled=FFA0A0A0
codechickencore.color.buttonTextHover=FFFFFFA0
codechickencore.color.buttonText=FFE0E0E0
codechickencore.color.textFieldBorder=FFA0A0A0
codechickencore.color.textFieldBackground=FF000000
codechickencore.color.textFieldText=E0E0E0
codechickencore.color.textFieldTextDisabled=707070
codechickencore.color.background=FF000000
codechickencore.color.overlayTop=FFA0A0A0
codechickencore.color.overlayBottom=FFA0A0A0
codechickencore.color.overlayLeft=FFA0A0A0
codechickencore.color.overlayRight=FFA0A0A0
codechickencore.color.scrollbarCorners=FF8B8B8B
codechickencore.color.scrollbarTopLeft=FFF0F0F0
codechickencore.color.scrollbarBottomRight=FF555555
codechickencore.color.scrollbarFill=FFC6C6C6
codechickencore.color.scrollbarGuide=FF808080
codechickencore.color.tooltipBgStart=F0100010
codechickencore.color.tooltipBgEnd=F0100010
codechickencore.color.tooltipBorderStart=505000FF
codechickencore.color.tooltipBorderEnd=5028007F
codechickencore.color.tooltipText=FFFFFFFF
codechickencore.color.itemQuantityText=FFFFFF
codechickencore.color.modDescriptionText=DDDDDD
@@ -0,0 +1 @@
codechickencore.update=Доступна версія %s для %s
@@ -0,0 +1,218 @@
package codechicken.lib.packet;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.zip.Deflater;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class PacketCustomTest {
private static final int MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024;
@Test
public void rejectsLargeDeclarationWithoutCompressedData() {
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(8 * 1024 * 1024));
}
@ParameterizedTest
@ValueSource(ints = { -1, Integer.MIN_VALUE, MAX_DECOMPRESSED_SIZE + 1, Integer.MAX_VALUE })
public void rejectsInvalidDeclaredLengths(int length) {
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(length));
}
@Test
public void rejectsMissingCompressedDataWithinLimit() {
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(MAX_DECOMPRESSED_SIZE));
}
@Test
public void rejectsCompleteStreamAboveDecodeLimit() {
assertRejected(compressed(new byte[MAX_DECOMPRESSED_SIZE + 1]));
}
@Test
public void rejectsTruncatedLengthHeader() {
assertRejected(Unpooled.buffer(4).writeByte(0x81).writeMedium(10));
}
@Test
public void rejectsMismatchedInflatedLengths() {
byte[] data = new byte[16384];
assertRejected(compressed(data).setInt(1, data.length - 1));
assertRejected(compressed(data).setInt(1, data.length + 1));
assertRejected(compressed(data).setInt(1, 0));
}
@Test
public void rejectsTruncatedStreamEvenWhenAllOutputWasProduced() {
ByteBuf payload = compressed(new byte[16384]);
payload.writerIndex(payload.writerIndex() - 1);
assertRejected(payload);
}
@Test
public void rejectsCorruptStream() {
ByteBuf payload = compressed(new byte[16384]);
payload.setByte(payload.writerIndex() - 1, payload.getByte(payload.writerIndex() - 1) ^ 1);
assertRejected(payload);
}
@Test
public void rejectsDictionaryDependentStream() {
assertRejected(compressed(new byte[16384], new byte[] { 1, 2, 3 }));
}
@Test
public void rejectsTrailingCompressedData() {
assertRejected(compressed(new byte[16384]).writeByte(0));
}
@ParameterizedTest
@ValueSource(ints = { 0, 1, 8192, 8193, MAX_DECOMPRESSED_SIZE })
public void decodesCompleteStreams(int length) {
byte[] data = new byte[length];
Arrays.fill(data, (byte) 42);
assertDecoded(compressed(data), data);
}
@Test
public void decodesSlicedAndDirectBuffers() {
byte[] data = new byte[16384];
Arrays.fill(data, (byte) 42);
ByteBuf payload = compressed(data);
ByteBuf prefixed = Unpooled.buffer().writeLong(-1).writeBytes(payload, 0, payload.readableBytes());
ByteBuf direct = Unpooled.directBuffer().writeBytes(payload);
payload.release();
try {
assertDecoded(prefixed.slice(8, prefixed.readableBytes() - 8).retain(), data);
assertDecoded(direct, data);
} finally {
prefixed.release();
}
}
@Test
public void preservesUncompressedPackets() {
ByteBuf payload = Unpooled.buffer().writeByte(7).writeInt(42);
try {
PacketCustom packet = new PacketCustom(payload);
assertEquals(7, packet.getType());
assertEquals(42, packet.readInt());
assertEquals(0, packet.getByteBuf().readableBytes());
} finally {
payload.release();
}
}
@Test
public void roundTripsOutgoingCompression() {
for (int length : new int[] { 16, 64, 40000, MAX_DECOMPRESSED_SIZE }) {
byte[] data = new byte[length];
Arrays.fill(data, (byte) 42);
PacketCustom outgoing = new PacketCustom("PacketTest", 7);
FMLProxyPacket wire = null;
try {
outgoing.writeByteArray(data);
if (length <= 64) outgoing.compress();
wire = outgoing.toPacket();
assertEquals(length == 16 ? 7 : 0x87, wire.payload().getUnsignedByte(0));
assertDecoded(wire.payload().retain(), data);
} finally {
if (wire != null) wire.payload().release();
outgoing.getByteBuf().release();
}
}
}
@ParameterizedTest
@ValueSource(booleans = { false, true })
public void repeatedSerializationPreservesCompressedPayload(boolean forceCompression) {
byte[] data = new byte[40000];
Arrays.fill(data, (byte) 42);
PacketCustom outgoing = new PacketCustom("PacketTest", 7).writeByteArray(data);
if (forceCompression) outgoing.compress();
try {
for (int serialization = 0; serialization < 3; serialization++) {
FMLProxyPacket wire = outgoing.toPacket();
try {
assertEquals(0x87, wire.payload().getUnsignedByte(0));
assertDecoded(wire.payload().retain(), data);
} finally {
wire.payload().release();
}
}
} finally {
outgoing.getByteBuf().release();
}
}
@Test
public void rejectsOutgoingPayloadAboveDecodeLimit() {
PacketCustom outgoing = new PacketCustom("PacketTest", 7);
try {
outgoing.getByteBuf().writeZero(MAX_DECOMPRESSED_SIZE + 1);
assertThrows(IllegalArgumentException.class, outgoing::toPacket);
} finally {
outgoing.getByteBuf().release();
}
}
private static ByteBuf compressed(byte[] data) {
return compressed(data, null);
}
private static ByteBuf compressed(byte[] data, byte[] dictionary) {
Deflater deflater = new Deflater();
try {
if (dictionary != null) deflater.setDictionary(dictionary);
deflater.setInput(data);
deflater.finish();
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
byte[] scratch = new byte[8192];
while (!deflater.finished()) {
int count = deflater.deflate(scratch);
compressed.write(scratch, 0, count);
}
return Unpooled.buffer().writeByte(0x87).writeInt(data.length).writeBytes(compressed.toByteArray());
} finally {
deflater.end();
}
}
private static void assertDecoded(ByteBuf payload, byte[] expected) {
PacketCustom packet = null;
try {
packet = new PacketCustom(payload);
assertEquals(7, packet.getType());
assertEquals(expected.length, packet.getByteBuf().readableBytes());
byte[] actual = new byte[expected.length];
packet.getByteBuf().readBytes(actual);
assertArrayEquals(expected, actual);
} finally {
if (packet != null && packet.getByteBuf() != payload) packet.getByteBuf().release();
payload.release();
}
}
private static void assertRejected(ByteBuf payload) {
try {
assertThrows(IllegalArgumentException.class, () -> {
PacketCustom packet = new PacketCustom(payload);
if (packet.getByteBuf() != payload) packet.getByteBuf().release();
});
} finally {
payload.release();
}
}
}
@@ -0,0 +1,102 @@
package codechicken.lib.render;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import codechicken.lib.vec.Vector3;
public class CCRenderStateThreadingTest {
private static int[] attribIndices() {
return new int[] { CCRenderState.normalAttrib().attributeIndex, CCRenderState.colourAttrib().attributeIndex,
CCRenderState.lightingAttrib().attributeIndex, CCRenderState.sideAttrib().attributeIndex,
CCRenderState.lightCoordAttrib().attributeIndex };
}
@Test
public void attributeIndicesStableAcrossThreads() throws Exception {
int[] main = attribIndices();
assertEquals(5, Arrays.stream(main).distinct().count());
ExecutorService pool = Executors.newFixedThreadPool(8);
try {
List<Future<int[]>> futures = new ArrayList<>();
for (int i = 0; i < 8; i++) futures.add(pool.submit(CCRenderStateThreadingTest::attribIndices));
for (Future<int[]> f : futures) assertArrayEquals(main, f.get(10, TimeUnit.SECONDS));
} finally {
pool.shutdownNow();
}
}
@Test
public void modelBakedOnOneThreadIsReadableOnAnother() throws Exception {
ExecutorService bakeThread = Executors.newSingleThreadExecutor();
ExecutorService renderThread = Executors.newSingleThreadExecutor();
try {
CCModel model = bakeThread.submit(() -> {
CCModel m = CCModel.quadModel(4);
m.verts[0] = new Vertex5(0, 1, 0, 0, 0);
m.verts[1] = new Vertex5(0, 1, 1, 0, 1);
m.verts[2] = new Vertex5(1, 1, 1, 1, 1);
m.verts[3] = new Vertex5(1, 1, 0, 1, 0);
return m.computeNormals();
}).get(10, TimeUnit.SECONDS);
int side = renderThread.submit(() -> {
assertTrue(
model.hasAttribute(CCRenderState.normalAttrib()),
"normals baked on another thread not visible under this thread's attributeIndex");
Vector3[] normals = model.getAttributes(CCRenderState.normalAttrib());
assertNotNull(normals);
return CCModel.findSide(normals[0]);
}).get(10, TimeUnit.SECONDS);
assertEquals(1, side);
assertEquals(1, CCModel.findSide(model.getAttributes(CCRenderState.normalAttrib())[0]));
} finally {
bakeThread.shutdownNow();
renderThread.shutdownNow();
}
}
@Test
public void concurrentFirstUseKeepsIndicesConsistent() throws Exception {
int threads = 16;
CyclicBarrier barrier = new CyclicBarrier(threads);
ExecutorService pool = Executors.newFixedThreadPool(threads);
try {
List<Future<int[]>> futures = new ArrayList<>();
for (int i = 0; i < threads; i++) futures.add(pool.submit(() -> {
barrier.await(10, TimeUnit.SECONDS);
return attribIndices();
}));
int[] expected = attribIndices();
for (Future<int[]> f : futures) assertArrayEquals(expected, f.get(10, TimeUnit.SECONDS));
} finally {
pool.shutdownNow();
}
}
@Test
public void getAttributeReturnsCanonicalForIndex() {
int normalIndex = CCRenderState.normalAttrib().attributeIndex;
CCRenderState.VertexAttribute<?> canonical = CCRenderState.getAttribute(normalIndex);
assertNotNull(canonical);
assertEquals(normalIndex, canonical.attributeIndex);
assertSame(canonical, CCRenderState.getAttribute(normalIndex));
}
}
@@ -0,0 +1,20 @@
package codechicken.lib.render;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import net.minecraft.util.IIcon;
import org.junit.jupiter.api.Test;
public class EntityDigIconFXTest {
@Test
public void rejectsMismatchedIconAndColourCountsBeforeSpawning() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> EntityDigIconFX.addBlockDestroyEffects(null, null, new IIcon[1], new int[0], null));
assertEquals("icons and colours must have the same length", exception.getMessage());
}
}