Initial commit

This commit is contained in:
FliegendeWurst 2020-05-16 13:56:21 +02:00
commit 03f32a594b
No known key found for this signature in database
GPG Key ID: CA38E82B54B32A88
15 changed files with 1050 additions and 0 deletions

32
.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
# eclipse
bin
*.launch
.settings
.metadata
.classpath
.project
# idea
out
*.ipr
*.iws
*.iml
.idea
# gradle
build
.gradle
# other
eclipse
run
# Files from Forge MDK
forge*changelog.txt
# local maven repo
mcmodsrepo
# Keystore
servertps.jks
signing.properties

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# ServerTPS
Displays server TPS estimate and client CPU usage in F3 debug screen

94
build.gradle Normal file
View File

@ -0,0 +1,94 @@
buildscript {
repositories {
maven { url = 'https://files.minecraftforge.net/maven' }
jcenter()
mavenCentral()
}
dependencies {
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
}
}
apply plugin: 'net.minecraftforge.gradle'
// Only edit below this line, the above code adds and enables the necessary things for Forge to be setup.
apply plugin: 'eclipse'
apply plugin: 'maven-publish'
ext.configFile = file('build.properties')
ext.config = parseConfig(configFile)
ext.signingFile = file('signing.properties')
if (signingFile != null) {
ext.signing = parseConfig(signingFile)
}
version = config.version
group = "fliegendewurst.${config.mod_id}"
archivesBaseName = config.mod_name
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8'
minecraft {
mappings channel: "${config.mapping_channel}", version: "${config.mapping_version}"
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
runs {
client {
workingDirectory project.file('run')
property 'forge.logging.markers', ''
property 'forge.logging.console.level', 'debug'
}
server {
workingDirectory project.file('run')
property 'forge.logging.markers', 'REGISTRIES,REGISTRYDUMP'
property 'forge.logging.console.level', 'debug'
}
}
}
dependencies {
minecraft "net.minecraftforge:forge:${config.mc_version}-${config.forge_version}"
}
jar {
archiveName = "${baseName}-${config.mc_version}-${version}.${extension}"
manifest {
attributes([
"Specification-Title": "${config.mod_id}",
"Specification-Vendor": 'FliegendeWurst',
"Specification-Version": '1',
"Implementation-Title": "${config.mod_id}",
"Implementation-Version": "${version}",
"Implementation-Vendor" :'FliegendeWurst',
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"Signing-Fingerprint" : project.hasProperty('signSHA1') ? project.findProperty('signSHA1') : "unsigned"
])
}
}
// Example configuration to allow publishing using the maven-publish task
// This is the preferred method to reobfuscate your jar file
jar.finalizedBy('reobfJar')
publishing {
publications {
mavenJava(MavenPublication) {
artifact jar
}
}
repositories {
maven {
url "file:///${project.projectDir}/mcmodsrepo"
}
}
}
def parseConfig(File config) {
config.withReader {
def prop = new Properties()
prop.load(it)
return (new ConfigSlurper().parse(prop))
}
}

7
build.properties Normal file
View File

@ -0,0 +1,7 @@
mod_name=ServerTPS
forge_version=31.1.87
mapping_version=20200225-1.15.1
mapping_channel=snapshot
mod_id=servertps
version=1.0
mc_version=1.15.2

4
gradle.properties Normal file
View File

@ -0,0 +1,4 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
# This is required to provide enough memory for the Minecraft decompilation process.
org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false

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

Binary file not shown.

View File

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

172
gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,122 @@
package fliegendewurst.servertps;
import com.google.common.collect.EvictingQueue;
import net.minecraft.client.Minecraft;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.commons.lang3.ThreadUtils;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = ServerTPS.MODID)
public final class DebugHandler {
private static long lastFrame = 0;
private static final ConcurrentHashMap<Long, Integer> THREAD_PRIORITIES = new ConcurrentHashMap<>();
private static final HashMap<Long, Long> THREAD_TIMES = new HashMap<>();
private static final HashMap<Long, String> THREAD_NAMES = new HashMap<>();
private static final HashMap<Long, EvictingQueue<Float>> THREAD_CPU = new HashMap<>();
private static final Lock LOCK = new ReentrantLock();
@SubscribeEvent
public static void onDrawDebugText(RenderGameOverlayEvent.Text event) {
if (Minecraft.getInstance().gameSettings.showDebugInfo) {
List<String> side = event.getRight();
side.add("");
side.add(String.format("Client tick: %.1f ms", ServerTPS.calculateClientTick()));
side.add(String.format("Server TPS: %.1f", ServerTPS.calculateServerTPS()));
side.add("");
Map<Long, Float> threadCPU = new HashMap<>();
LOCK.lock();
THREAD_PRIORITIES.keySet().stream().filter(THREAD_CPU::containsKey).forEach(id ->
threadCPU.put(id, THREAD_CPU.get(id).stream().reduce(0f, Float::sum) / THREAD_CPU.get(id).size())
);
threadCPU.keySet().stream()
.sorted(Comparator.comparing(threadCPU::get).reversed())
.forEach(id -> {
float usage = threadCPU.get(id);
if (usage < 1.0f) {
return;
}
int priority = THREAD_PRIORITIES.get(id);
if (priority != Thread.NORM_PRIORITY) {
side.add(String.format("%s [prio %s]: %.1f%%",
THREAD_NAMES.get(id),
THREAD_PRIORITIES.get(id),
threadCPU.get(id)));
} else {
side.add(String.format("%s: %.1f%%", THREAD_NAMES.get(id), threadCPU.get(id)));
}
});
LOCK.unlock();
}
}
private static ThreadMXBean tmxb;
public static void measureCPU() {
tmxb = ManagementFactory.getThreadMXBean();
while (true) {
long start = System.nanoTime();
measureThreads();
long timeTaken = System.nanoTime() - start;
// only measure every 50 ms
long timeToWait = 5000000 - timeTaken;
if (timeToWait > 0) {
try {
Thread.sleep(timeToWait / 100000, (int) (timeToWait % 100000));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
private static void measureThreads() {
//ServerTPS.LOGGER.debug("CPU time measurement supported: {}", tmxb.isThreadCpuTimeSupported());
//ServerTPS.LOGGER.debug("CPU time measurement enabled: {}", tmxb.isThreadCpuTimeEnabled());
if (!tmxb.isThreadCpuTimeEnabled()) {
return;
}
// store thread priorities
Collection<Thread> allThreads = ThreadUtils.getAllThreads();
allThreads.forEach(thread -> THREAD_PRIORITIES.put(thread.getId(), thread.getPriority()));
try {
LOCK.lock();
THREAD_PRIORITIES.keySet().removeIf(id -> allThreads.stream().noneMatch(thread -> thread.getId() == id));
//long start = System.nanoTime();
// compute usage since last frame
long diff = System.nanoTime() - lastFrame;
lastFrame = System.nanoTime();
long[] ids = tmxb.getAllThreadIds();
for (long id : ids) {
float usage = (tmxb.getThreadCpuTime(id) - THREAD_TIMES.getOrDefault(id, 0L)) / (float) diff * 100.0f;
THREAD_CPU.computeIfAbsent(id, x -> EvictingQueue.create(20)).add(usage);
THREAD_TIMES.put(id, tmxb.getThreadCpuTime(id));
ThreadInfo threadInfo = tmxb.getThreadInfo(id);
if (threadInfo != null) {
THREAD_NAMES.put(id, threadInfo.getThreadName());
}
}
//long timeTaken = System.nanoTime() - start;
//ServerTPS.LOGGER.debug("time measurement took {} ms", timeTaken / 100000);
} finally {
LOCK.unlock();
}
}
}

View File

@ -0,0 +1,89 @@
package fliegendewurst.servertps;
import com.google.common.collect.EvictingQueue;
import net.minecraft.network.play.server.SUpdateTimePacket;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.loading.FMLEnvironment;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod("servertps")
public class ServerTPS {
public static final String MODID = "servertps";
public static final Logger LOGGER = LogManager.getLogger("ServerTPS");
public ServerTPS() {
if (FMLEnvironment.dist.isDedicatedServer()) {
throw new IllegalStateException("client-side mod!");
}
MinecraftForge.EVENT_BUS.register(this);
Thread measurementThread = new Thread(DebugHandler::measureCPU, "CPU Usage");
measurementThread.setPriority(Thread.MIN_PRIORITY);
measurementThread.start();
}
@SubscribeEvent
public void onClientConnectedToServer(ClientPlayerNetworkEvent.LoggedInEvent event) {
LOGGER.debug("client connected, starting TPS recording...");
clientTicks.clear();
serverTPS.clear();
systemTime1 = 0;
systemTime2 = 0;
serverTime = 0;
}
public static EvictingQueue<Float> clientTicks = EvictingQueue.create(20);
public static EvictingQueue<Float> serverTPS = EvictingQueue.create(3);
private static long systemTime1 = 0;
private static long systemTime2 = 0;
private static long serverTime = 0;
public static void onServerTick(SUpdateTimePacket packet) {
if (systemTime1 == 0) {
systemTime1 = System.currentTimeMillis();
serverTime = packet.getTotalWorldTime();
} else {
long newSystemTime = System.currentTimeMillis();
long newServerTime = packet.getTotalWorldTime();
serverTPS.add((((float) (newServerTime - serverTime)) / (((float) (newSystemTime - systemTime1)) / 50.0f)) * 20.0f);
systemTime1 = newSystemTime;
serverTime = newServerTime;
}
}
// TODO: two event listeners with high and low priority
@SubscribeEvent
public void onTick(TickEvent.ClientTickEvent event) {
if (event.phase == TickEvent.Phase.START) {
systemTime2 = System.currentTimeMillis();
} else {
long newSystemTime = System.currentTimeMillis();
float newClientTick = ((float) newSystemTime) - systemTime2;
//LOGGER.debug("adding {} to client TPS [{}, {}, {}, {}]", newClientTick, systemTime2, newSystemTime, clientTime, newClientTime);
//LOGGER.debug("client tick took: {} ms", newSystemTime - systemTime2);
clientTicks.add(newClientTick);
}
}
public static float calculateClientTick() {
float sum = 0.0f;
for (Float f : clientTicks) {
sum += f;
}
return sum / (float) clientTicks.size();
}
public static float calculateServerTPS() {
float sum = 0.0f;
for (Float f : serverTPS) {
sum += f;
}
return sum / (float) serverTPS.size();
}
}

View File

@ -0,0 +1,3 @@
{
"ServerTPS Transformer": "servertps-transformer.js"
}

View File

@ -0,0 +1,28 @@
modLoader="javafml"
loaderVersion="[31,)"
issueTrackerURL="https://github.com/FliegendeWurst/ServerTPS/issues"
[[mods]]
modId="servertps"
displayName="ServerTPS"
version="1.0"
#updateJSONURL="https://servertps.com/"
displayURL="https://github.com/FliegendeWurst/ServerTPS/"
#logoFile="servertps.png"
authors="FliegendeWurst"
description='''
Displays server TPS estimate and client CPU usage in F3 debug screen
'''
[[dependencies.servertps]]
modId="forge"
mandatory=true
versionRange="[31,)" #mandatory
ordering="NONE"
side="BOTH"
[[dependencies.servertps]]
modId="minecraft"
mandatory=true
versionRange="[1.15.2]"
ordering="NONE"
side="BOTH"

View File

@ -0,0 +1,6 @@
{
"pack": {
"description": "servertps resources",
"pack_format": 5
}
}

View File

@ -0,0 +1,401 @@
/**
* This function is called by Forge before any minecraft classes are loaded to
* setup the coremod.
*
* @return {object} All the transformers of this coremod.
*/
function initializeCoreMod() {
/*Class/Interface*/ Opcodes = Java.type("org.objectweb.asm.Opcodes");
/*Class*/ ASMAPI = Java.type("net.minecraftforge.coremod.api.ASMAPI");;
/*Class*/ InsnList = Java.type("org.objectweb.asm.tree.InsnList");
/*Class*/ LabelNode = Java.type("org.objectweb.asm.tree.LabelNode");
/*Class*/ FieldNode = Java.type("org.objectweb.asm.tree.FieldNode");
/*Class*/ MethodNode = Java.type("org.objectweb.asm.tree.MethodNode");
/*Class*/ AbstractInsnNode = Java.type("org.objectweb.asm.tree.AbstractInsnNode");
/*Class*/ InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");
/*Class*/ VarInsnNode = Java.type("org.objectweb.asm.tree.VarInsnNode");
/*Class*/ FieldInsnNode = Java.type("org.objectweb.asm.tree.FieldInsnNode");
/*Class*/ MethodInsnNode = Java.type("org.objectweb.asm.tree.MethodInsnNode");
/*Class*/ JumpInsnNode = Java.type("org.objectweb.asm.tree.JumpInsnNode");
/*Class*/ TypeInsnNode = Java.type("org.objectweb.asm.tree.TypeInsnNode");
ACC_PUBLIC = Opcodes.ACC_PUBLIC;
INVOKESTATIC = Opcodes.INVOKESTATIC;
INVOKEVIRTUAL = Opcodes.INVOKEVIRTUAL;
ALOAD = Opcodes.ALOAD;
ILOAD = Opcodes.ILOAD;
FLOAD = Opcodes.FLOAD;
DLOAD = Opcodes.DLOAD;
ASTORE = Opcodes.ASTORE;
ISTORE = Opcodes.ISTORE;
RETURN = Opcodes.RETURN;
ARETURN = Opcodes.ARETURN;
IRETURN = Opcodes.IRETURN;
DRETURN = Opcodes.DRETURN;
NEW = Opcodes.NEW;
ACONST_NULL = Opcodes.ACONST_NULL;
ICONST_0 = Opcodes.ICONST_0;
IFEQ = Opcodes.IFEQ;
IFNE = Opcodes.IFNE;
IF_ACMPEQ = Opcodes.IF_ACMPEQ;
IFNULL = Opcodes.IFNULL;
GETFIELD = Opcodes.GETFIELD;
GETSTATIC = Opcodes.GETSTATIC;
GOTO = Opcodes.GOTO;
LABEL = AbstractInsnNode.LABEL;
METHOD_INSN = AbstractInsnNode.METHOD_INSN;
VAR_INSN = AbstractInsnNode.VAR_INSN;
return {
"ClientPlayNetHandler called every tick": {
"target": {
"type": "METHOD",
"class": "net.minecraft.client.network.play.ClientPlayNetHandler",
"methodName": "func_147285_a", // func_147285_a handleTimeUpdate
"methodDesc": "(Lnet/minecraft/network/play/server/SUpdateTimePacket;)V"
},
"transformer": function (methodNode) {
//printInstructions(methodNode.instructions);
var loadArg = new VarInsnNode(ALOAD, 1);
var callMe = new MethodInsnNode(INVOKESTATIC, "fliegendewurst/servertps/ServerTPS", "onServerTick", "(Lnet/minecraft/network/play/server/SUpdateTimePacket;)V", false);
var toInject = new InsnList();
toInject.add(loadArg);
toInject.add(callMe);
methodNode.instructions.insert(methodNode.instructions.get(6), toInject);
return methodNode;
}
}
};
}
/**
* Util function to print a list of instructions for debugging
*
* @param {InsnList} instructions The list of instructions to print
*/
function printInstructions(instructions) {
var arrayLength = instructions.size();
var labelNames = {
length: 0
};
for (var i = 0; i < arrayLength; ++i) {
var text = getInstructionText(instructions.get(i), labelNames);
if (text.length > 0) // Some instructions are ignored
print(text);
}
}
/**
* Util function to get the text for an instruction
*
* @param {AbstractInsnNode} instruction The instruction to generate text for
* @param {Map<int, string>} labelNames The names of the labels in the format Map<LabelHashCode, LabelName>
*/
function getInstructionText(instruction, labelNames) {
var out = "";
if (instruction.getType() != 8) // LABEL
out += " "; // Nice formatting
if (instruction.getOpcode() > 0) // Labels, Frames and LineNumbers don't have opcodes
out += OPCODES[instruction.getOpcode()] + " ";
switch (instruction.getType()) {
default:
case 0: // INSN
break;
case 1: // INT_INSN
out += instruction.operand;
break;
case 2: // VAR_INSN
out += instruction.var;
break;
case 3: // TYPE_INSN
out += instruction.desc;
break;
case 4: // FIELD_INSN
out += instruction.owner + "." + instruction.name + " " + instruction.desc;
break;
case 5: // METHOD_INSN
out += instruction.owner + "." + instruction.name + " " + instruction.desc + " (" + instruction.itf + ")";
break;
case 6: // INVOKE_DYNAMIC_INSN
out += instruction.name + " " + instruction.desc;
break;
case 7: // JUMP_INSN
out += getLabelName(instruction.label, labelNames);
break;
case 8: // LABEL
out += getLabelName(instruction.getLabel(), labelNames);
break;
case 9: // LDC_INSN
out += instruction.cst;
break;
case 10: // IINC_INSN
out += instruction.var + " " + instruction.incr;
break;
case 11: // TABLESWITCH_INSN
out += instruction.min + " " + instruction.max;
out += "\n";
for (var i = 0; i < instruction.labels.length; ++i) {
out += " " + (instruction.min + i) + ": ";
out += getLabelName(instruction.labels[i], labelNames);
out += "\n";
}
out += " " + "default: " + getLabelName(instruction.dflt, labelNames);
break;
case 12: // LOOKUPSWITCH_INSN
for (var i = 0; i < instruction.labels.length; ++i) {
out += " " + instruction.keys[i] + ": ";
out += getLabelName(instruction.labels[i], labelNames);
out += "\n";
}
out += " " + "default: " + getLabelName(instruction.dflt, labelNames);
break;
case 13: // MULTIANEWARRAY_INSN
out += instruction.desc + " " + instruction.dims;
break;
case 14: // FRAME
out += "FRAME";
// Frames don't work because Nashhorn calls AbstractInsnNode#getType()
// instead of accessing FrameNode#type for the code "instruction.type"
// so there is no way to get the frame type of the FrameNode
break;
case 15: // LINENUMBER
out += "LINENUMBER ";
out += instruction.line + " " + getLabelName(instruction.start.getLabel(), labelNames);
break;
}
return out;
}
/**
* Util function to get the name for a LabelNode "instruction"
*
* @param {LabelNode} label The label to generate a name for
* @param {Map<int, string>} labelNames The names of other labels in the format Map<LabelHashCode, LabelName>
*/
function getLabelName(label, labelNames) {
var labelHashCode = label.hashCode();
var labelName = labelNames[labelHashCode];
if (labelName == undefined) {
labelName = "L" + labelNames.length;
labelNames[labelHashCode] = labelName;
++labelNames.length;
}
return labelName;
}
/** The names of the Java Virtual Machine opcodes. */
OPCODES = [
"NOP", // 0 (0x0)
"ACONST_NULL", // 1 (0x1)
"ICONST_M1", // 2 (0x2)
"ICONST_0", // 3 (0x3)
"ICONST_1", // 4 (0x4)
"ICONST_2", // 5 (0x5)
"ICONST_3", // 6 (0x6)
"ICONST_4", // 7 (0x7)
"ICONST_5", // 8 (0x8)
"LCONST_0", // 9 (0x9)
"LCONST_1", // 10 (0xa)
"FCONST_0", // 11 (0xb)
"FCONST_1", // 12 (0xc)
"FCONST_2", // 13 (0xd)
"DCONST_0", // 14 (0xe)
"DCONST_1", // 15 (0xf)
"BIPUSH", // 16 (0x10)
"SIPUSH", // 17 (0x11)
"LDC", // 18 (0x12)
"LDC_W", // 19 (0x13)
"LDC2_W", // 20 (0x14)
"ILOAD", // 21 (0x15)
"LLOAD", // 22 (0x16)
"FLOAD", // 23 (0x17)
"DLOAD", // 24 (0x18)
"ALOAD", // 25 (0x19)
"ILOAD_0", // 26 (0x1a)
"ILOAD_1", // 27 (0x1b)
"ILOAD_2", // 28 (0x1c)
"ILOAD_3", // 29 (0x1d)
"LLOAD_0", // 30 (0x1e)
"LLOAD_1", // 31 (0x1f)
"LLOAD_2", // 32 (0x20)
"LLOAD_3", // 33 (0x21)
"FLOAD_0", // 34 (0x22)
"FLOAD_1", // 35 (0x23)
"FLOAD_2", // 36 (0x24)
"FLOAD_3", // 37 (0x25)
"DLOAD_0", // 38 (0x26)
"DLOAD_1", // 39 (0x27)
"DLOAD_2", // 40 (0x28)
"DLOAD_3", // 41 (0x29)
"ALOAD_0", // 42 (0x2a)
"ALOAD_1", // 43 (0x2b)
"ALOAD_2", // 44 (0x2c)
"ALOAD_3", // 45 (0x2d)
"IALOAD", // 46 (0x2e)
"LALOAD", // 47 (0x2f)
"FALOAD", // 48 (0x30)
"DALOAD", // 49 (0x31)
"AALOAD", // 50 (0x32)
"BALOAD", // 51 (0x33)
"CALOAD", // 52 (0x34)
"SALOAD", // 53 (0x35)
"ISTORE", // 54 (0x36)
"LSTORE", // 55 (0x37)
"FSTORE", // 56 (0x38)
"DSTORE", // 57 (0x39)
"ASTORE", // 58 (0x3a)
"ISTORE_0", // 59 (0x3b)
"ISTORE_1", // 60 (0x3c)
"ISTORE_2", // 61 (0x3d)
"ISTORE_3", // 62 (0x3e)
"LSTORE_0", // 63 (0x3f)
"LSTORE_1", // 64 (0x40)
"LSTORE_2", // 65 (0x41)
"LSTORE_3", // 66 (0x42)
"FSTORE_0", // 67 (0x43)
"FSTORE_1", // 68 (0x44)
"FSTORE_2", // 69 (0x45)
"FSTORE_3", // 70 (0x46)
"DSTORE_0", // 71 (0x47)
"DSTORE_1", // 72 (0x48)
"DSTORE_2", // 73 (0x49)
"DSTORE_3", // 74 (0x4a)
"ASTORE_0", // 75 (0x4b)
"ASTORE_1", // 76 (0x4c)
"ASTORE_2", // 77 (0x4d)
"ASTORE_3", // 78 (0x4e)
"IASTORE", // 79 (0x4f)
"LASTORE", // 80 (0x50)
"FASTORE", // 81 (0x51)
"DASTORE", // 82 (0x52)
"AASTORE", // 83 (0x53)
"BASTORE", // 84 (0x54)
"CASTORE", // 85 (0x55)
"SASTORE", // 86 (0x56)
"POP", // 87 (0x57)
"POP2", // 88 (0x58)
"DUP", // 89 (0x59)
"DUP_X1", // 90 (0x5a)
"DUP_X2", // 91 (0x5b)
"DUP2", // 92 (0x5c)
"DUP2_X1", // 93 (0x5d)
"DUP2_X2", // 94 (0x5e)
"SWAP", // 95 (0x5f)
"IADD", // 96 (0x60)
"LADD", // 97 (0x61)
"FADD", // 98 (0x62)
"DADD", // 99 (0x63)
"ISUB", // 100 (0x64)
"LSUB", // 101 (0x65)
"FSUB", // 102 (0x66)
"DSUB", // 103 (0x67)
"IMUL", // 104 (0x68)
"LMUL", // 105 (0x69)
"FMUL", // 106 (0x6a)
"DMUL", // 107 (0x6b)
"IDIV", // 108 (0x6c)
"LDIV", // 109 (0x6d)
"FDIV", // 110 (0x6e)
"DDIV", // 111 (0x6f)
"IREM", // 112 (0x70)
"LREM", // 113 (0x71)
"FREM", // 114 (0x72)
"DREM", // 115 (0x73)
"INEG", // 116 (0x74)
"LNEG", // 117 (0x75)
"FNEG", // 118 (0x76)
"DNEG", // 119 (0x77)
"ISHL", // 120 (0x78)
"LSHL", // 121 (0x79)
"ISHR", // 122 (0x7a)
"LSHR", // 123 (0x7b)
"IUSHR", // 124 (0x7c)
"LUSHR", // 125 (0x7d)
"IAND", // 126 (0x7e)
"LAND", // 127 (0x7f)
"IOR", // 128 (0x80)
"LOR", // 129 (0x81)
"IXOR", // 130 (0x82)
"LXOR", // 131 (0x83)
"IINC", // 132 (0x84)
"I2L", // 133 (0x85)
"I2F", // 134 (0x86)
"I2D", // 135 (0x87)
"L2I", // 136 (0x88)
"L2F", // 137 (0x89)
"L2D", // 138 (0x8a)
"F2I", // 139 (0x8b)
"F2L", // 140 (0x8c)
"F2D", // 141 (0x8d)
"D2I", // 142 (0x8e)
"D2L", // 143 (0x8f)
"D2F", // 144 (0x90)
"I2B", // 145 (0x91)
"I2C", // 146 (0x92)
"I2S", // 147 (0x93)
"LCMP", // 148 (0x94)
"FCMPL", // 149 (0x95)
"FCMPG", // 150 (0x96)
"DCMPL", // 151 (0x97)
"DCMPG", // 152 (0x98)
"IFEQ", // 153 (0x99)
"IFNE", // 154 (0x9a)
"IFLT", // 155 (0x9b)
"IFGE", // 156 (0x9c)
"IFGT", // 157 (0x9d)
"IFLE", // 158 (0x9e)
"IF_ICMPEQ", // 159 (0x9f)
"IF_ICMPNE", // 160 (0xa0)
"IF_ICMPLT", // 161 (0xa1)
"IF_ICMPGE", // 162 (0xa2)
"IF_ICMPGT", // 163 (0xa3)
"IF_ICMPLE", // 164 (0xa4)
"IF_ACMPEQ", // 165 (0xa5)
"IF_ACMPNE", // 166 (0xa6)
"GOTO", // 167 (0xa7)
"JSR", // 168 (0xa8)
"RET", // 169 (0xa9)
"TABLESWITCH", // 170 (0xaa)
"LOOKUPSWITCH", // 171 (0xab)
"IRETURN", // 172 (0xac)
"LRETURN", // 173 (0xad)
"FRETURN", // 174 (0xae)
"DRETURN", // 175 (0xaf)
"ARETURN", // 176 (0xb0)
"RETURN", // 177 (0xb1)
"GETSTATIC", // 178 (0xb2)
"PUTSTATIC", // 179 (0xb3)
"GETFIELD", // 180 (0xb4)
"PUTFIELD", // 181 (0xb5)
"INVOKEVIRTUAL", // 182 (0xb6)
"INVOKESPECIAL", // 183 (0xb7)
"INVOKESTATIC", // 184 (0xb8)
"INVOKEINTERFACE", // 185 (0xb9)
"INVOKEDYNAMIC", // 186 (0xba)
"NEW", // 187 (0xbb)
"NEWARRAY", // 188 (0xbc)
"ANEWARRAY", // 189 (0xbd)
"ARRAYLENGTH", // 190 (0xbe)
"ATHROW", // 191 (0xbf)
"CHECKCAST", // 192 (0xc0)
"INSTANCEOF", // 193 (0xc1)
"MONITORENTER", // 194 (0xc2)
"MONITOREXIT", // 195 (0xc3)
"WIDE", // 196 (0xc4)
"MULTIANEWARRAY", // 197 (0xc5)
"IFNULL", // 198 (0xc6)
"IFNONNULL" // 199 (0xc7)
];