From e71dbdb290d045db86c646a3398d5140bd1d4b75 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:23:19 +0200 Subject: [PATCH 001/323] Define gradle wrapper checksum --- gradle/wrapper/gradle-wrapper.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3c4101c..a257bc7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,3 +3,4 @@ distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd From dd01b057d5b8e22b919d2cbd50ff9e8807b94b99 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:28:34 +0200 Subject: [PATCH 002/323] Create custom class loader class --- .../io/pzstorm/storm/StormClassLoader.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/StormClassLoader.java diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java new file mode 100644 index 0000000..b0920b8 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -0,0 +1,23 @@ +import java.net.URLClassLoader; +/** + * This is a custom {@code ClassLoader} used to define, transform and load Project Zomboid classes. + * It is initially invoked by {@link StormLauncher} when launching the game. + */ +@SuppressWarnings("WeakerAccess") +public class StormClassLoader extends ClassLoader { + /** + * {@code ClassLoader} that is the parent of this {@code ClassLoader}. + * When loading classes, those classes not matching the whitelist pattern will have + * their loading process delegated to this {@code ClassLoader}. + */ + private final ClassLoader parentClassLoader; + private final URLClassLoader urlClassLoader; + + StormClassLoader() { + parentClassLoader = getClass().getClassLoader(); + urlClassLoader = (URLClassLoader) getParent(); + } + @Override + public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + } +} From 15e6100ce90466dd9e9c8a37501028acd8f29487 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:30:21 +0200 Subject: [PATCH 003/323] Create whitelist of classes to load --- .../io/pzstorm/storm/StormClassLoader.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index b0920b8..6779b1a 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -1,10 +1,32 @@ import java.net.URLClassLoader; +import org.jetbrains.annotations.Contract; /** * This is a custom {@code ClassLoader} used to define, transform and load Project Zomboid classes. * It is initially invoked by {@link StormLauncher} when launching the game. */ @SuppressWarnings("WeakerAccess") public class StormClassLoader extends ClassLoader { + + /** + *

{@code Set} of class prefixes that mark classes to be loaded with this class loader. + * If a class does not match this patter it's loading will be delegated to the parent loader. + *

+ * This {@code Set} includes both Java libraries and main classes. Java Libraries have to be + * loaded by the same class loader loading the game so that native libraries loaded from + * those Java libraries become associated with the class loader loading the game. + * If the native libraries are loaded using a different class loader they + * will not be accessible to game classes. + */ + private static final Set CLASS_WHITELIST = ImmutableSet.of( + // zomboid library classes + "org.lwjgl.", "net.java.games.", "jassimp.", + // zomboid main classes + "astar.", "com.evildevil.engines.bubble.texture.", + "com.jcraft.", "com.sixlegs.png.", "de.jarnbjo.", "fmod.", + "javax.vecmath.", "org.joml.", "org.luaj.kahluafork.compiler.", + "org.mindrot.jbcrypt.", "se.krka.kahlua.", "zombie." + ); + /** * {@code ClassLoader} that is the parent of this {@code ClassLoader}. * When loading classes, those classes not matching the whitelist pattern will have @@ -17,6 +39,16 @@ public class StormClassLoader extends ClassLoader { parentClassLoader = getClass().getClassLoader(); urlClassLoader = (URLClassLoader) getParent(); } + + /** + * Returns {@code true} if at least one prefix pattern in whitelist matches the given name. + * When a class name is considered whitelisted it will be loaded by this {@code ClassLoader}. + */ + @Contract("null -> fail") + protected static boolean isWhitelistedClass(String name) { + return CLASS_WHITELIST.stream().anyMatch(name::startsWith); + } + @Override public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { } From 71fcfb9baa091bacb64f4bf3d90ff92e6f91188e Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:33:24 +0200 Subject: [PATCH 004/323] Create method to read class to byte array --- .../io/pzstorm/storm/StormClassLoader.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 6779b1a..634844c 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -1,3 +1,6 @@ +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URLClassLoader; import org.jetbrains.annotations.Contract; /** @@ -51,5 +54,44 @@ protected static boolean isWhitelistedClass(String name) { @Override public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + + /** + * Converts the given class name to a file name. + */ + @Contract(pure = true, value = "null -> fail") + private String getClassFileName(String name) { + return name.replace('.', '/') + ".class"; + } + + /** + * Reads the {@code Class} with given name into a {@code byte} array and return the result. + * + * @param name name of the {@code Class} to read. + * @return {@code byte} array read from {@code Class} or an empty array + * if the {@code Class} with the given name could not be found. + * + * @throws IOException if an I/O error occurred while reading or writing to stream. + */ + @Contract("null -> fail") + protected byte[] getRawClassByteArray(String name) throws IOException { + + // opens an input stream to read the class for given name + InputStream inputStream = getResourceAsStream(getClassFileName(name)); + if (inputStream == null) { + return new byte[0]; + } + int a = inputStream.available(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(a < 32 ? 32768 : a); + /* + * read from input stream and write to output stream + * a maximum of 8192 bytes per write operation + */ + int len; + byte[] buffer = new byte[8192]; + while ((len = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, len); + } + inputStream.close(); + return outputStream.toByteArray(); } } From 607b8c4286683dcd81d02296681edaaf13ea188e Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:35:10 +0200 Subject: [PATCH 005/323] Create miscellaneous loader methods --- .../io/pzstorm/storm/StormClassLoader.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 634844c..7c17975 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -1,8 +1,12 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.net.URLClassLoader; +import java.util.Objects; +import java.util.Set; import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; /** * This is a custom {@code ClassLoader} used to define, transform and load Project Zomboid classes. * It is initially invoked by {@link StormLauncher} when launching the game. @@ -52,9 +56,36 @@ protected static boolean isWhitelistedClass(String name) { return CLASS_WHITELIST.stream().anyMatch(name::startsWith); } + @Override + @Contract("null -> fail") + public @Nullable URL getResource(String name) { + Objects.requireNonNull(name); + + URL url = urlClassLoader.getResource(name); + if (url == null) { + url = parentClassLoader.getResource(name); + } + return url; + } + + @Override + protected @Nullable URL findResource(String name) { + return urlClassLoader.findResource(name); + } @Override public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + + @Override + public @Nullable InputStream getResourceAsStream(String name) { + + InputStream inputStream = urlClassLoader.getResourceAsStream(name); + if (inputStream == null) { + inputStream = parentClassLoader.getResourceAsStream(name); + } + return inputStream; + } + /** * Converts the given class name to a file name. */ From 8fafb55b70a6964bc08e227b7831d88459826e14 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:37:12 +0200 Subject: [PATCH 006/323] Implement custom game class loading Game classes and native libraries will be loaded by StormClassLoader. --- .../io/pzstorm/storm/StormClassLoader.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 7c17975..1fac380 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -7,6 +7,10 @@ import java.util.Set; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import com.google.common.collect.ImmutableSet; + +import zombie.debug.DebugLog; + /** * This is a custom {@code ClassLoader} used to define, transform and load Project Zomboid classes. * It is initially invoked by {@link StormLauncher} when launching the game. @@ -72,9 +76,90 @@ protected static boolean isWhitelistedClass(String name) { protected @Nullable URL findResource(String name) { return urlClassLoader.findResource(name); } + + /** + * Defines and returns a package by name in this {@code ClassLoader}. + * + * @param name name of the package to define. + * @return instance of {@link Package} defined or {@code null} if no package defined. + * + * @throws IllegalArgumentException if package name duplicates an existing + * package either in this class loader or one of its ancestors + */ + @Contract("null -> fail") + protected @Nullable Package definePackageForName(String name) { + + int pkgDelimiterPos = name.lastIndexOf('.'); + if (pkgDelimiterPos > 0) + { + String pkgString = name.substring(0, pkgDelimiterPos); + if (getPackage(pkgString) == null) { + return definePackage(pkgString, null, null, + null, null, null, null, null); + } + } + return null; + } + @Override public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + // make sure our loading is not interrupted by other operations + synchronized (getClassLoadingLock(name)) { + return loadClassInternal(name, resolve); + } + } + + /** + * Loads the class with the specified binary name. This method will get called every time + * a class is about to be loaded by this {@code ClassLoader}. It is called before the class + * is defined or loaded into JVM, which allows us to manipulate the class to our desire. + * + * @param name the binary name of the class. + * @param resolve if {@code true} then resolve the class. + * @return the resulting {@code Class} object. + * + * @throws ClassNotFoundException if the class could not be found. + */ + private Class loadClassInternal(String name, boolean resolve) throws ClassNotFoundException { + + Class clazz = findLoadedClass(name); + if (clazz == null) + { + DebugLog.General.debugln("STORM: Preparing to load class " + name); + if (isWhitelistedClass(name)) + { + DebugLog.General.debugln("STORM: Loading with StormClassLoader"); + try { + byte[] input = getRawClassByteArray(name); + if (input != null) + { + // package has to be created before we define the class + definePackageForName(name); + + clazz = defineClass(name, input, 0, input.length); + if (clazz.getClassLoader() == this) { + DebugLog.General.debugln("STORM: Successfully loaded class with StormClassLoader"); + } + else throw new RuntimeException("Unable to load class with StormClassLoader"); + } + } + catch (IOException e) { + throw new RuntimeException("I/O exception occurred while transforming class to byte array"); + } + } + } + // if the class is not whitelisted delegate loading to parent class loader + if (clazz == null) + { + DebugLog.General.debugln("STORM: Loading with application class loader"); + clazz = parentClassLoader.loadClass(name); + } + if (resolve) { + resolveClass(clazz); + } + return clazz; + } @Override public @Nullable InputStream getResourceAsStream(String name) { From e0f9d30855c5934c16cde9f03b11b0d3faa972dc Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:42:34 +0200 Subject: [PATCH 007/323] Declare missing class package --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 1fac380..7356540 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -1,3 +1,5 @@ +package io.pzstorm.storm; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; From 1c8f3b67ad402de6e0a64524c117c9a8a2d7138f Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:43:39 +0200 Subject: [PATCH 008/323] Create storm launcher class --- .../java/io/pzstorm/storm/StormLauncher.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/StormLauncher.java diff --git a/src/main/java/io/pzstorm/storm/StormLauncher.java b/src/main/java/io/pzstorm/storm/StormLauncher.java new file mode 100644 index 0000000..2f50ed1 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/StormLauncher.java @@ -0,0 +1,45 @@ +package io.pzstorm.storm; + +import java.lang.reflect.Method; + +import zombie.debug.DebugLog; + +class StormLauncher { + + /** + * Class name of Project Zomboid application entry point. This is the first class + * loaded by {@link StormClassLoader} which in turn loads all game classes. + */ + private static final String ZOMBOID_ENTRY_POINT_CLASS = "zombie.gameStates.MainScreenState"; + + /** + * Name of the method that is the entry point to Project Zomboid execution. + * This will be invoked through reflection from {@link #launch(String[])} to launch the game + */ + private static final String ZOMBOID_ENTRY_POINT = "main"; + + /** + * Launch Project Zomboid with given array or arguments. + * + * @param args array of arguments to use to launch the game. + * + * @throws ReflectiveOperationException if loading or invoking entry point failed. + */ + static void launch(String[] args) throws ReflectiveOperationException { + + StormClassLoader stormLoader = new StormClassLoader(); + Class entryPointClass = stormLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); + Method entryPoint = entryPointClass.getMethod(ZOMBOID_ENTRY_POINT, String[].class); + try { + /* we invoke the entry point using reflection because we don't want to reference + * the entry point class which would to the class being loaded by application class loader + */ + entryPoint.invoke(null, (Object) args); + } + catch (Throwable e) + { + DebugLog.General.error("An unhandled exception occurred while running Project Zomboid"); + throw new RuntimeException(e); + } + } +} From 836d3c41413ccd60155e993e4394b0e6ff2c5bbe Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:44:07 +0200 Subject: [PATCH 009/323] Run storm launcher from entry point --- src/main/java/io/pzstorm/storm/Main.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/Main.java b/src/main/java/io/pzstorm/storm/Main.java index 7d14d89..30a427f 100644 --- a/src/main/java/io/pzstorm/storm/Main.java +++ b/src/main/java/io/pzstorm/storm/Main.java @@ -1,6 +1,8 @@ package io.pzstorm.storm; public class Main { + public static void main(String[] args) throws ReflectiveOperationException { + StormLauncher.launch(args); } } From a9cac4ed6ed964b66c255ec91b5ccd03a181adaa Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:51:42 +0200 Subject: [PATCH 010/323] Change storm application entry point --- src/main/java/io/pzstorm/storm/Main.java | 8 -------- src/main/java/io/pzstorm/storm/StormLauncher.java | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 src/main/java/io/pzstorm/storm/Main.java diff --git a/src/main/java/io/pzstorm/storm/Main.java b/src/main/java/io/pzstorm/storm/Main.java deleted file mode 100644 index 30a427f..0000000 --- a/src/main/java/io/pzstorm/storm/Main.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.pzstorm.storm; - -public class Main { - - public static void main(String[] args) throws ReflectiveOperationException { - StormLauncher.launch(args); - } -} diff --git a/src/main/java/io/pzstorm/storm/StormLauncher.java b/src/main/java/io/pzstorm/storm/StormLauncher.java index 2f50ed1..f3d1c71 100644 --- a/src/main/java/io/pzstorm/storm/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/StormLauncher.java @@ -14,7 +14,7 @@ class StormLauncher { /** * Name of the method that is the entry point to Project Zomboid execution. - * This will be invoked through reflection from {@link #launch(String[])} to launch the game + * This will be invoked through reflection from {@link #main(String[])} to launch the game */ private static final String ZOMBOID_ENTRY_POINT = "main"; @@ -25,7 +25,7 @@ class StormLauncher { * * @throws ReflectiveOperationException if loading or invoking entry point failed. */ - static void launch(String[] args) throws ReflectiveOperationException { + public static void main(String[] args) throws ReflectiveOperationException { StormClassLoader stormLoader = new StormClassLoader(); Class entryPointClass = stormLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); From 95476fe8cc6979e5ff4c31f8b343030c228eb437 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 09:55:02 +0200 Subject: [PATCH 011/323] Use immutable container declaration --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 7356540..9502f7b 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -6,7 +6,6 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.Objects; -import java.util.Set; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import com.google.common.collect.ImmutableSet; @@ -30,7 +29,7 @@ public class StormClassLoader extends ClassLoader { * If the native libraries are loaded using a different class loader they * will not be accessible to game classes. */ - private static final Set CLASS_WHITELIST = ImmutableSet.of( + private static final ImmutableSet CLASS_WHITELIST = ImmutableSet.of( // zomboid library classes "org.lwjgl.", "net.java.games.", "jassimp.", // zomboid main classes From c5700d96f998fdf386069dc7ed4927a2ba829d02 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 10:23:30 +0200 Subject: [PATCH 012/323] Fix truncated class error in class loading --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 9502f7b..9c4f26a 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -133,7 +133,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot DebugLog.General.debugln("STORM: Loading with StormClassLoader"); try { byte[] input = getRawClassByteArray(name); - if (input != null) + if (input.length > 0) { // package has to be created before we define the class definePackageForName(name); From 7c71161da4f9bf8ecb6c9c3d3534f918ab7707d0 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 10:28:15 +0200 Subject: [PATCH 013/323] Configure capsid gradle plugin --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index 55d1ef9..cc67893 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,12 @@ apply from: "https://gist.githubusercontent.com/yooksi/${spotlessGistID}/raw/spo version '0.1.0' +capsid { + isModProject = false + setRepositoryOwner("pzstorm") + setRepositoryName("storm") +} + dependencies { // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1' From 32897287a8fbc42de5b6ec4c6396cfe8dab855f1 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 10:28:31 +0200 Subject: [PATCH 014/323] Apply spotless code formatting --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 9c4f26a..3fb2552 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -6,8 +6,10 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.Objects; + import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; + import com.google.common.collect.ImmutableSet; import zombie.debug.DebugLog; From f9cc1ddd1198c2a51989427b80409955677ba540 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 19:49:10 +0200 Subject: [PATCH 015/323] Add IDEA search scopes --- .idea/scopes/pz_java.xml | 3 +++ .idea/scopes/pz_lua.xml | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 .idea/scopes/pz_java.xml create mode 100644 .idea/scopes/pz_lua.xml diff --git a/.idea/scopes/pz_java.xml b/.idea/scopes/pz_java.xml new file mode 100644 index 0000000..552dbfd --- /dev/null +++ b/.idea/scopes/pz_java.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/scopes/pz_lua.xml b/.idea/scopes/pz_lua.xml new file mode 100644 index 0000000..d18fdac --- /dev/null +++ b/.idea/scopes/pz_lua.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 4f50093e604945d6e4b496296c0a260cf0865add Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 22:33:12 +0200 Subject: [PATCH 016/323] Rename storm URLClassLoader field --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 3fb2552..8bb18d9 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -47,11 +47,11 @@ public class StormClassLoader extends ClassLoader { * their loading process delegated to this {@code ClassLoader}. */ private final ClassLoader parentClassLoader; - private final URLClassLoader urlClassLoader; + protected final URLClassLoader resourceClassLoader; StormClassLoader() { parentClassLoader = getClass().getClassLoader(); - urlClassLoader = (URLClassLoader) getParent(); + resourceClassLoader = (URLClassLoader) getParent(); } /** @@ -68,7 +68,7 @@ protected static boolean isWhitelistedClass(String name) { public @Nullable URL getResource(String name) { Objects.requireNonNull(name); - URL url = urlClassLoader.getResource(name); + URL url = resourceClassLoader.getResource(name); if (url == null) { url = parentClassLoader.getResource(name); } @@ -77,7 +77,7 @@ protected static boolean isWhitelistedClass(String name) { @Override protected @Nullable URL findResource(String name) { - return urlClassLoader.findResource(name); + return resourceClassLoader.findResource(name); } /** @@ -167,7 +167,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot @Override public @Nullable InputStream getResourceAsStream(String name) { - InputStream inputStream = urlClassLoader.getResourceAsStream(name); + InputStream inputStream = resourceClassLoader.getResourceAsStream(name); if (inputStream == null) { inputStream = parentClassLoader.getResourceAsStream(name); } From cb5eff3ea64867defc5a6a9b1b635ecdca0e1994 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 22:34:06 +0200 Subject: [PATCH 017/323] Expand storm class loader resource locations --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 8bb18d9..09178ae 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -49,7 +49,17 @@ public class StormClassLoader extends ClassLoader { private final ClassLoader parentClassLoader; protected final URLClassLoader resourceClassLoader; - StormClassLoader() { + /** + * Create {@code StormClassLoader} with additional locations to search for resources. + * + * @param resourceLocations the URLs from which to load classes and resources. + */ + protected StormClassLoader(URL[] resourceLocations) { + parentClassLoader = getClass().getClassLoader(); + resourceClassLoader = new URLClassLoader(resourceLocations, getParent()); + } + + protected StormClassLoader() { parentClassLoader = getClass().getClassLoader(); resourceClassLoader = (URLClassLoader) getParent(); } From 05d6af5aba09cb12d1cbdbea63b1a2041625e5d0 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 22:36:52 +0200 Subject: [PATCH 018/323] Create unit test interface class --- src/test/java/io/pzstorm/storm/UnitTest.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/UnitTest.java diff --git a/src/test/java/io/pzstorm/storm/UnitTest.java b/src/test/java/io/pzstorm/storm/UnitTest.java new file mode 100644 index 0000000..af682b3 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/UnitTest.java @@ -0,0 +1,7 @@ +package io.pzstorm.storm; + +import org.junit.jupiter.api.Tag; + +@Tag("unit") +public interface UnitTest { +} From 2700e7623a54b8c09ea7b1e9a0d3281444b0aee5 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 22:41:25 +0200 Subject: [PATCH 019/323] Exclude resource class files from gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 634e66f..9445672 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ out/ # Compiled class file *.class +# Exclude class files used as resources +!**/resources/**/*.class + # Log file *.log From 42341f35328c96ecb6c3362c5fa2681a442f690f Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 22:41:37 +0200 Subject: [PATCH 020/323] Write storm class loader unit tests --- .../pzstorm/storm/StormClassLoaderTest.java | 100 ++++++++++++++++++ .../io/pzstorm/storm/SampleClass.class | Bin 0 -> 460 bytes src/test/resources/delegate/delegate.resource | 0 3 files changed, 100 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/StormClassLoaderTest.java create mode 100644 src/test/resources/classes/io/pzstorm/storm/SampleClass.class create mode 100644 src/test/resources/delegate/delegate.resource diff --git a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java new file mode 100644 index 0000000..c2815a0 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java @@ -0,0 +1,100 @@ +package io.pzstorm.storm; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.util.*; + +import com.google.common.collect.ImmutableMap; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class StormClassLoaderTest extends StormClassLoader implements UnitTest { + + private static final ClassLoader CL = StormClassLoaderTest.class.getClassLoader(); + private static final URL CLASSES_RESOURCE_DIR = CL.getResource("./classes/"); + private static final URL DELEGATE_RESOURCE_DIR = CL.getResource("./delegate/"); + + StormClassLoaderTest() { + super(new URL[] { CLASSES_RESOURCE_DIR, DELEGATE_RESOURCE_DIR }); + } + + @Test + void shouldMatchRawClassByteArrayToClassFile() throws URISyntaxException, IOException { + + File resourceDir = new File(Objects.requireNonNull(CLASSES_RESOURCE_DIR).toURI()); + File expectedClass = new File(resourceDir, "io/pzstorm/storm/SampleClass.class"); + + String className = "io.pzstorm.storm.SampleClass"; + + byte[] expectedBytes = Files.readAllBytes(expectedClass.toPath()); + byte[] actualBytes = getRawClassByteArray(className); + + for (int i = 0; i < expectedBytes.length; i++) { + Assertions.assertEquals(expectedBytes[i], actualBytes[i]); + } + } + + @Test + void shouldDelegateToResourceClassLoaderWhenResolvingResource() { + + Assertions.assertNotNull(getResourceAsStream("delegate.resource")); + Assertions.assertNotNull(findResource("delegate.resource")); + Assertions.assertNotNull(getResource("delegate.resource")); + } + + @Test + void whenIncorrectPackageNameShouldNotDefinePackage() { + + String[] incorrectPackageNames = new String[]{ + "package", ".package", ".", "" + }; + for (String incorrectPackageName : incorrectPackageNames) { + Assertions.assertNull(definePackageForName(incorrectPackageName)); + } + } + + @Test + void shouldDefinePackageWhenPackageNameIsValid() { + + Map validPackageNames = ImmutableMap.of( + "valid.package", "valid", + "another.valid.package", "another.valid", + "yet.another.valid.package", "yet.another.valid" + ); + for (Map.Entry entry : validPackageNames.entrySet()) + { + Package resultPackage = definePackageForName(entry.getKey()); + Assertions.assertEquals(entry.getValue(), Objects.requireNonNull(resultPackage).getName()); + } + } + + @Test + @SuppressWarnings("unchecked") + void shouldProperlyRecognizeWhitelistedClassNames() throws ReflectiveOperationException { + + Field field = StormClassLoader.class.getDeclaredField("CLASS_WHITELIST"); + field.setAccessible(true); + + Set whitelist = (Set) field.get(null); + for (String className : whitelist) { + Assertions.assertTrue(isWhitelistedClass(className)); + } + Random rand = new Random(); + for (int i1 = 10; i1 > 0; i1--) + { + StringBuilder sb = new StringBuilder(); + sb.append(UUID.randomUUID().toString().replaceAll("-", "")); + + // random number of package directories + for (int i2 = rand.nextInt(5) + 1; i2 > 0; i2--) { + sb.append(".").append(UUID.randomUUID().toString().replaceAll("-", "")); + } + Assertions.assertFalse(isWhitelistedClass(sb.toString())); + } + } +} diff --git a/src/test/resources/classes/io/pzstorm/storm/SampleClass.class b/src/test/resources/classes/io/pzstorm/storm/SampleClass.class new file mode 100644 index 0000000000000000000000000000000000000000..94f6096b564296cec09f5d191d16aadc66310fc4 GIT binary patch literal 460 zcmaJ-%Syvg5IvKoiNuN8xm?V=9LM#n`L$Ar`WJXyXm7zcA1 z=7iR!we}>~OA!@v{7C5bM%%8!Icog0G7M080;A29$C)_SFWx|}tMLwFIR;C18QqEi c2YX!c8?ev#M{K^sc;TUejo8{MK3K6DKPPcup8x;= literal 0 HcmV?d00001 diff --git a/src/test/resources/delegate/delegate.resource b/src/test/resources/delegate/delegate.resource new file mode 100644 index 0000000..e69de29 From b6ab64b5409efc2b22ea94085aa6d4a998b78d15 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 23:55:02 +0200 Subject: [PATCH 021/323] Refactor StormClassLoader test class --- .../pzstorm/storm/StormClassLoaderTest.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java index c2815a0..3bbd86f 100644 --- a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java @@ -74,27 +74,35 @@ void shouldDefinePackageWhenPackageNameIsValid() { } @Test - @SuppressWarnings("unchecked") void shouldProperlyRecognizeWhitelistedClassNames() throws ReflectiveOperationException { - Field field = StormClassLoader.class.getDeclaredField("CLASS_WHITELIST"); - field.setAccessible(true); - - Set whitelist = (Set) field.get(null); - for (String className : whitelist) { + for (String className : getWhitelistedClasses()) { Assertions.assertTrue(isWhitelistedClass(className)); } Random rand = new Random(); for (int i1 = 10; i1 > 0; i1--) { StringBuilder sb = new StringBuilder(); - sb.append(UUID.randomUUID().toString().replaceAll("-", "")); + sb.append(getRandomString()); // random number of package directories for (int i2 = rand.nextInt(5) + 1; i2 > 0; i2--) { - sb.append(".").append(UUID.randomUUID().toString().replaceAll("-", "")); + sb.append(".").append(getRandomString()); } Assertions.assertFalse(isWhitelistedClass(sb.toString())); } } + + private static String getRandomString() { + return UUID.randomUUID().toString().replaceAll("-", ""); + } + + @SuppressWarnings("unchecked") + private static Set getWhitelistedClasses() throws ReflectiveOperationException { + + Field field = StormClassLoader.class.getDeclaredField("CLASS_WHITELIST"); + field.setAccessible(true); + + return (Set) field.get(null); + } } From 418438c12cf3a74bd9b952822aee94776d2c6075 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Thu, 22 Apr 2021 23:57:18 +0200 Subject: [PATCH 022/323] Test storm loading whitelisted classes --- .../pzstorm/storm/StormClassLoaderTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java index 3bbd86f..64c9f17 100644 --- a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java @@ -9,6 +9,7 @@ import java.util.*; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -23,6 +24,24 @@ class StormClassLoaderTest extends StormClassLoader implements UnitTest { super(new URL[] { CLASSES_RESOURCE_DIR, DELEGATE_RESOURCE_DIR }); } + @Test + void shouldLoadWhitelistedClassesWithStormClassLoader() throws ReflectiveOperationException { + + for (String whitelistedClass : getWhitelistedClasses()) { + Assertions.assertNull(findLoadedClass(whitelistedClass)); + } + ImmutableSet dummyGameClasses = ImmutableSet.of( + "fmod.FmodClass", "jassimp.JassimpLibraryClass", "javax.vecmath.MathClass", + "org.lwjgl.LwjglLibraryClass", "zombie.ZombieClass" + ); + for (String dummyGameClass : dummyGameClasses) + { + Class loadedClass = loadClass(dummyGameClass, true); + Assertions.assertEquals(loadedClass, findLoadedClass(dummyGameClass)); + Assertions.assertEquals(this, loadedClass.getClassLoader()); + } + } + @Test void shouldMatchRawClassByteArrayToClassFile() throws URISyntaxException, IOException { From 4743cdf648b458d79396b811bd382b93a48ae7cb Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Fri, 23 Apr 2021 01:35:36 +0200 Subject: [PATCH 023/323] Add compiled dummy classes as resources --- .../classes/com/dummy/PancakeClass.class | Bin 0 -> 281 bytes .../classes/com/dummy/PineappleClass.class | Bin 0 -> 287 bytes .../classes/com/dummy/StrawberryClass.class | Bin 0 -> 290 bytes src/test/resources/classes/fmod/FmodClass.class | Bin 0 -> 334 bytes .../classes/jassimp/JassimpLibraryClass.class | Bin 0 -> 298 bytes .../classes/javax/vecmath/MathClass.class | Bin 0 -> 475 bytes .../classes/org/lwjgl/LwjglLibraryClass.class | Bin 0 -> 296 bytes .../resources/classes/zombie/ZombieClass.class | Bin 0 -> 347 bytes 8 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/test/resources/classes/com/dummy/PancakeClass.class create mode 100644 src/test/resources/classes/com/dummy/PineappleClass.class create mode 100644 src/test/resources/classes/com/dummy/StrawberryClass.class create mode 100644 src/test/resources/classes/fmod/FmodClass.class create mode 100644 src/test/resources/classes/jassimp/JassimpLibraryClass.class create mode 100644 src/test/resources/classes/javax/vecmath/MathClass.class create mode 100644 src/test/resources/classes/org/lwjgl/LwjglLibraryClass.class create mode 100644 src/test/resources/classes/zombie/ZombieClass.class diff --git a/src/test/resources/classes/com/dummy/PancakeClass.class b/src/test/resources/classes/com/dummy/PancakeClass.class new file mode 100644 index 0000000000000000000000000000000000000000..d0d028b6c7ceb6f6de6be513d380a410099d0b8c GIT binary patch literal 281 zcmZvWyH3ME5Jm4eet=`Jp+K~Bkb-UKf@myJS%3seKi;fh6F($BBJo*NNECcPJ}P21 zqM%?#y4rhY?rOHXuWtZ1$YM-z6yZ2RN(gUlV~0D!>0Y?3 zgq#+w=4!#6b^aIn!<+31=Y?)-wH)jET|IH5d2RBF`+iP{pWCt1=H9wLPW}$BSG?f_ zJ_5qo|9L{@mQ=;ft9mR~Mi01vC+E8&X-SKQ;?&*)!R!=xifDkoYXBNGbRLJ_@l$ zX(+NQjdo^tM%we`_XJ>u&_@eB4-X#t1b3k`T`dW%(RfX;SNTQ}`jO7mYn{fb_>{3> zRXqD=P1cjv8mJaAl}(1cRQn2MwaEA;D6+Gk*F8lqzvxY)2-ag z05%-L(|;L+usIV(W?$kh-m0X+5Eeht6mhek(PbuX9B?K-pd$+wJVuvgu%XQu+-3(I L_{<1hMhn3e@Q*$} literal 0 HcmV?d00001 diff --git a/src/test/resources/classes/com/dummy/StrawberryClass.class b/src/test/resources/classes/com/dummy/StrawberryClass.class new file mode 100644 index 0000000000000000000000000000000000000000..1c30f85e90563242d5d273bb36329863ceb4be12 GIT binary patch literal 290 zcmZ`!O>4qX5S)$q(3sXBy@)4o>Y?w_TZ^TF8}!A31&`5X8Eoh>2G`j^ M4?Z(OpV30_2X1CS2LJ#7 literal 0 HcmV?d00001 diff --git a/src/test/resources/classes/fmod/FmodClass.class b/src/test/resources/classes/fmod/FmodClass.class new file mode 100644 index 0000000000000000000000000000000000000000..5883315dcaf4f8c6ba0e3c10c04e43c8ab33f38c GIT binary patch literal 334 zcmZ8byKcfj5S;asIOh2W>GF``CS4E`LZY%l0ir+KXEFh?rWxZK!CjSGoyeJLiu!+KUQ@9X z9!yw-!9QJuphzHz5Bg=pfWvCD4PI!ode^sw M4qPTem(_rG0h8ZDE&u=k literal 0 HcmV?d00001 diff --git a/src/test/resources/classes/javax/vecmath/MathClass.class b/src/test/resources/classes/javax/vecmath/MathClass.class new file mode 100644 index 0000000000000000000000000000000000000000..2076e0a5c081d4cb9bbc211867d4cf03a98ca662 GIT binary patch literal 475 zcmZ`#%T5A85Uk-fu&#h0B0dh@z=Lx!UNl}vOib{RK)4UfL7LCb{>pWJdh)wm|8FLq(79gw)l_CnTbvqTD_@U$S1K-ULegx+)(KHGD*%EoN;8~ zKqij_`q4c0)Rnf589s=+(NuY6jp{!q zMg{b(Xih>SeWB2ixQ}&c`X*K~px4z?k9C@X&uo()(mLALogt2pqvhF?0c{MNVv&%b z%qZuN-@#r|xkRy$l36%ZyT1+CpbAuPE{D%jGobj241Ys*HSdsOT{zv9M{(&jVQr{A_B|%?Q1_>4p%_n^#R3NK@;@Zkubj zR=H(oB%4CnjdI58lNokzrX}3S#+AZ;RHYTtQ<~f<_c^z!ZJ!9~s@b_hzZf4hKHGSx z)Vs1Pjt_+M*QI>CnhAPj5311O{dA=_aIZ-;Wb0vP+ zii2pufy)=&X0ccc!c=7Ia29W6Qegm#zoCx>el}dL9#0`7F}hFRpf9ubxq61Q(cr#y f7NCj1?C%Vt&Cj*jvOmq{;`FYZR_!F*8`Z-<1*AQu literal 0 HcmV?d00001 From f6c166df83e965fb6ff9e83459f6ab3f814209b7 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Fri, 23 Apr 2021 01:35:49 +0200 Subject: [PATCH 024/323] Test delegating storm class loading --- .../io/pzstorm/storm/StormClassLoaderTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java index 64c9f17..79854e9 100644 --- a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; @@ -42,6 +43,23 @@ void shouldLoadWhitelistedClassesWithStormClassLoader() throws ReflectiveOperati } } + @Test + void shouldDelegateLoadingNonWhitelistedClassesToParentClassLoader() throws ReflectiveOperationException { + + Method method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); + method.setAccessible(true); + + ImmutableSet dummyClasses = ImmutableSet.of( + "com.dummy.PancakeClass", "com.dummy.PineappleClass", "com.dummy.StrawberryClass" + ); + for (String dummyClass : dummyClasses) + { + Class loadedClass = loadClass(dummyClass, true); + Assertions.assertEquals(loadedClass, method.invoke(getParent(), dummyClass)); + Assertions.assertEquals(this.getParent(), loadedClass.getClassLoader()); + } + } + @Test void shouldMatchRawClassByteArrayToClassFile() throws URISyntaxException, IOException { From 837cced599edb7992c075f91bc47020ca76db446 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Fri, 23 Apr 2021 15:41:47 +0200 Subject: [PATCH 025/323] Expand project gitignore rules --- .gitignore | 6 +++--- .idea/.gitignore | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9445672..66f3b69 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,9 @@ version.properties ### Intellij ### # File-based project format - *.iml - *.ipr - *.iws +**/*.iml +**/*.ipr +**/*.iws # IntelliJ out/ diff --git a/.idea/.gitignore b/.idea/.gitignore index 12e280c..27c8e6b 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -16,5 +16,6 @@ gradle.xml /artifacts/ /modules/ compiler.xml +libraries-with-intellij-classes.xml jarRepositories.xml modules.xml From 2983511e63eb6e5ee429600e868aada9cbb6359a Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Fri, 23 Apr 2021 19:48:57 +0200 Subject: [PATCH 026/323] Remove and ignore IDEA search scopes --- .idea/.gitignore | 1 + .idea/scopes/pz_java.xml | 3 --- .idea/scopes/pz_lua.xml | 3 --- 3 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 .idea/scopes/pz_java.xml delete mode 100644 .idea/scopes/pz_lua.xml diff --git a/.idea/.gitignore b/.idea/.gitignore index 27c8e6b..5851056 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,6 +1,7 @@ # User-specific stuff /dictionaries/ /shelf/ +/scopes/ workspace.xml tasks.xml usage.statistics.xml diff --git a/.idea/scopes/pz_java.xml b/.idea/scopes/pz_java.xml deleted file mode 100644 index 552dbfd..0000000 --- a/.idea/scopes/pz_java.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/scopes/pz_lua.xml b/.idea/scopes/pz_lua.xml deleted file mode 100644 index d18fdac..0000000 --- a/.idea/scopes/pz_lua.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file From 667f34e85553b1db7656edb58f84c4b65d046f77 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Fri, 23 Apr 2021 20:22:02 +0200 Subject: [PATCH 027/323] Define IDEA project code style --- .idea/codeStyles/Project.xml | 40 ++++++++++++++++++++++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 ++++ 2 files changed, 45 insertions(+) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..527b8a1 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,40 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..307554b --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file From ce8f4d88318776195d399b4814975445027a3f2b Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Fri, 23 Apr 2021 23:05:39 +0200 Subject: [PATCH 028/323] Update capsid plugin configuration --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index cc67893..57e0d95 100644 --- a/build.gradle +++ b/build.gradle @@ -15,8 +15,7 @@ version '0.1.0' capsid { isModProject = false - setRepositoryOwner("pzstorm") - setRepositoryName("storm") + setProjectRepository('pzstorm', 'storm') } dependencies { From ae998636f1474bd9cb89ed9e9dcec2794a908c78 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Fri, 23 Apr 2021 23:57:03 +0200 Subject: [PATCH 029/323] Generate Launch Storm run configuration --- build.gradle | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/build.gradle b/build.gradle index 57e0d95..927f306 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,6 @@ +import io.pzstorm.capsid.setup.xml.LaunchRunConfig +import io.pzstorm.capsid.setup.VmParameter + plugins { id 'io.pzstorm.capsid' version '0.2.0' @@ -42,3 +45,17 @@ test { useJUnitPlatform() testLogging.events "passed", "skipped", "failed" } + +tasks.createLaunchRunConfigs.configure { + actions.clear() + doLast { + //@formatter:off + LaunchRunConfig launchStorm = new LaunchRunConfig( + "Launch Storm", "io.pzstorm.storm.StormLauncher", + new VmParameter.Builder().build(), [ + Main: new File((gameDir as File), "logs/main.log").toPath() , + Debug: new File((gameDir as File), "logs/debug.log").toPath()] as Map + )//@formatter:on + launchStorm.configure(getProject()).writeToFile() + } +} \ No newline at end of file From b912c8865ca079213bcb3c0f912ef9ebf81fcc3f Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 01:59:53 +0200 Subject: [PATCH 030/323] Remove redundant distribution files --- src/main/dist/.idea/scopes/pz_java.xml | 3 --- src/main/dist/.idea/scopes/pz_lua.xml | 3 --- 2 files changed, 6 deletions(-) delete mode 100644 src/main/dist/.idea/scopes/pz_java.xml delete mode 100644 src/main/dist/.idea/scopes/pz_lua.xml diff --git a/src/main/dist/.idea/scopes/pz_java.xml b/src/main/dist/.idea/scopes/pz_java.xml deleted file mode 100644 index 552dbfd..0000000 --- a/src/main/dist/.idea/scopes/pz_java.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/main/dist/.idea/scopes/pz_lua.xml b/src/main/dist/.idea/scopes/pz_lua.xml deleted file mode 100644 index d18fdac..0000000 --- a/src/main/dist/.idea/scopes/pz_lua.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file From 430c9a34c47f09dff3e8380862f2fea72682a4b3 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 02:36:33 +0200 Subject: [PATCH 031/323] Ignore IDEA run configurations They are now generated with gradle task. --- .idea/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/.gitignore b/.idea/.gitignore index 5851056..6265ce5 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -16,6 +16,7 @@ gradle.xml # Other stuff /artifacts/ /modules/ +/runConfigurations/ compiler.xml libraries-with-intellij-classes.xml jarRepositories.xml From f8c78ea55f537a6593de059ac6327b3684799a57 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 02:37:24 +0200 Subject: [PATCH 032/323] Change launch run config log directory --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 927f306..1e2cd82 100644 --- a/build.gradle +++ b/build.gradle @@ -53,8 +53,8 @@ tasks.createLaunchRunConfigs.configure { LaunchRunConfig launchStorm = new LaunchRunConfig( "Launch Storm", "io.pzstorm.storm.StormLauncher", new VmParameter.Builder().build(), [ - Main: new File((gameDir as File), "logs/main.log").toPath() , - Debug: new File((gameDir as File), "logs/debug.log").toPath()] as Map + Main: new File((gameDir as File), "logs/storm/main.log").toPath() , + Debug: new File((gameDir as File), "logs/storm/debug.log").toPath()] as Map )//@formatter:on launchStorm.configure(getProject()).writeToFile() } From 27171ba24399ec8df63fba1eb021af115c4c9322 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 02:37:51 +0200 Subject: [PATCH 033/323] Add Log4j2 project dependency --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 1e2cd82..ba053f6 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,9 @@ dependencies { // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.1' + // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core + implementation 'org.apache.logging.log4j:log4j-core:2.14.0' + // https://mvnrepository.com/artifact/com.google.guava/guava implementation 'com.google.guava:guava:30.1-jre' From e902b576caf2e86ff0973e01fd6a10fdc227ca6c Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 03:20:23 +0200 Subject: [PATCH 034/323] Create logger implementation --- .../java/io/pzstorm/storm/StormLogger.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/StormLogger.java diff --git a/src/main/java/io/pzstorm/storm/StormLogger.java b/src/main/java/io/pzstorm/storm/StormLogger.java new file mode 100644 index 0000000..ea2a117 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/StormLogger.java @@ -0,0 +1,106 @@ +package io.pzstorm.storm; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; + +/** + *

Simple wrapper class for logging with Log4j 2 logger. + * To configure root logger level launch Storm with {@code JVM_PROPERTY} set to a custom logger level. + *

+ * Logs will automatically be printed to console and configured log files. + * Check {@code log4j2.xml} for log file locations. + *

+ * Use the {@code static} methods to print logs with desired log level. + * If you want to print with a log level not covered by {@code static} methods + * use {@link #get()} method to get a reference to logger instance. + */ +@SuppressWarnings({ "unused", "WeakerAccess" }) +public class StormLogger { + + public static final Level VERBOSE = Level.forName("VERBOSE", 450); + private static final String JVM_PROPERTY = "storm.logger"; + private static final org.apache.logging.log4j.Logger LOGGER; + + static + { + LOGGER = LogManager.getLogger("Storm"); + String sLevel = System.getProperty("storm.logger"); + if (sLevel != null && sLevel.isEmpty()) + { + Level level = Level.getLevel(sLevel); + if (level != null) + { + Configurator.setLevel("Storm", level); + LOGGER.debug("Setting custom level for Storm logger '" + sLevel + '\''); + } + else LOGGER.error("Unable to resolve logging level '" + sLevel + '\''); + } + LOGGER.debug("Initialized Storm logger"); + } + + /* Make the constructor private to disable instantiation */ + private StormLogger() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an instance of Log4j {@link Logger} used for logging. + */ + public static Logger get() { + return LOGGER; + } + + public static void info(String log) { + LOGGER.info(log); + } + + public static void info(String log, Object... params) { + LOGGER.printf(Level.INFO, log, params); + } + + public static void detail(String log) { + LOGGER.log(VERBOSE, log); + } + + public static void detail(String log, Object... params) { + LOGGER.printf(VERBOSE, log, params); + } + + public static void error(String log) { + LOGGER.error(log); + } + + public static void error(String log, Object... args) { + LOGGER.printf(Level.ERROR, log, args); + } + + public static void error(String log, Throwable t) { + LOGGER.error(log, t); + } + + public static void warn(String log) { + LOGGER.warn(log); + } + + public static void warn(String format, Object... params) { + LOGGER.printf(Level.WARN, format, params); + } + + public static void debug(String log) { + LOGGER.debug(log); + } + + public static void debug(String format, Object... args) { + LOGGER.printf(Level.DEBUG, format, args); + } + + public static void debug(String log, Throwable t) { + LOGGER.debug(log, t); + } + + public static void printf(Level level, String format, Object... params) { + LOGGER.printf(level, format, params); + } +} From 4bd04004a743ce77806d4b0d396b1f42d6839395 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 03:20:58 +0200 Subject: [PATCH 035/323] Configure log4j2 implementation --- src/main/resources/log4j2.xml | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/resources/log4j2.xml diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..37ca2b9 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,45 @@ + + + + + %level{VERBOSE=INFO} + + + logs/storm/$${date:yyyy-MM}/pz-storm-%d{MM-dd-yyyy} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From a5653a2cd667a7a164e3fb8469128bb41e08a77c Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 03:27:01 +0200 Subject: [PATCH 036/323] Start logging with StormLogger --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 11 +++++------ src/main/java/io/pzstorm/storm/StormLauncher.java | 7 ++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 09178ae..44d7b98 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -12,8 +12,6 @@ import com.google.common.collect.ImmutableSet; -import zombie.debug.DebugLog; - /** * This is a custom {@code ClassLoader} used to define, transform and load Project Zomboid classes. * It is initially invoked by {@link StormLauncher} when launching the game. @@ -60,6 +58,7 @@ protected StormClassLoader(URL[] resourceLocations) { } protected StormClassLoader() { + StormLogger.debug("Initialized StormClassLoader"); parentClassLoader = getClass().getClassLoader(); resourceClassLoader = (URLClassLoader) getParent(); } @@ -139,10 +138,10 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot Class clazz = findLoadedClass(name); if (clazz == null) { - DebugLog.General.debugln("STORM: Preparing to load class " + name); + StormLogger.debug("Preparing to load class " + name); if (isWhitelistedClass(name)) { - DebugLog.General.debugln("STORM: Loading with StormClassLoader"); + StormLogger.debug("Loading with StormClassLoader"); try { byte[] input = getRawClassByteArray(name); if (input.length > 0) @@ -152,7 +151,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot clazz = defineClass(name, input, 0, input.length); if (clazz.getClassLoader() == this) { - DebugLog.General.debugln("STORM: Successfully loaded class with StormClassLoader"); + StormLogger.debug("STORM: Successfully loaded class with StormClassLoader"); } else throw new RuntimeException("Unable to load class with StormClassLoader"); } @@ -165,7 +164,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot // if the class is not whitelisted delegate loading to parent class loader if (clazz == null) { - DebugLog.General.debugln("STORM: Loading with application class loader"); + StormLogger.debug("Loading with application class loader"); clazz = parentClassLoader.loadClass(name); } if (resolve) { diff --git a/src/main/java/io/pzstorm/storm/StormLauncher.java b/src/main/java/io/pzstorm/storm/StormLauncher.java index f3d1c71..4109658 100644 --- a/src/main/java/io/pzstorm/storm/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/StormLauncher.java @@ -2,8 +2,6 @@ import java.lang.reflect.Method; -import zombie.debug.DebugLog; - class StormLauncher { /** @@ -27,18 +25,21 @@ class StormLauncher { */ public static void main(String[] args) throws ReflectiveOperationException { + StormLogger.debug("Preparing to launch Project Zomboid"); StormClassLoader stormLoader = new StormClassLoader(); + Class entryPointClass = stormLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); Method entryPoint = entryPointClass.getMethod(ZOMBOID_ENTRY_POINT, String[].class); try { /* we invoke the entry point using reflection because we don't want to reference * the entry point class which would to the class being loaded by application class loader */ + StormLogger.debug("Launching Project Zomboid..."); entryPoint.invoke(null, (Object) args); } catch (Throwable e) { - DebugLog.General.error("An unhandled exception occurred while running Project Zomboid"); + StormLogger.error("An unhandled exception occurred while running Project Zomboid"); throw new RuntimeException(e); } } From 9519f6be4fd15a4f8d8e91188eb09681bd85302c Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 03:35:35 +0200 Subject: [PATCH 037/323] Bump guava dependency version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ba053f6..665f69c 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ dependencies { implementation 'org.apache.logging.log4j:log4j-core:2.14.0' // https://mvnrepository.com/artifact/com.google.guava/guava - implementation 'com.google.guava:guava:30.1-jre' + implementation 'com.google.guava:guava:30.1.1-jre' // https://mvnrepository.com/artifact/org.jetbrains/annotations compileOnly 'org.jetbrains:annotations:20.1.0' From fc19b93275439555f03e914ef89f8e989bf05fd8 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 03:40:03 +0200 Subject: [PATCH 038/323] Ignore only launch run configurations --- .idea/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/.gitignore b/.idea/.gitignore index 6265ce5..9292ba8 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -16,7 +16,7 @@ gradle.xml # Other stuff /artifacts/ /modules/ -/runConfigurations/ +/runConfigurations/Launch_* compiler.xml libraries-with-intellij-classes.xml jarRepositories.xml From 5ee1d44b304fcc5743d0daa3e4a3eff815f919e4 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 03:42:58 +0200 Subject: [PATCH 039/323] Add unit test run configuration --- .idea/runConfigurations/Run_Unit_Tests.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .idea/runConfigurations/Run_Unit_Tests.xml diff --git a/.idea/runConfigurations/Run_Unit_Tests.xml b/.idea/runConfigurations/Run_Unit_Tests.xml new file mode 100644 index 0000000..1fc729b --- /dev/null +++ b/.idea/runConfigurations/Run_Unit_Tests.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file From 3398b97a7425047e96fa9c550ee14d2e2b1908e8 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 04:06:47 +0200 Subject: [PATCH 040/323] Recompile test resource classes Tests were failing to find classes. --- .../classes/com/dummy/PancakeClass.class | Bin 281 -> 285 bytes .../classes/com/dummy/PineappleClass.class | Bin 287 -> 291 bytes .../classes/com/dummy/StrawberryClass.class | Bin 290 -> 294 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/test/resources/classes/com/dummy/PancakeClass.class b/src/test/resources/classes/com/dummy/PancakeClass.class index d0d028b6c7ceb6f6de6be513d380a410099d0b8c..69c6d3b44acdaa6b1a4539718e4b0c04e2c29695 100644 GIT binary patch delta 26 gcmbQqG?!_@eMaMn55%~+7?>Cs7+8RiZQ@@U0AUygGynhq delta 23 fcmbQsG?Qt55(Bn7#JBC7?>wBGRgn|QECMv diff --git a/src/test/resources/classes/com/dummy/PineappleClass.class b/src/test/resources/classes/com/dummy/PineappleClass.class index df5a193f685a2686d03a74eade0ef75a4c0dc21f..e3edd29cd98401da4c63211cd193d3e5e6f4018c 100644 GIT binary patch delta 69 zcmbQww3unaQ#)e@Mj&KhVAa~rz_<~}U}9hgl3YNVfq?}G*%&y0EFOp=22P+11CY%P IB$*g^04k9MF#rGn delta 65 zcmZ3?G@oh0Q!9N2Mj&KhVAa~rz_<~}U}9hgl5Ai>W(E!*n+KwRfs=s?$YW&S2CL!$ E025IK82|tP diff --git a/src/test/resources/classes/com/dummy/StrawberryClass.class b/src/test/resources/classes/com/dummy/StrawberryClass.class index 1c30f85e90563242d5d273bb36329863ceb4be12..78611c862faff17b97598cc0cf9f7d12c1a17695 100644 GIT binary patch delta 69 zcmZ3)w2W!O3p-;5Mj&KhVAa~rz_<~}U}9hgl3YNVfq?}G*%&y0EFOp=22P+11CY%P IB$*g^04(VRIsgCw delta 41 pcmZ3+w1{cK3psrTMj&KhVAa~rz_<~}U}9hgl5Ai>=E+=)G60*~1$h7f From 6843206de2c3e36e205a0bd6901b1abd784d8f33 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 06:18:20 +0200 Subject: [PATCH 041/323] Bump capsid plugin version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 665f69c..1cbf1d0 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ import io.pzstorm.capsid.setup.xml.LaunchRunConfig import io.pzstorm.capsid.setup.VmParameter plugins { - id 'io.pzstorm.capsid' version '0.2.0' + id 'io.pzstorm.capsid' version '0.3.0' // Plugin that keeps your code spotless with Gradle // https://plugins.gradle.org/plugin/com.diffplug.spotless From 1acd5298eae5d0f2483fbd08fdfaf2a9ebf3dc62 Mon Sep 17 00:00:00 2001 From: Matthew Cain Date: Sat, 24 Apr 2021 06:38:19 +0200 Subject: [PATCH 042/323] Fix class delegation test failing Unknown cause as to why the test was failing. --- .../io/pzstorm/storm/StormClassLoaderTest.java | 3 ++- .../classes/com/dummy/PancakeClass.class | Bin 285 -> 0 bytes .../classes/com/dummy/PineappleClass.class | Bin 291 -> 0 bytes .../classes/com/dummy/StrawberryClass.class | Bin 294 -> 0 bytes 4 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 src/test/resources/classes/com/dummy/PancakeClass.class delete mode 100644 src/test/resources/classes/com/dummy/PineappleClass.class delete mode 100644 src/test/resources/classes/com/dummy/StrawberryClass.class diff --git a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java index 79854e9..ee20f57 100644 --- a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java @@ -50,7 +50,8 @@ void shouldDelegateLoadingNonWhitelistedClassesToParentClassLoader() throws Refl method.setAccessible(true); ImmutableSet dummyClasses = ImmutableSet.of( - "com.dummy.PancakeClass", "com.dummy.PineappleClass", "com.dummy.StrawberryClass" + "com.google.common.io.Files", "com.google.common.util.concurrent.Atomics", + "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.Level" ); for (String dummyClass : dummyClasses) { diff --git a/src/test/resources/classes/com/dummy/PancakeClass.class b/src/test/resources/classes/com/dummy/PancakeClass.class deleted file mode 100644 index 69c6d3b44acdaa6b1a4539718e4b0c04e2c29695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285 zcmZvWy>7xl5QJxq!NfQ~C{WsTfg&!F4n%`QWhIe<=-0<7Iq;9 z7g11fC(Yi>e!IHgzwaLaFX;Pd;MPOeLyyp!n#@!)LgR6?BDnK>tqHxzWcsyEV_hsc zP9zybx#DESg|Yq|y49B{3HOo8({Np<={|htOz~FF6E4dM!T-qXLg|IEJMJ!Z>@$3eH(FJAfFs|xhvdq%XiMDlzXzJ*Bj_N315ebK3c*}~+VTnn O69_lbu`4>F4uTV9G&_s{ diff --git a/src/test/resources/classes/com/dummy/PineappleClass.class b/src/test/resources/classes/com/dummy/PineappleClass.class deleted file mode 100644 index e3edd29cd98401da4c63211cd193d3e5e6f4018c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 291 zcmZ`!yH3ME5S(?KI5>txq(O9akOCLb1*M5ZWdTy6^k?S;7oF|M4W0h PL>o;w;lxD@h#sOJ>w-R0 diff --git a/src/test/resources/classes/com/dummy/StrawberryClass.class b/src/test/resources/classes/com/dummy/StrawberryClass.class deleted file mode 100644 index 78611c862faff17b97598cc0cf9f7d12c1a17695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmZ{fzfQw27{u>O)24(#E7Yax#0Ct_P&N=trA`qHMctj$0$2GXIZ3NN7E`4TJOB@c zdPYo0*wWc|-_Ixc>-+c#;2y&e9b5#s3@{M%9!+VQCqd`tb|vuV)mjS%i7EASTcp~) zsx)VEl~kF^SIQdae?z}{H??4rWK|Kb+oIUV%f_l5pV|F9S9Sd$2$xlBGrcgbX7p!1 z-KedK;G-)T|Jx!MIxfyt`4+#V8=W<{hR1)nCEP5a_UL9m9_Zd3ARi1o1k{ixi1~#2 RyhX$a;fxbkaZdFRod9roKpOx6 From 3565219eaa4b18a543018b732b47bcee5893b84d Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 27 Apr 2021 12:03:51 +0200 Subject: [PATCH 043/323] Ignore IDEA project name file --- .idea/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/.gitignore b/.idea/.gitignore index 9292ba8..e070dc0 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -21,3 +21,4 @@ compiler.xml libraries-with-intellij-classes.xml jarRepositories.xml modules.xml +.name From 35833f08b1330692f16d454fb8f8544e5abaab62 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 27 Apr 2021 12:04:46 +0200 Subject: [PATCH 044/323] Fix logger initialization code --- src/main/java/io/pzstorm/storm/StormLogger.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/StormLogger.java b/src/main/java/io/pzstorm/storm/StormLogger.java index ea2a117..4c134cf 100644 --- a/src/main/java/io/pzstorm/storm/StormLogger.java +++ b/src/main/java/io/pzstorm/storm/StormLogger.java @@ -20,14 +20,13 @@ public class StormLogger { public static final Level VERBOSE = Level.forName("VERBOSE", 450); - private static final String JVM_PROPERTY = "storm.logger"; private static final org.apache.logging.log4j.Logger LOGGER; static { LOGGER = LogManager.getLogger("Storm"); String sLevel = System.getProperty("storm.logger"); - if (sLevel != null && sLevel.isEmpty()) + if (sLevel != null && !sLevel.isEmpty()) { Level level = Level.getLevel(sLevel); if (level != null) From de247b61405e2d79f6805c980e829304cd7d94ee Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 27 Apr 2021 12:07:39 +0200 Subject: [PATCH 045/323] Test setting storm logger level --- .../io/pzstorm/storm/StormLoggerTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/StormLoggerTest.java diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/StormLoggerTest.java new file mode 100644 index 0000000..9752c1c --- /dev/null +++ b/src/test/java/io/pzstorm/storm/StormLoggerTest.java @@ -0,0 +1,29 @@ +package io.pzstorm.storm; + +import org.apache.logging.log4j.Level; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +class StormLoggerTest implements UnitTest { + + private static final String LOGGER_PROPERTY = "storm.logger"; + + @Test + void shouldSetStormLoggerLevelFromSystemProperties() { + + // assert that key does not exist first + Assertions.assertNull(System.getProperty(LOGGER_PROPERTY)); + + Level level = Level.forName("CUSTOM_LEVEL", 1); + System.setProperty(LOGGER_PROPERTY, level.name()); + + // assert that system property was properly set + String systemProperty = System.getProperty(LOGGER_PROPERTY); + Assertions.assertEquals(level.name(), systemProperty); + + // assert that logger has correct level + Assertions.assertEquals(level, StormLogger.get().getLevel()); + } +} From 3f0d6e83bfb314d29e0dd178a67f437626d9c287 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 27 Apr 2021 20:35:37 +0200 Subject: [PATCH 046/323] Grant gradlew execution permissions --- gradlew | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From cf142fccbed3dc7d43d58ecd3d606f6ddbb99535 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 15:59:49 +0200 Subject: [PATCH 047/323] Add launch run config Linux support --- build.gradle | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 1cbf1d0..fbf770e 100644 --- a/build.gradle +++ b/build.gradle @@ -52,13 +52,20 @@ test { tasks.createLaunchRunConfigs.configure { actions.clear() doLast { + def vmParameterBuilder = new VmParameter.Builder() + def os = org.gradle.internal.os.OperatingSystem.current() + if (os == org.gradle.internal.os.OperatingSystem.LINUX) { + vmParameterBuilder = vmParameterBuilder + .withJavaLibraryPaths(gameDir as String, "${gameDir}/linux64" as String, "${gameDir}/jre64/lib/amd64" as String) + .withLwjglLibraryPaths(gameDir) + } //@formatter:off LaunchRunConfig launchStorm = new LaunchRunConfig( "Launch Storm", "io.pzstorm.storm.StormLauncher", - new VmParameter.Builder().build(), [ - Main: new File((gameDir as File), "logs/storm/main.log").toPath() , - Debug: new File((gameDir as File), "logs/storm/debug.log").toPath()] as Map + vmParameterBuilder.build(), [ + Main: new File(file(gameDir), "logs/storm/main.log").toPath() , + Debug: new File(file(gameDir), "logs/storm/debug.log").toPath()] as Map )//@formatter:on launchStorm.configure(getProject()).writeToFile() } -} \ No newline at end of file +} From 161a1dbd5bafb15468fd24f06f4e7cb096b54589 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:01:08 +0200 Subject: [PATCH 048/323] Change dependency declaration format --- build.gradle | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index fbf770e..65d5986 100644 --- a/build.gradle +++ b/build.gradle @@ -23,17 +23,14 @@ capsid { dependencies { // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.1' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.1' // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core implementation 'org.apache.logging.log4j:log4j-core:2.14.0' - // https://mvnrepository.com/artifact/com.google.guava/guava - implementation 'com.google.guava:guava:30.1.1-jre' - // https://mvnrepository.com/artifact/org.jetbrains/annotations compileOnly 'org.jetbrains:annotations:20.1.0' @@ -42,6 +39,10 @@ dependencies { // https://mvnrepository.com/artifact/com.google.errorprone/javac errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') + + // https://mvnrepository.com/artifact/com.google.guava/guava + implementation 'com.google.guava:guava:30.1.1-jre' + } test { From b59306960781041dbb185187193d499870e401fc Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:23:10 +0200 Subject: [PATCH 049/323] Ignore swing designer config file --- .idea/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/.gitignore b/.idea/.gitignore index e070dc0..b5a6551 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -8,6 +8,7 @@ usage.statistics.xml # Generated files contentModel.xml +uiDesigner.xml # Gradle /libraries/ From d8732a26fc3dd05a37f9e7bf175393dd310e87c1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:23:32 +0200 Subject: [PATCH 050/323] Create NonNullPackage annotation --- .../java/io/pzstorm/storm/NonNullPackage.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/NonNullPackage.java diff --git a/src/main/java/io/pzstorm/storm/NonNullPackage.java b/src/main/java/io/pzstorm/storm/NonNullPackage.java new file mode 100644 index 0000000..37e7fe2 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/NonNullPackage.java @@ -0,0 +1,15 @@ +package io.pzstorm.storm; + +import javax.annotation.Nonnull; +import javax.annotation.meta.TypeQualifierDefault; +import javax.annotation.meta.TypeQualifierNickname; +import java.lang.annotation.*; + +@Documented +@TypeQualifierNickname +@TypeQualifierDefault({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER }) +@Nonnull +@Target(ElementType.PACKAGE) +@Retention(RetentionPolicy.CLASS) +public @interface NonNullPackage { +} \ No newline at end of file From 27e9b865adb3e78de66c787002d411512e22eb99 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:24:01 +0200 Subject: [PATCH 051/323] Annotate package as NonNull --- src/main/java/io/pzstorm/storm/package-info.java | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/package-info.java diff --git a/src/main/java/io/pzstorm/storm/package-info.java b/src/main/java/io/pzstorm/storm/package-info.java new file mode 100644 index 0000000..be56d48 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/package-info.java @@ -0,0 +1,2 @@ +@NonNullPackage +package io.pzstorm.storm; \ No newline at end of file From 21b5dcd9eeebdfdb177dd54360a09d4d2d188649 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:24:50 +0200 Subject: [PATCH 052/323] Remove unneeded contract annotations --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 44d7b98..93e0323 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -67,13 +67,11 @@ protected StormClassLoader() { * Returns {@code true} if at least one prefix pattern in whitelist matches the given name. * When a class name is considered whitelisted it will be loaded by this {@code ClassLoader}. */ - @Contract("null -> fail") protected static boolean isWhitelistedClass(String name) { return CLASS_WHITELIST.stream().anyMatch(name::startsWith); } @Override - @Contract("null -> fail") public @Nullable URL getResource(String name) { Objects.requireNonNull(name); @@ -98,7 +96,6 @@ protected static boolean isWhitelistedClass(String name) { * @throws IllegalArgumentException if package name duplicates an existing * package either in this class loader or one of its ancestors */ - @Contract("null -> fail") protected @Nullable Package definePackageForName(String name) { int pkgDelimiterPos = name.lastIndexOf('.'); @@ -186,7 +183,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot /** * Converts the given class name to a file name. */ - @Contract(pure = true, value = "null -> fail") + @Contract(pure = true) private String getClassFileName(String name) { return name.replace('.', '/') + ".class"; } @@ -200,7 +197,6 @@ private String getClassFileName(String name) { * * @throws IOException if an I/O error occurred while reading or writing to stream. */ - @Contract("null -> fail") protected byte[] getRawClassByteArray(String name) throws IOException { // opens an input stream to read the class for given name From 8c421372ad60309438987e03f4f40b578d210379 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:25:11 +0200 Subject: [PATCH 053/323] Suppress spell checking warning --- src/main/java/io/pzstorm/storm/StormClassLoader.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/StormClassLoader.java index 93e0323..ce7dcc5 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/StormClassLoader.java @@ -29,6 +29,7 @@ public class StormClassLoader extends ClassLoader { * If the native libraries are loaded using a different class loader they * will not be accessible to game classes. */ + @SuppressWarnings("SpellCheckingInspection") private static final ImmutableSet CLASS_WHITELIST = ImmutableSet.of( // zomboid library classes "org.lwjgl.", "net.java.games.", "jassimp.", From eba0f897c5724d4f1b780f98a584f7eefc107e81 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:51:57 +0200 Subject: [PATCH 054/323] Move and encapsulate core classes --- .../storm/{ => core}/StormClassLoader.java | 19 ++++++++++++++----- .../storm/{ => core}/StormLauncher.java | 4 +++- .../{ => core}/StormClassLoaderTest.java | 8 +++++--- 3 files changed, 22 insertions(+), 9 deletions(-) rename src/main/java/io/pzstorm/storm/{ => core}/StormClassLoader.java (95%) rename src/main/java/io/pzstorm/storm/{ => core}/StormLauncher.java (95%) rename src/test/java/io/pzstorm/storm/{ => core}/StormClassLoaderTest.java (98%) diff --git a/src/main/java/io/pzstorm/storm/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java similarity index 95% rename from src/main/java/io/pzstorm/storm/StormClassLoader.java rename to src/main/java/io/pzstorm/storm/core/StormClassLoader.java index ce7dcc5..56d103c 100644 --- a/src/main/java/io/pzstorm/storm/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -1,4 +1,4 @@ -package io.pzstorm.storm; +package io.pzstorm.storm.core; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -12,12 +12,14 @@ import com.google.common.collect.ImmutableSet; +import io.pzstorm.storm.StormLogger; + /** * This is a custom {@code ClassLoader} used to define, transform and load Project Zomboid classes. * It is initially invoked by {@link StormLauncher} when launching the game. */ @SuppressWarnings("WeakerAccess") -public class StormClassLoader extends ClassLoader { +class StormClassLoader extends ClassLoader { /** *

{@code Set} of class prefixes that mark classes to be loaded with this class loader. @@ -39,14 +41,13 @@ public class StormClassLoader extends ClassLoader { "javax.vecmath.", "org.joml.", "org.luaj.kahluafork.compiler.", "org.mindrot.jbcrypt.", "se.krka.kahlua.", "zombie." ); - + protected final URLClassLoader resourceClassLoader; /** * {@code ClassLoader} that is the parent of this {@code ClassLoader}. * When loading classes, those classes not matching the whitelist pattern will have * their loading process delegated to this {@code ClassLoader}. */ private final ClassLoader parentClassLoader; - protected final URLClassLoader resourceClassLoader; /** * Create {@code StormClassLoader} with additional locations to search for resources. @@ -54,11 +55,13 @@ public class StormClassLoader extends ClassLoader { * @param resourceLocations the URLs from which to load classes and resources. */ protected StormClassLoader(URL[] resourceLocations) { + parentClassLoader = getClass().getClassLoader(); resourceClassLoader = new URLClassLoader(resourceLocations, getParent()); } protected StormClassLoader() { + StormLogger.debug("Initialized StormClassLoader"); parentClassLoader = getClass().getClassLoader(); resourceClassLoader = (URLClassLoader) getParent(); @@ -142,6 +145,11 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot StormLogger.debug("Loading with StormClassLoader"); try { byte[] input = getRawClassByteArray(name); + + StormClassTransformer transformer = StormClassTransformer.getRegistered(name); + if (transformer != null) { + input = transformer.read(input).visit().transform(); + } if (input.length > 0) { // package has to be created before we define the class @@ -155,7 +163,8 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot } } catch (IOException e) { - throw new RuntimeException("I/O exception occurred while transforming class to byte array"); + throw new RuntimeException("I/O exception occurred while transforming class to byte " + + "array"); } } } diff --git a/src/main/java/io/pzstorm/storm/StormLauncher.java b/src/main/java/io/pzstorm/storm/core/StormLauncher.java similarity index 95% rename from src/main/java/io/pzstorm/storm/StormLauncher.java rename to src/main/java/io/pzstorm/storm/core/StormLauncher.java index 4109658..15dc475 100644 --- a/src/main/java/io/pzstorm/storm/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/core/StormLauncher.java @@ -1,7 +1,9 @@ -package io.pzstorm.storm; +package io.pzstorm.storm.core; import java.lang.reflect.Method; +import io.pzstorm.storm.StormLogger; + class StormLauncher { /** diff --git a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java similarity index 98% rename from src/test/java/io/pzstorm/storm/StormClassLoaderTest.java rename to src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java index ee20f57..f8601e6 100644 --- a/src/test/java/io/pzstorm/storm/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java @@ -1,4 +1,4 @@ -package io.pzstorm.storm; +package io.pzstorm.storm.core; import java.io.File; import java.io.IOException; @@ -9,11 +9,13 @@ import java.nio.file.Files; import java.util.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import io.pzstorm.storm.UnitTest; class StormClassLoaderTest extends StormClassLoader implements UnitTest { From e971f5aed1326932e94265e5163daa4bf19c5fa5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 16:52:25 +0200 Subject: [PATCH 055/323] Do minor spotless formatting --- src/main/java/io/pzstorm/storm/NonNullPackage.java | 5 +++-- src/main/java/io/pzstorm/storm/package-info.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/NonNullPackage.java b/src/main/java/io/pzstorm/storm/NonNullPackage.java index 37e7fe2..8e5ec16 100644 --- a/src/main/java/io/pzstorm/storm/NonNullPackage.java +++ b/src/main/java/io/pzstorm/storm/NonNullPackage.java @@ -1,9 +1,10 @@ package io.pzstorm.storm; +import java.lang.annotation.*; + import javax.annotation.Nonnull; import javax.annotation.meta.TypeQualifierDefault; import javax.annotation.meta.TypeQualifierNickname; -import java.lang.annotation.*; @Documented @TypeQualifierNickname @@ -12,4 +13,4 @@ @Target(ElementType.PACKAGE) @Retention(RetentionPolicy.CLASS) public @interface NonNullPackage { -} \ No newline at end of file +} diff --git a/src/main/java/io/pzstorm/storm/package-info.java b/src/main/java/io/pzstorm/storm/package-info.java index be56d48..7693995 100644 --- a/src/main/java/io/pzstorm/storm/package-info.java +++ b/src/main/java/io/pzstorm/storm/package-info.java @@ -1,2 +1,2 @@ @NonNullPackage -package io.pzstorm.storm; \ No newline at end of file +package io.pzstorm.storm; From 9d07a2a8255ea14615cbcdccdd59a522927db6cf Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 18:09:59 +0200 Subject: [PATCH 056/323] Add ASM 9.1 dependencies --- build.gradle | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/build.gradle b/build.gradle index 65d5986..38c5d40 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,20 @@ dependencies { // https://mvnrepository.com/artifact/com.google.guava/guava implementation 'com.google.guava:guava:30.1.1-jre' + // https://mvnrepository.com/artifact/org.ow2.asm/asm + implementation 'org.ow2.asm:asm:9.1' + + // https://mvnrepository.com/artifact/org.ow2.asm/asm-commons + implementation 'org.ow2.asm:asm-commons:9.1' + + // https://mvnrepository.com/artifact/org.ow2.asm/asm-tree + implementation 'org.ow2.asm:asm-tree:9.1' + + // https://mvnrepository.com/artifact/org.ow2.asm/asm-util + implementation 'org.ow2.asm:asm-util:9.1' + + // https://mvnrepository.com/artifact/org.ow2.asm/asm-analysis + implementation 'org.ow2.asm:asm-analysis:9.1' } test { From b1d76c2ac78f18e2ed8d886402499f792c5bbb03 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 18:10:06 +0200 Subject: [PATCH 057/323] Create StormClassTransformer class --- .../pzstorm/storm/core/StormClassLoader.java | 3 +- .../storm/core/StormClassTransformer.java | 167 ++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/pzstorm/storm/core/StormClassTransformer.java diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index 56d103c..0062d1f 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -71,6 +71,7 @@ protected StormClassLoader() { * Returns {@code true} if at least one prefix pattern in whitelist matches the given name. * When a class name is considered whitelisted it will be loaded by this {@code ClassLoader}. */ + @Contract(pure = true) protected static boolean isWhitelistedClass(String name) { return CLASS_WHITELIST.stream().anyMatch(name::startsWith); } @@ -148,7 +149,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot StormClassTransformer transformer = StormClassTransformer.getRegistered(name); if (transformer != null) { - input = transformer.read(input).visit().transform(); + input = transformer.transform(input); } if (input.length > 0) { diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java new file mode 100644 index 0000000..356d048 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -0,0 +1,167 @@ +package io.pzstorm.storm.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; + +import io.pzstorm.storm.StormLogger; + +/** + * This class represents a {@code Class} transformer used to alter {@code Class} + * bytecode using ASM. When new instance of {@code StormClassTransformer} is created + * it is automatically mapped in internal registry. To check if transformer is registered + * use {@link #isRegistered(String)} and to retrieve a mapped instance use {@link #getRegistered(String)}. + * The transformation process calls the following method in fixed order: + *

    + *
  • {@link #read(byte[])} to read given byte array with {@link ClassReader}.
  • + *
  • {@link #visit()} to use the visitation system to parse the class with visitor.
  • + *
  • {@link #transform()} to transform the visited class before writing it.
  • + *
  • {@link #write()} to write the class using {@link ClassWriter}
  • + *
+ */ +@SuppressWarnings("WeakerAccess") +abstract class StormClassTransformer { + + /** + * Internal registry of created transformers. This map is checked for entries + * by {@link StormClassLoader} when loading classes and invokes the transformation + * chain of methods to transform the class before defining it via JVM. + */ + private static final Map TRANSFORMERS = new HashMap<>(); + + private final ClassNode visitor; + private @Nullable ClassReader classReader; + + StormClassTransformer(String className, ClassNode visitor) { + + TRANSFORMERS.put(className, this); + this.visitor = visitor; + } + + StormClassTransformer(String className) { + this(className, new ClassNode()); + } + + /** + * Returns {@code true} if transformer with given name is registered. + */ + static boolean isRegistered(String name) { + return TRANSFORMERS.containsKey(name); + } + + /** + * Returns registered instance of {@link StormClassTransformer}. + * + * @return {@code StormClassTransformer} or {@code null} if no registered instance found. + */ + @Contract(pure = true) + static @Nullable StormClassTransformer getRegistered(String className) { + return TRANSFORMERS.getOrDefault(className, null); + } + + /** + * Returns list of instructions for method that matches given parameters. + * + * @param name name of the method to match. + * @param descriptor descriptor of the method to match. + * @return list of instructions for matched method or an empty {@link InsnList}. + * + * @see + * ASM User Guide - Method descriptors + */ + @Contract(pure = true) + final InsnList getInstructionsForMethod(String name, String descriptor) { + + if (classReader != null) + { + Optional methodNode = visitor.methods.stream() + .filter(m -> m.name.equals(name) && m.desc.equals(descriptor)).findFirst(); + + if (methodNode.isPresent()) { + return methodNode.get().instructions; + } + } + return new InsnList(); + } + + /** + * Read or parse class byte array using {@link ClassReader}. + * + * @param rawClass array of bytes to read. + * @return {@code this} instance of {@link StormClassTransformer}. + * + * @see + * ASM User Guide - Parsing classes + */ + @Contract("_ -> this") + StormClassTransformer read(byte[] rawClass) { + + classReader = new ClassReader(rawClass); + return this; + } + + /** + * Visit class structure with {@link ClassVisitor} passed + * as constructor argument to this transformer instance. + * + * @return {@code this} instance of {@link StormClassTransformer}. + * + * @see + * ASM User Guide - Class interfaces and components + */ + @Contract("-> this") + StormClassTransformer visit() { + + if (classReader != null) { + classReader.accept(visitor, 0); + } + else StormLogger.warn("Tried to visit null ClassReader"); + return this; + } + + /** + * Write the class parsed by visitor using {@link ClassWriter} to + * convert the visited class to byte array. The resulting byte array + * can then be used to define the class by class loaders. + * + * @return byte array representing transformed class. + * + * @see + * ASM User Guide - Components composition + */ + byte[] write() { + + ClassWriter classWriter = new ClassWriter(0); + visitor.accept(classWriter); + return classWriter.toByteArray(); + } + + /** + * Calls method chain to transform the given {@code Class} byte array. + * + * @param rawClass byte array representing the {@code Class} to transform. + * @return byte array representing transformed class. + */ + final byte[] transform(byte[] rawClass) { + return read(rawClass).visit().transform().write(); + } + + /** + * Intermediary step between visiting and writing class. + * {@link StormClassTransformer} implementations need to override + * this method and are free to write their own transformation steps. + * + * @return {@code this} instance of {@link StormClassTransformer}. + */ + @Contract("-> this") + abstract StormClassTransformer transform(); +} From f1a9a2dd2341f8f3e952844052e9897da9a2f73f Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 23:06:24 +0200 Subject: [PATCH 058/323] Apply custom logger level to console --- .../java/io/pzstorm/storm/StormLogger.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/StormLogger.java b/src/main/java/io/pzstorm/storm/StormLogger.java index 4c134cf..652c34e 100644 --- a/src/main/java/io/pzstorm/storm/StormLogger.java +++ b/src/main/java/io/pzstorm/storm/StormLogger.java @@ -3,11 +3,14 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; /** *

Simple wrapper class for logging with Log4j 2 logger. - * To configure root logger level launch Storm with {@code JVM_PROPERTY} set to a custom logger level. + * To configure console logging level launch Storm with {@code JVM_PROPERTY} + * set to a custom logger level. The level will be matched with {@link Logger#getLevel()}. *

* Logs will automatically be printed to console and configured log files. * Check {@code log4j2.xml} for log file locations. @@ -31,8 +34,17 @@ public class StormLogger { Level level = Level.getLevel(sLevel); if (level != null) { - Configurator.setLevel("Storm", level); - LOGGER.debug("Setting custom level for Storm logger '" + sLevel + '\''); + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + + LoggerConfig rootLoggerConfig = config.getLoggers().get(""); + for (String appender : new String[]{ "Console", "ManFile" }) + { + rootLoggerConfig.removeAppender(appender); + rootLoggerConfig.addAppender(config.getAppender(appender), level, null); + } + ctx.updateLoggers(); + LOGGER.info("Setting custom level for Storm logger '" + sLevel + '\''); } else LOGGER.error("Unable to resolve logging level '" + sLevel + '\''); } From c71f340b54f86875e173ec38128661a5b73196c0 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 29 Apr 2021 23:07:13 +0200 Subject: [PATCH 059/323] Remove StormLogger unit test Changed appender levels cannot be tested so easily. --- .../io/pzstorm/storm/StormLoggerTest.java | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 src/test/java/io/pzstorm/storm/StormLoggerTest.java diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/StormLoggerTest.java deleted file mode 100644 index 9752c1c..0000000 --- a/src/test/java/io/pzstorm/storm/StormLoggerTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.pzstorm.storm; - -import org.apache.logging.log4j.Level; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -@TestInstance(TestInstance.Lifecycle.PER_METHOD) -class StormLoggerTest implements UnitTest { - - private static final String LOGGER_PROPERTY = "storm.logger"; - - @Test - void shouldSetStormLoggerLevelFromSystemProperties() { - - // assert that key does not exist first - Assertions.assertNull(System.getProperty(LOGGER_PROPERTY)); - - Level level = Level.forName("CUSTOM_LEVEL", 1); - System.setProperty(LOGGER_PROPERTY, level.name()); - - // assert that system property was properly set - String systemProperty = System.getProperty(LOGGER_PROPERTY); - Assertions.assertEquals(level.name(), systemProperty); - - // assert that logger has correct level - Assertions.assertEquals(level, StormLogger.get().getLevel()); - } -} From 1ccbe1028d68d2bc2ab2ca49fa615302e95dedef Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 30 Apr 2021 14:25:12 +0200 Subject: [PATCH 060/323] Make StormClassLoader constructor null-safe --- src/main/java/io/pzstorm/storm/core/StormClassLoader.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index 0062d1f..98db532 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -57,7 +57,9 @@ class StormClassLoader extends ClassLoader { protected StormClassLoader(URL[] resourceLocations) { parentClassLoader = getClass().getClassLoader(); - resourceClassLoader = new URLClassLoader(resourceLocations, getParent()); + resourceClassLoader = new URLClassLoader( + Objects.requireNonNull(resourceLocations), getParent() + ); } protected StormClassLoader() { From 4c72e91c0380969457454f5d0060e3950e869bc1 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 30 Apr 2021 14:30:17 +0200 Subject: [PATCH 061/323] Create ClassTransformationException class --- .../storm/core/ClassTransformationException.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/core/ClassTransformationException.java diff --git a/src/main/java/io/pzstorm/storm/core/ClassTransformationException.java b/src/main/java/io/pzstorm/storm/core/ClassTransformationException.java new file mode 100644 index 0000000..bbb0997 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/core/ClassTransformationException.java @@ -0,0 +1,16 @@ +package io.pzstorm.storm.core; + +/** + * Signals that an unexpected error occurred while transforming classes. + * This exception is only meant to be used by {@link StormClassTransformer} classes. + */ +class ClassTransformationException extends RuntimeException { + + ClassTransformationException(String message, Throwable cause) { + super(message, cause); + } + + ClassTransformationException(String message) { + super(message); + } +} From 5ecc0b4f56ced27bf9ce51120c3cecc56bca3ec7 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 30 Apr 2021 14:30:55 +0200 Subject: [PATCH 062/323] Make transformation more safe --- .../java/io/pzstorm/storm/core/StormClassTransformer.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java index 356d048..400e4fe 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -99,12 +99,16 @@ final InsnList getInstructionsForMethod(String name, String descriptor) { * @param rawClass array of bytes to read. * @return {@code this} instance of {@link StormClassTransformer}. * + * @throws ClassTransformationException if given byte array is empty. * @see * ASM User Guide - Parsing classes */ @Contract("_ -> this") StormClassTransformer read(byte[] rawClass) { + if (rawClass.length == 0) { + throw new ClassTransformationException("Tried to read empty byte array"); + } classReader = new ClassReader(rawClass); return this; } @@ -149,6 +153,8 @@ byte[] write() { * Calls method chain to transform the given {@code Class} byte array. * * @param rawClass byte array representing the {@code Class} to transform. + * + * @throws ClassTransformationException if given byte array is empty. * @return byte array representing transformed class. */ final byte[] transform(byte[] rawClass) { From f114ef62162c3e10a9d56aad56783c809d4bbd7f Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 30 Apr 2021 14:32:12 +0200 Subject: [PATCH 063/323] Fix StormLogger custom level not applied --- src/main/java/io/pzstorm/storm/StormLogger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/pzstorm/storm/StormLogger.java b/src/main/java/io/pzstorm/storm/StormLogger.java index 652c34e..baa6e5e 100644 --- a/src/main/java/io/pzstorm/storm/StormLogger.java +++ b/src/main/java/io/pzstorm/storm/StormLogger.java @@ -38,7 +38,7 @@ public class StormLogger { Configuration config = ctx.getConfiguration(); LoggerConfig rootLoggerConfig = config.getLoggers().get(""); - for (String appender : new String[]{ "Console", "ManFile" }) + for (String appender : new String[]{ "Console", "MainFile" }) { rootLoggerConfig.removeAppender(appender); rootLoggerConfig.addAppender(config.getAppender(appender), level, null); From fbc9959852df057705b2715aadbc6b9901a25170 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 30 Apr 2021 14:36:44 +0200 Subject: [PATCH 064/323] Revert "Remove StormLogger unit test" This reverts commit c71f340b54f86875e173ec38128661a5b73196c0. --- .../io/pzstorm/storm/StormLoggerTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/StormLoggerTest.java diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/StormLoggerTest.java new file mode 100644 index 0000000..9752c1c --- /dev/null +++ b/src/test/java/io/pzstorm/storm/StormLoggerTest.java @@ -0,0 +1,29 @@ +package io.pzstorm.storm; + +import org.apache.logging.log4j.Level; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +class StormLoggerTest implements UnitTest { + + private static final String LOGGER_PROPERTY = "storm.logger"; + + @Test + void shouldSetStormLoggerLevelFromSystemProperties() { + + // assert that key does not exist first + Assertions.assertNull(System.getProperty(LOGGER_PROPERTY)); + + Level level = Level.forName("CUSTOM_LEVEL", 1); + System.setProperty(LOGGER_PROPERTY, level.name()); + + // assert that system property was properly set + String systemProperty = System.getProperty(LOGGER_PROPERTY); + Assertions.assertEquals(level.name(), systemProperty); + + // assert that logger has correct level + Assertions.assertEquals(level, StormLogger.get().getLevel()); + } +} From 85f6b451d1cc6e2675b77914ce9a41afa73420ee Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 30 Apr 2021 14:55:22 +0200 Subject: [PATCH 065/323] Implement real logger unit test --- .../io/pzstorm/storm/StormLoggerTest.java | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/StormLoggerTest.java index 9752c1c..fd4ceb3 100644 --- a/src/test/java/io/pzstorm/storm/StormLoggerTest.java +++ b/src/test/java/io/pzstorm/storm/StormLoggerTest.java @@ -1,29 +1,64 @@ package io.pzstorm.storm; import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.AppenderControl; +import org.apache.logging.log4j.core.config.AppenderControlArraySet; +import org.apache.logging.log4j.core.config.LoggerConfig; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -@TestInstance(TestInstance.Lifecycle.PER_METHOD) +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + class StormLoggerTest implements UnitTest { private static final String LOGGER_PROPERTY = "storm.logger"; @Test - void shouldSetStormLoggerLevelFromSystemProperties() { + void shouldSetStormLoggerLevelFromSystemProperties() throws ReflectiveOperationException { // assert that key does not exist first Assertions.assertNull(System.getProperty(LOGGER_PROPERTY)); - Level level = Level.forName("CUSTOM_LEVEL", 1); - System.setProperty(LOGGER_PROPERTY, level.name()); + Level expectedLevel = Level.forName("CUSTOM_LEVEL", 1); + System.setProperty(LOGGER_PROPERTY, expectedLevel.name()); // assert that system property was properly set String systemProperty = System.getProperty(LOGGER_PROPERTY); - Assertions.assertEquals(level.name(), systemProperty); + Assertions.assertEquals(expectedLevel.name(), systemProperty); + + // initialize StormLogger class + Class.forName("io.pzstorm.storm.StormLogger"); + + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + LoggerConfig rootLoggerConfig = ctx.getConfiguration().getLoggers().get(""); + + Field appendersField = LoggerConfig.class.getDeclaredField("appenders"); + appendersField.setAccessible(true); + + Field appenderLevelField = AppenderControl.class.getDeclaredField("level"); + appenderLevelField.setAccessible(true); + + Map actualLevels = new HashMap<>(); + + AppenderControlArraySet appenders = (AppenderControlArraySet) appendersField.get(rootLoggerConfig); + for (AppenderControl appenderControl : appenders.get()) + { + String appenderName = appenderControl.getAppenderName(); + if (appenderName.equals("Console") || appenderName.equals("MainFile")) { + actualLevels.put(appenderName, (Level) appenderLevelField.get(appenderControl)); + } + } + // assert that all appenders were found + Assertions.assertTrue(actualLevels.containsKey("Console")); + Assertions.assertTrue(actualLevels.containsKey("MainFile")); - // assert that logger has correct level - Assertions.assertEquals(level, StormLogger.get().getLevel()); + // assert that logger has correct levels + for (Map.Entry entry : actualLevels.entrySet()) { + Assertions.assertEquals(expectedLevel, entry.getValue()); + } } } From 9637e03b8ed20ba6990c5bfd20575fedf7fbdcb2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 30 Apr 2021 23:12:07 +0200 Subject: [PATCH 066/323] Expand code style margin width --- .idea/codeStyles/Project.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 527b8a1..be8fc47 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,10 +1,10 @@

{@code Set} of class prefixes that mark classes to be loaded with this class loader. - * If a class does not match this patter it's loading will be delegated to the parent loader. + *

+ * {@code Set} of class prefixes that mark classes to not load with this {@code ClassLoader}. + * If a class matches this patter it's loading will be delegated to the parent loader. *

- * This {@code Set} includes both Java libraries and main classes. Java Libraries have to be - * loaded by the same class loader loading the game so that native libraries loaded from - * those Java libraries become associated with the class loader loading the game. - * If the native libraries are loaded using a different class loader they - * will not be accessible to game classes. + * Classes matching this prefix are causing exceptions when loaded with this {@code ClassLoader}, + * and they also do not need to be loaded with this {@code ClassLoader}. */ @SuppressWarnings("SpellCheckingInspection") - private static final ImmutableSet CLASS_WHITELIST = ImmutableSet.of( - // zomboid library classes - "org.lwjgl.", "net.java.games.", "jassimp.", - // zomboid main classes - "astar.", "com.evildevil.engines.bubble.texture.", - "com.jcraft.", "com.sixlegs.png.", "de.jarnbjo.", "fmod.", - "javax.vecmath.", "org.joml.", "org.luaj.kahluafork.compiler.", - "org.mindrot.jbcrypt.", "se.krka.kahlua.", "zombie." + private static final ImmutableSet CLASS_BLACKLIST = ImmutableSet.of( + "java.", "org.objectweb.asm.", "sun.", "com.sun.", + "javax.imageio.", "javax.xml.", "org.w3c." ); protected final URLClassLoader resourceClassLoader; /** @@ -70,12 +63,12 @@ protected StormClassLoader() { } /** - * Returns {@code true} if at least one prefix pattern in whitelist matches the given name. - * When a class name is considered whitelisted it will be loaded by this {@code ClassLoader}. + * Returns {@code true} if at least one prefix pattern in blacklist matches the given name. + * When a class name is considered blacklisted it will not be loaded by this {@code ClassLoader}. */ @Contract(pure = true) - protected static boolean isWhitelistedClass(String name) { - return CLASS_WHITELIST.stream().anyMatch(name::startsWith); + protected static boolean isBlacklistedClass(String name) { + return CLASS_BLACKLIST.stream().anyMatch(name::startsWith); } @Override @@ -143,7 +136,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot if (clazz == null) { StormLogger.debug("Preparing to load class " + name); - if (isWhitelistedClass(name)) + if (!isBlacklistedClass(name)) { StormLogger.debug("Loading with StormClassLoader"); try { diff --git a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java index f8601e6..eae3586 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java @@ -28,10 +28,10 @@ class StormClassLoaderTest extends StormClassLoader implements UnitTest { } @Test - void shouldLoadWhitelistedClassesWithStormClassLoader() throws ReflectiveOperationException { + void shouldLoadNonBlacklistedClassesWithStormClassLoader() throws ReflectiveOperationException { - for (String whitelistedClass : getWhitelistedClasses()) { - Assertions.assertNull(findLoadedClass(whitelistedClass)); + for (String blacklistedClasses : getBlacklistedClasses()) { + Assertions.assertNull(findLoadedClass(blacklistedClasses)); } ImmutableSet dummyGameClasses = ImmutableSet.of( "fmod.FmodClass", "jassimp.JassimpLibraryClass", "javax.vecmath.MathClass", @@ -46,20 +46,24 @@ void shouldLoadWhitelistedClassesWithStormClassLoader() throws ReflectiveOperati } @Test - void shouldDelegateLoadingNonWhitelistedClassesToParentClassLoader() throws ReflectiveOperationException { + void shouldDelegateLoadingBlacklistedClassesToParentClassLoader() throws ReflectiveOperationException { Method method = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); method.setAccessible(true); ImmutableSet dummyClasses = ImmutableSet.of( - "com.google.common.io.Files", "com.google.common.util.concurrent.Atomics", - "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.Level" + "java.util.concurrent.CountDownLatch", + "org.objectweb.asm.ClassWriter", + "sun.reflect.FieldInfo", + "com.sun.java.util.jar.pack.DriverResource", + "javax.imageio.IIOImage", + "javax.xml.XMLConstants", + "org.w3c.dom.Attr" ); for (String dummyClass : dummyClasses) { - Class loadedClass = loadClass(dummyClass, true); - Assertions.assertEquals(loadedClass, method.invoke(getParent(), dummyClass)); - Assertions.assertEquals(this.getParent(), loadedClass.getClassLoader()); + ClassLoader classLoader = loadClass(dummyClass, true).getClassLoader(); + Assertions.assertTrue(classLoader == null || !classLoader.equals(this)); } } @@ -114,10 +118,10 @@ void shouldDefinePackageWhenPackageNameIsValid() { } @Test - void shouldProperlyRecognizeWhitelistedClassNames() throws ReflectiveOperationException { + void shouldProperlyRecognizeBlacklistedClassNames() throws ReflectiveOperationException { - for (String className : getWhitelistedClasses()) { - Assertions.assertTrue(isWhitelistedClass(className)); + for (String className : getBlacklistedClasses()) { + Assertions.assertTrue(isBlacklistedClass(className)); } Random rand = new Random(); for (int i1 = 10; i1 > 0; i1--) @@ -129,7 +133,7 @@ void shouldProperlyRecognizeWhitelistedClassNames() throws ReflectiveOperationEx for (int i2 = rand.nextInt(5) + 1; i2 > 0; i2--) { sb.append(".").append(getRandomString()); } - Assertions.assertFalse(isWhitelistedClass(sb.toString())); + Assertions.assertFalse(isBlacklistedClass(sb.toString())); } } @@ -138,9 +142,9 @@ private static String getRandomString() { } @SuppressWarnings("unchecked") - private static Set getWhitelistedClasses() throws ReflectiveOperationException { + private static Set getBlacklistedClasses() throws ReflectiveOperationException { - Field field = StormClassLoader.class.getDeclaredField("CLASS_WHITELIST"); + Field field = StormClassLoader.class.getDeclaredField("CLASS_BLACKLIST"); field.setAccessible(true); return (Set) field.get(null); From 2367d4e97048033262d271dbfc03f49237f91163 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 13:25:30 +0200 Subject: [PATCH 068/323] Convert gitattributes EOL to LF --- .gitattributes | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitattributes b/.gitattributes index 61793db..00a51af 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ -# -# https://help.github.com/articles/dealing-with-line-endings/ -# -# These are explicitly windows files and should use crlf -*.bat text eol=crlf - +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# These are explicitly windows files and should use crlf +*.bat text eol=crlf + From eb66aa599485419ae41e8fd26bd06ef70d343ff9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 14:03:29 +0200 Subject: [PATCH 069/323] Suppress spell checking inspection --- build.gradle | 1 + gradle/wrapper/gradle-wrapper.properties | 1 + src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java | 1 + 3 files changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 38c5d40..ea66b75 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,4 @@ +//file:noinspection SpellCheckingInspection import io.pzstorm.capsid.setup.xml.LaunchRunConfig import io.pzstorm.capsid.setup.VmParameter diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a257bc7..28fceaa 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,3 +1,4 @@ +# suppress inspection "SpellCheckingInspection" for whole file distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip diff --git a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java index eae3586..29f1459 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java @@ -17,6 +17,7 @@ import io.pzstorm.storm.UnitTest; +@SuppressWarnings("SpellCheckingInspection") class StormClassLoaderTest extends StormClassLoader implements UnitTest { private static final ClassLoader CL = StormClassLoaderTest.class.getClassLoader(); From 840ffff59cb503afa07487eee3bab4e3b77ee2e5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 14:27:07 +0200 Subject: [PATCH 070/323] Improve class encapsulation --- .../java/io/pzstorm/storm/core/StormClassLoader.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index d907da4..4393e69 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -47,7 +47,7 @@ class StormClassLoader extends ClassLoader { * * @param resourceLocations the URLs from which to load classes and resources. */ - protected StormClassLoader(URL[] resourceLocations) { + StormClassLoader(URL[] resourceLocations) { parentClassLoader = getClass().getClassLoader(); resourceClassLoader = new URLClassLoader( @@ -55,7 +55,7 @@ protected StormClassLoader(URL[] resourceLocations) { ); } - protected StormClassLoader() { + StormClassLoader() { StormLogger.debug("Initialized StormClassLoader"); parentClassLoader = getClass().getClassLoader(); @@ -67,7 +67,7 @@ protected StormClassLoader() { * When a class name is considered blacklisted it will not be loaded by this {@code ClassLoader}. */ @Contract(pure = true) - protected static boolean isBlacklistedClass(String name) { + static boolean isBlacklistedClass(String name) { return CLASS_BLACKLIST.stream().anyMatch(name::startsWith); } @@ -96,7 +96,7 @@ protected static boolean isBlacklistedClass(String name) { * @throws IllegalArgumentException if package name duplicates an existing * package either in this class loader or one of its ancestors */ - protected @Nullable Package definePackageForName(String name) { + @Nullable Package definePackageForName(String name) { int pkgDelimiterPos = name.lastIndexOf('.'); if (pkgDelimiterPos > 0) @@ -203,7 +203,7 @@ private String getClassFileName(String name) { * * @throws IOException if an I/O error occurred while reading or writing to stream. */ - protected byte[] getRawClassByteArray(String name) throws IOException { + byte[] getRawClassByteArray(String name) throws IOException { // opens an input stream to read the class for given name InputStream inputStream = getResourceAsStream(getClassFileName(name)); From 01884c631c919fceeb54bbf26d3a20ddd36b241b Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 16:23:06 +0200 Subject: [PATCH 071/323] Bootstrap class loader and transformer --- .../io/pzstorm/storm/core/StormBootstrap.java | 76 +++++++++++++++++++ .../pzstorm/storm/core/StormClassLoader.java | 17 +++-- .../storm/core/StormClassTransformer.java | 6 +- .../io/pzstorm/storm/core/StormLauncher.java | 6 +- 4 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 src/main/java/io/pzstorm/storm/core/StormBootstrap.java diff --git a/src/main/java/io/pzstorm/storm/core/StormBootstrap.java b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java new file mode 100644 index 0000000..0142261 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java @@ -0,0 +1,76 @@ +package io.pzstorm.storm.core; + +import java.lang.reflect.Method; + +/** + * This class bootstraps everything needed to launch the game with static initialization. + * It should be loaded from {@link StormLauncher} before Storm attempts to launch the game. + */ +class StormBootstrap { + + /** + * {@code ClassLoader} used to transform and load all needed classes. Because class loaders + * maintain their own set of class instances and native libraries you should always use + * this loader to load classes that access or modify transformed class fields or methods. + */ + static final StormClassLoader CLASS_LOADER = new StormClassLoader(); + + /** + * Loaded and initialized {@link StormClassTransformer} {@code Class}. To transform specific + * classes during load time (on-fly) {@link StormClassLoader} has to read and invoke + * registered transformers. Due to how class loading works in Java references to classes within + * {@code ClassLoader} do not get loaded by that specific {@code ClassLoader} but get delegate + * to {@code AppClassLoader}. For this reason we have to use bootstrapping and reflection to + * access transformers from {@code StormClassLoader}. + */ + static final Class TRANSFORMER_CLASS; + + /** + * Represents {@link StormClassTransformer#getRegistered(String)} method. + * + * @see #getRegisteredTransformer(String) + */ + private static final Method TRANSFORMER_GETTER; + + /** + * Represents {@link StormClassTransformer#transform(byte[])} method. + * + * @see #invokeTransformer(Object, byte[]) + */ + private static final Method TRANSFORMER_INVOKER; + + static + { + try { + String transformerClass = "io.pzstorm.storm.core.StormClassTransformer"; + TRANSFORMER_CLASS = Class.forName(transformerClass, true, CLASS_LOADER); + + TRANSFORMER_GETTER = TRANSFORMER_CLASS.getDeclaredMethod("getRegistered", String.class); + TRANSFORMER_INVOKER = TRANSFORMER_CLASS.getDeclaredMethod("transform", byte[].class); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns registered instance of {@link StormClassTransformer} that matches the given name. + * + * @throws ReflectiveOperationException if an error occurred while invoking method. + * @see StormClassTransformer#getRegistered(String) + */ + static Object getRegisteredTransformer(String name) throws ReflectiveOperationException { + return TRANSFORMER_GETTER.invoke(null, name); + } + + /** + * Calls method chain to transform the given {@code Class} byte array. + * + * @throws ReflectiveOperationException if an error occurred while invoking method. + * @see StormClassTransformer#transform(byte[]) + */ + @SuppressWarnings("RedundantCast") + static byte[] invokeTransformer(Object transformer, byte[] rawClass) throws ReflectiveOperationException { + return (byte[]) TRANSFORMER_INVOKER.invoke(transformer, (Object) rawClass); + } +} diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index 4393e69..4eacc81 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -142,9 +142,13 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot try { byte[] input = getRawClassByteArray(name); - StormClassTransformer transformer = StormClassTransformer.getRegistered(name); - if (transformer != null) { - input = transformer.transform(input); + Class transformerClass = StormBootstrap.TRANSFORMER_CLASS; + if (transformerClass != null) + { + Object transformer = StormBootstrap.getRegisteredTransformer(name); + if (transformer != null) { + input = StormBootstrap.invokeTransformer(transformer, input); + } } if (input.length > 0) { @@ -153,14 +157,13 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot clazz = defineClass(name, input, 0, input.length); if (clazz.getClassLoader() == this) { - StormLogger.debug("STORM: Successfully loaded class with StormClassLoader"); + StormLogger.debug("Successfully loaded class with StormClassLoader"); } else throw new RuntimeException("Unable to load class with StormClassLoader"); } } - catch (IOException e) { - throw new RuntimeException("I/O exception occurred while transforming class to byte " + - "array"); + catch (IOException | ReflectiveOperationException e) { + throw new RuntimeException(e); } } } diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java index 400e4fe..580ee9e 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -29,7 +29,7 @@ * */ @SuppressWarnings("WeakerAccess") -abstract class StormClassTransformer { +public abstract class StormClassTransformer { /** * Internal registry of created transformers. This map is checked for entries @@ -59,12 +59,12 @@ static boolean isRegistered(String name) { } /** - * Returns registered instance of {@link StormClassTransformer}. + * Returns registered instance of {@link StormClassTransformer} that matches the given name. * * @return {@code StormClassTransformer} or {@code null} if no registered instance found. */ @Contract(pure = true) - static @Nullable StormClassTransformer getRegistered(String className) { + public static @Nullable StormClassTransformer getRegistered(String className) { return TRANSFORMERS.getOrDefault(className, null); } diff --git a/src/main/java/io/pzstorm/storm/core/StormLauncher.java b/src/main/java/io/pzstorm/storm/core/StormLauncher.java index 15dc475..0ae7bab 100644 --- a/src/main/java/io/pzstorm/storm/core/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/core/StormLauncher.java @@ -28,9 +28,11 @@ class StormLauncher { public static void main(String[] args) throws ReflectiveOperationException { StormLogger.debug("Preparing to launch Project Zomboid"); - StormClassLoader stormLoader = new StormClassLoader(); - Class entryPointClass = stormLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); + StormClassLoader classLoader = StormBootstrap.CLASS_LOADER; + Class.forName("io.pzstorm.storm.core.StormEventHooks", true, classLoader); + + Class entryPointClass = classLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); Method entryPoint = entryPointClass.getMethod(ZOMBOID_ENTRY_POINT, String[].class); try { /* we invoke the entry point using reflection because we don't want to reference From 36509c4d6d3c55f7ceadf7007b4c3714e602e9d3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 16:24:38 +0200 Subject: [PATCH 072/323] Do minor code formatting --- src/main/java/io/pzstorm/storm/NonNullPackage.java | 1 + src/test/java/io/pzstorm/storm/StormLoggerTest.java | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/NonNullPackage.java b/src/main/java/io/pzstorm/storm/NonNullPackage.java index 8e5ec16..14b5087 100644 --- a/src/main/java/io/pzstorm/storm/NonNullPackage.java +++ b/src/main/java/io/pzstorm/storm/NonNullPackage.java @@ -13,4 +13,5 @@ @Target(ElementType.PACKAGE) @Retention(RetentionPolicy.CLASS) public @interface NonNullPackage { + } diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/StormLoggerTest.java index fd4ceb3..4b9280c 100644 --- a/src/test/java/io/pzstorm/storm/StormLoggerTest.java +++ b/src/test/java/io/pzstorm/storm/StormLoggerTest.java @@ -1,5 +1,9 @@ package io.pzstorm.storm; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; @@ -9,10 +13,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; - class StormLoggerTest implements UnitTest { private static final String LOGGER_PROPERTY = "storm.logger"; From e3022fbe3a88009c261ee5d240a1f5ed40b0f35a Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 17:39:09 +0200 Subject: [PATCH 073/323] Add more class names to blacklist --- src/main/java/io/pzstorm/storm/core/StormClassLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index 4eacc81..db2f09f 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -31,8 +31,8 @@ class StormClassLoader extends ClassLoader { */ @SuppressWarnings("SpellCheckingInspection") private static final ImmutableSet CLASS_BLACKLIST = ImmutableSet.of( - "java.", "org.objectweb.asm.", "sun.", "com.sun.", - "javax.imageio.", "javax.xml.", "org.w3c." + "java.", "org.objectweb.asm.", "sun.", "com.sun.", "org.xml.", "org.w3c.", + "javax.script.", "javax.management.", "javax.imageio.", "javax.xml." ); protected final URLClassLoader resourceClassLoader; /** From b7d31394953a2f458ce93c45fbac2440016b9b72 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 17:40:12 +0200 Subject: [PATCH 074/323] Lower logger initialization log level --- src/main/java/io/pzstorm/storm/StormLogger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/pzstorm/storm/StormLogger.java b/src/main/java/io/pzstorm/storm/StormLogger.java index baa6e5e..b0b3ac4 100644 --- a/src/main/java/io/pzstorm/storm/StormLogger.java +++ b/src/main/java/io/pzstorm/storm/StormLogger.java @@ -48,7 +48,7 @@ public class StormLogger { } else LOGGER.error("Unable to resolve logging level '" + sLevel + '\''); } - LOGGER.debug("Initialized Storm logger"); + LOGGER.info("Initialized Storm logger"); } /* Make the constructor private to disable instantiation */ From a05b4379925a553bd6055aa29c4c048af7c71bfa Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 17:40:48 +0200 Subject: [PATCH 075/323] Fix missing logger level error in test --- src/test/java/io/pzstorm/storm/StormLoggerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/StormLoggerTest.java index 4b9280c..04ca3d2 100644 --- a/src/test/java/io/pzstorm/storm/StormLoggerTest.java +++ b/src/test/java/io/pzstorm/storm/StormLoggerTest.java @@ -23,7 +23,7 @@ void shouldSetStormLoggerLevelFromSystemProperties() throws ReflectiveOperationE // assert that key does not exist first Assertions.assertNull(System.getProperty(LOGGER_PROPERTY)); - Level expectedLevel = Level.forName("CUSTOM_LEVEL", 1); + Level expectedLevel = Level.forName("ALL", 1); System.setProperty(LOGGER_PROPERTY, expectedLevel.name()); // assert that system property was properly set From 5180fe9836efa7e8605c69767a7e017fd38bc870 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 17:52:02 +0200 Subject: [PATCH 076/323] Define zombie testing module --- build.gradle | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/build.gradle b/build.gradle index ea66b75..cc40feb 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,22 @@ capsid { setProjectRepository('pzstorm', 'storm') } +sourceSets { + // this sourceSet is used for testing class transformation + zombie { + runtimeClasspath += sourceSets.main.output + compileClasspath += sourceSets.main.output + } + test { + // tests need to see classes used in class transformation tests + runtimeClasspath += sourceSets.zombie.output + compileClasspath += sourceSets.zombie.output + } +} +configurations { + zombieImplementation.extendsFrom(compileOnly, zomboidImplementation) +} + dependencies { // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' From 505cc4da4662a8dae28fec5d78fa4d3544e3da21 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 17:58:33 +0200 Subject: [PATCH 077/323] Test transforming stack constant --- .../storm/core/StormClassTransformerTest.java | 32 +++++++++++++++++++ .../storm/core/ZombieHelloTransformer.java | 26 +++++++++++++++ src/zombie/java/zombie/ZombieHello.java | 17 ++++++++++ 3 files changed, 75 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java create mode 100644 src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java create mode 100644 src/zombie/java/zombie/ZombieHello.java diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java new file mode 100644 index 0000000..a8f225e --- /dev/null +++ b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java @@ -0,0 +1,32 @@ +package io.pzstorm.storm.core; + +import java.lang.reflect.Constructor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.pzstorm.storm.UnitTest; + +class StormClassTransformerTest implements UnitTest { + + @Test + void shouldChangeStackConstantInInstructionList() throws ReflectiveOperationException { + + String className = "zombie.ZombieHello"; + + // create and register transformer + Class transformerClass = Class.forName( + "io.pzstorm.storm.core.ZombieHelloTransformer", + true, StormBootstrap.CLASS_LOADER + ); + Constructor constructor = transformerClass.getConstructor(); + constructor.setAccessible(true); + constructor.newInstance(); + + Class zombieHello = StormBootstrap.CLASS_LOADER.loadClass(className, true); + String hello = (String) zombieHello.getDeclaredMethod("getHello").invoke(null); + Assertions.assertEquals("Zombie says: you die today!", hello); + + zombieHello.getDeclaredMethod("sayHello").invoke(null); + } +} diff --git a/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java new file mode 100644 index 0000000..6e98c07 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java @@ -0,0 +1,26 @@ +package io.pzstorm.storm.core; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; + +class ZombieHelloTransformer extends StormClassTransformer { + + ZombieHelloTransformer() { + super("zombie.ZombieHello"); + } + + @Override + public StormClassTransformer transform() { + + InsnList instructions = getInstructionsForMethod("getHello", "()Ljava/lang/String;"); + for (AbstractInsnNode instruction : instructions) + { + if (instruction.getOpcode() == Opcodes.LDC) { + ((LdcInsnNode) instruction).cst = "Zombie says: you die today!"; + } + } + return this; + } +} diff --git a/src/zombie/java/zombie/ZombieHello.java b/src/zombie/java/zombie/ZombieHello.java new file mode 100644 index 0000000..dd5cd35 --- /dev/null +++ b/src/zombie/java/zombie/ZombieHello.java @@ -0,0 +1,17 @@ +package zombie; + +import java.io.FileNotFoundException; + +import org.jetbrains.annotations.TestOnly; + +@TestOnly +public class ZombieHello { + + public static void sayHello() { + System.out.println(getHello()); + } + + public static String getHello() { + return "Zombie says: hello!"; + } +} From 0c3791d92a16a8c5bd157600d482456f21d10387 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 19:51:14 +0200 Subject: [PATCH 078/323] Fix StormLogger initialized twice --- src/main/java/io/pzstorm/storm/core/StormClassLoader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index db2f09f..95107a4 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -32,7 +32,8 @@ class StormClassLoader extends ClassLoader { @SuppressWarnings("SpellCheckingInspection") private static final ImmutableSet CLASS_BLACKLIST = ImmutableSet.of( "java.", "org.objectweb.asm.", "sun.", "com.sun.", "org.xml.", "org.w3c.", - "javax.script.", "javax.management.", "javax.imageio.", "javax.xml." + "javax.script.", "javax.management.", "javax.imageio.", "javax.xml.", + "io.pzstorm.storm.StormLogger" ); protected final URLClassLoader resourceClassLoader; /** From e7f10496f5eb491196f56b3afa9787e63a683501 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 19:53:02 +0200 Subject: [PATCH 079/323] Fix tests logged at debug level --- src/test/java/io/pzstorm/storm/StormLoggerTest.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/StormLoggerTest.java index 04ca3d2..d770cf2 100644 --- a/src/test/java/io/pzstorm/storm/StormLoggerTest.java +++ b/src/test/java/io/pzstorm/storm/StormLoggerTest.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AppenderControl; import org.apache.logging.log4j.core.config.AppenderControlArraySet; +import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -20,8 +21,8 @@ class StormLoggerTest implements UnitTest { @Test void shouldSetStormLoggerLevelFromSystemProperties() throws ReflectiveOperationException { - // assert that key does not exist first - Assertions.assertNull(System.getProperty(LOGGER_PROPERTY)); + // store current level from system properties + Level originalLevel = Level.toLevel(System.getProperty(LOGGER_PROPERTY, "INFO")); Level expectedLevel = Level.forName("ALL", 1); System.setProperty(LOGGER_PROPERTY, expectedLevel.name()); @@ -35,6 +36,7 @@ void shouldSetStormLoggerLevelFromSystemProperties() throws ReflectiveOperationE LoggerContext ctx = (LoggerContext) LogManager.getContext(false); LoggerConfig rootLoggerConfig = ctx.getConfiguration().getLoggers().get(""); + Configuration config = ctx.getConfiguration(); Field appendersField = LoggerConfig.class.getDeclaredField("appenders"); appendersField.setAccessible(true); @@ -60,5 +62,12 @@ void shouldSetStormLoggerLevelFromSystemProperties() throws ReflectiveOperationE for (Map.Entry entry : actualLevels.entrySet()) { Assertions.assertEquals(expectedLevel, entry.getValue()); } + // reset the logger levels to original values + for (String appender : new String[]{ "Console", "MainFile" }) + { + rootLoggerConfig.removeAppender(appender); + rootLoggerConfig.addAppender(config.getAppender(appender), originalLevel, null); + } + ctx.updateLoggers(); } } From 1020549c68d48ad89afd77399f478bae098adafd Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 19:56:41 +0200 Subject: [PATCH 080/323] Fix test transformer not accessible --- .../java/io/pzstorm/storm/core/ZombieHelloTransformer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java index 6e98c07..a1c4007 100644 --- a/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java +++ b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java @@ -5,9 +5,9 @@ import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LdcInsnNode; -class ZombieHelloTransformer extends StormClassTransformer { +public class ZombieHelloTransformer extends StormClassTransformer { - ZombieHelloTransformer() { + public ZombieHelloTransformer() { super("zombie.ZombieHello"); } From f921f1c80f39a6822abc2e9fd87cbe0490c853e9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 20:00:25 +0200 Subject: [PATCH 081/323] Make StormClassTransformer accessible --- .../java/io/pzstorm/storm/core/StormClassTransformer.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java index 580ee9e..0135d03 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -38,7 +38,8 @@ public abstract class StormClassTransformer { */ private static final Map TRANSFORMERS = new HashMap<>(); - private final ClassNode visitor; + protected final String className; + protected final ClassNode visitor; private @Nullable ClassReader classReader; StormClassTransformer(String className, ClassNode visitor) { @@ -153,11 +154,11 @@ byte[] write() { * Calls method chain to transform the given {@code Class} byte array. * * @param rawClass byte array representing the {@code Class} to transform. + * @return byte array representing transformed class. * * @throws ClassTransformationException if given byte array is empty. - * @return byte array representing transformed class. */ - final byte[] transform(byte[] rawClass) { + public byte[] transform(byte[] rawClass) { return read(rawClass).visit().transform().write(); } From 6f1de304c30bd64e42c42ac7186eaf07bd3f91b3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 23:23:05 +0200 Subject: [PATCH 082/323] Add diff-utils test dependency --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index cc40feb..d290ecb 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,9 @@ dependencies { // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.7.1' + // https://mvnrepository.com/artifact/io.github.java-diff-utils/java-diff-utils + testImplementation 'io.github.java-diff-utils:java-diff-utils:4.10' + // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core implementation 'org.apache.logging.log4j:log4j-core:2.14.0' From 23cb2012869f7435ae97ac68ccd54656ab4f5f7e Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 23:28:33 +0200 Subject: [PATCH 083/323] Generate bytecode diff in transformer tests --- .../storm/core/StormClassTestTransformer.java | 110 ++++++++++++++++++ .../storm/core/ZombieHelloTransformer.java | 2 +- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java new file mode 100644 index 0000000..25e3232 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java @@ -0,0 +1,110 @@ +package io.pzstorm.storm.core; + +import com.github.difflib.DiffUtils; +import com.github.difflib.UnifiedDiffUtils; +import com.github.difflib.patch.Patch; +import io.pzstorm.storm.StormLogger; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.util.Printer; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; + +/** + * This class represents a {@code Class} transformer used to alter {@code Class} + * bytecode using ASM when running JUnit tests. When transforming it will print a + * detailed diff of changed bytecode using {@link StormLogger} to make debugging easier. + */ +public abstract class StormClassTestTransformer extends StormClassTransformer { + + private static final Printer ASM_PRINTER = new Textifier(); + private static final TraceMethodVisitor METHOD_PRINTER = new TraceMethodVisitor(ASM_PRINTER); + + StormClassTestTransformer(String className) { + super(className); + } + + @Override + public byte[] transform(byte[] rawClass) { + + read(rawClass).visit(); + String original = getBytecodeFor(visitor); + + byte[] result = transform().write(); + String modifier = getBytecodeFor(visitor); + + StormLogger.info("Generating class bytecode diff"); + String diff = getBytecodeDiff(className, original, modifier); + if (!diff.isEmpty()) { + StormLogger.info(diff); + } + else StormLogger.warn("No diff available, class bytecode is identical"); + return result; + } + + /** + * Returns the text constructed from given node's bytecode. + * @param insnNode node to construct the text for. + */ + private static String getBytecodeFor(AbstractInsnNode insnNode) { + + insnNode.accept(METHOD_PRINTER); + + StringWriter stringWriter = new StringWriter(); + ASM_PRINTER.print(new PrintWriter(stringWriter)); + ASM_PRINTER.getText().clear(); + + return stringWriter.toString(); + } + + /** + * Returns the text constructed from given {@link ClassNode} bytecode. + * @param classNode node to construct the text for. + */ + private static String getBytecodeFor(ClassNode classNode) { + + StringBuilder builder = new StringBuilder(); + for (MethodNode methodNode: classNode.methods) + { + builder.append(String.format("Method %s -> %s\n", methodNode.name, methodNode.desc)); + for (AbstractInsnNode insnNode: methodNode.instructions) { + builder.append(String.format(".... %s", getBytecodeFor(insnNode))); + } + } + return builder.toString(); + } + + /** + * Returns the Unified Diff format text that represents the bytecode difference + * between {@code originalCode} and {@code modifiedCode}. + * + * @param className filename of the original (unrevised file). + * @param originalCode lines of the original file. + * @param modifiedCode lines of the modified file. + * + * @return bytecode difference between two set of code lines. + */ + private static String getBytecodeDiff(String className, String originalCode, String modifiedCode) { + + List originalLines = Arrays.asList(originalCode.split("\n")); + List modifiedLines = Arrays.asList(modifiedCode.split("\n")); + + Patch bytecodePatch = DiffUtils.diff(originalLines, modifiedLines); + List unifiedDiff = UnifiedDiffUtils.generateUnifiedDiff( + className + ".class (original)", + className + ".class (transformed)", + originalLines, bytecodePatch, 4 + ); + StringBuilder builder = new StringBuilder(); + for (String diffLine : unifiedDiff) { + builder.append(diffLine).append("\n"); + } + return builder.toString(); + } +} diff --git a/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java index a1c4007..bbfb120 100644 --- a/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java +++ b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java @@ -5,7 +5,7 @@ import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LdcInsnNode; -public class ZombieHelloTransformer extends StormClassTransformer { +public class ZombieHelloTransformer extends StormClassTestTransformer { public ZombieHelloTransformer() { super("zombie.ZombieHello"); From d1647dcc3f62ce057ce9c434a303b83b02d2d953 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sat, 1 May 2021 23:32:51 +0200 Subject: [PATCH 084/323] Do IDEA and spotless code formatting --- src/main/dist/build.gradle | 2 +- .../storm/core/StormClassTransformer.java | 4 +- src/main/resources/log4j2.xml | 2 +- src/test/java/io/pzstorm/storm/UnitTest.java | 1 + .../storm/core/StormClassLoaderTest.java | 28 ++++----- .../storm/core/StormClassTestTransformer.java | 62 ++++++++++--------- src/zombie/java/zombie/ZombieHello.java | 2 - 7 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/main/dist/build.gradle b/src/main/dist/build.gradle index b138032..bedf752 100644 --- a/src/main/dist/build.gradle +++ b/src/main/dist/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'io.pzstorm.capsid' version '1.0.0' + id 'io.pzstorm.capsid' version '1.0.0' } capsid { diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java index 0135d03..524bd42 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -44,8 +44,10 @@ public abstract class StormClassTransformer { StormClassTransformer(String className, ClassNode visitor) { - TRANSFORMERS.put(className, this); + this.className = className; this.visitor = visitor; + + TRANSFORMERS.put(className, this); } StormClassTransformer(String className) { diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 37ca2b9..ae92071 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -9,7 +9,7 @@ - + diff --git a/src/test/java/io/pzstorm/storm/UnitTest.java b/src/test/java/io/pzstorm/storm/UnitTest.java index af682b3..c8fbe36 100644 --- a/src/test/java/io/pzstorm/storm/UnitTest.java +++ b/src/test/java/io/pzstorm/storm/UnitTest.java @@ -4,4 +4,5 @@ @Tag("unit") public interface UnitTest { + } diff --git a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java index 29f1459..2b217c2 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java @@ -25,7 +25,20 @@ class StormClassLoaderTest extends StormClassLoader implements UnitTest { private static final URL DELEGATE_RESOURCE_DIR = CL.getResource("./delegate/"); StormClassLoaderTest() { - super(new URL[] { CLASSES_RESOURCE_DIR, DELEGATE_RESOURCE_DIR }); + super(new URL[]{ CLASSES_RESOURCE_DIR, DELEGATE_RESOURCE_DIR }); + } + + private static String getRandomString() { + return UUID.randomUUID().toString().replaceAll("-", ""); + } + + @SuppressWarnings("unchecked") + private static Set getBlacklistedClasses() throws ReflectiveOperationException { + + Field field = StormClassLoader.class.getDeclaredField("CLASS_BLACKLIST"); + field.setAccessible(true); + + return (Set) field.get(null); } @Test @@ -137,17 +150,4 @@ void shouldProperlyRecognizeBlacklistedClassNames() throws ReflectiveOperationEx Assertions.assertFalse(isBlacklistedClass(sb.toString())); } } - - private static String getRandomString() { - return UUID.randomUUID().toString().replaceAll("-", ""); - } - - @SuppressWarnings("unchecked") - private static Set getBlacklistedClasses() throws ReflectiveOperationException { - - Field field = StormClassLoader.class.getDeclaredField("CLASS_BLACKLIST"); - field.setAccessible(true); - - return (Set) field.get(null); - } } diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java index 25e3232..17f37a4 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java @@ -1,9 +1,10 @@ package io.pzstorm.storm.core; -import com.github.difflib.DiffUtils; -import com.github.difflib.UnifiedDiffUtils; -import com.github.difflib.patch.Patch; -import io.pzstorm.storm.StormLogger; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; + import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; @@ -11,10 +12,11 @@ import org.objectweb.asm.util.Textifier; import org.objectweb.asm.util.TraceMethodVisitor; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Arrays; -import java.util.List; +import com.github.difflib.DiffUtils; +import com.github.difflib.UnifiedDiffUtils; +import com.github.difflib.patch.Patch; + +import io.pzstorm.storm.StormLogger; /** * This class represents a {@code Class} transformer used to alter {@code Class} @@ -30,30 +32,12 @@ public abstract class StormClassTestTransformer extends StormClassTransformer { super(className); } - @Override - public byte[] transform(byte[] rawClass) { - - read(rawClass).visit(); - String original = getBytecodeFor(visitor); - - byte[] result = transform().write(); - String modifier = getBytecodeFor(visitor); - - StormLogger.info("Generating class bytecode diff"); - String diff = getBytecodeDiff(className, original, modifier); - if (!diff.isEmpty()) { - StormLogger.info(diff); - } - else StormLogger.warn("No diff available, class bytecode is identical"); - return result; - } - /** * Returns the text constructed from given node's bytecode. + * * @param insnNode node to construct the text for. */ private static String getBytecodeFor(AbstractInsnNode insnNode) { - insnNode.accept(METHOD_PRINTER); StringWriter stringWriter = new StringWriter(); @@ -65,15 +49,16 @@ private static String getBytecodeFor(AbstractInsnNode insnNode) { /** * Returns the text constructed from given {@link ClassNode} bytecode. + * * @param classNode node to construct the text for. */ private static String getBytecodeFor(ClassNode classNode) { StringBuilder builder = new StringBuilder(); - for (MethodNode methodNode: classNode.methods) + for (MethodNode methodNode : classNode.methods) { builder.append(String.format("Method %s -> %s\n", methodNode.name, methodNode.desc)); - for (AbstractInsnNode insnNode: methodNode.instructions) { + for (AbstractInsnNode insnNode : methodNode.instructions) { builder.append(String.format(".... %s", getBytecodeFor(insnNode))); } } @@ -87,7 +72,6 @@ private static String getBytecodeFor(ClassNode classNode) { * @param className filename of the original (unrevised file). * @param originalCode lines of the original file. * @param modifiedCode lines of the modified file. - * * @return bytecode difference between two set of code lines. */ private static String getBytecodeDiff(String className, String originalCode, String modifiedCode) { @@ -107,4 +91,22 @@ private static String getBytecodeDiff(String className, String originalCode, Str } return builder.toString(); } + + @Override + public byte[] transform(byte[] rawClass) { + + read(rawClass).visit(); + String original = getBytecodeFor(visitor); + + byte[] result = transform().write(); + String modifier = getBytecodeFor(visitor); + + StormLogger.info("Generating class bytecode diff"); + String diff = getBytecodeDiff(className, original, modifier); + if (!diff.isEmpty()) { + StormLogger.info(diff); + } + else StormLogger.warn("No diff available, class bytecode is identical"); + return result; + } } diff --git a/src/zombie/java/zombie/ZombieHello.java b/src/zombie/java/zombie/ZombieHello.java index dd5cd35..68191ae 100644 --- a/src/zombie/java/zombie/ZombieHello.java +++ b/src/zombie/java/zombie/ZombieHello.java @@ -1,7 +1,5 @@ package zombie; -import java.io.FileNotFoundException; - import org.jetbrains.annotations.TestOnly; @TestOnly From d7bb9bf202caa45fbd24a374fb144d359e51d48c Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 2 May 2021 21:17:13 +0200 Subject: [PATCH 085/323] Create ASM utility class --- src/main/java/io/pzstorm/storm/util/AsmUtils.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/util/AsmUtils.java diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java new file mode 100644 index 0000000..aae40f0 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -0,0 +1,14 @@ +package io.pzstorm.storm.util; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.*; + +import java.util.Arrays; +import java.util.List; + +/** + * This class contains an assortment of helpful methods when working with ASM. + */ +public class AsmUtils { + +} From 503de2c888c370328c7bde82f3f656dc2e2631fa Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 2 May 2021 21:18:24 +0200 Subject: [PATCH 086/323] Add createInsnList utility method --- .../java/io/pzstorm/storm/util/AsmUtils.java | 13 +++ .../io/pzstorm/storm/util/AsmUtilsTest.java | 109 ++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java index aae40f0..0544eef 100644 --- a/src/main/java/io/pzstorm/storm/util/AsmUtils.java +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -11,4 +11,17 @@ */ public class AsmUtils { + /** + * Create a new {@link InsnList} from given array of instructions. + * + * @param instructions array of instructions to use. + */ + public static InsnList createInsnList(AbstractInsnNode... instructions) { + + InsnList result = new InsnList(); + for (AbstractInsnNode instruction : instructions) { + result.add(instruction); + } + return result; + } } diff --git a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java new file mode 100644 index 0000000..ddfde72 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java @@ -0,0 +1,109 @@ +package io.pzstorm.storm.util; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.pzstorm.storm.UnitTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +class AsmUtilsTest implements UnitTest { + + private static final String intDesc = "(I)Ljava/lang/Integer;"; + private static final LabelNode[] LABELS = new LabelNode[]{ + new LabelNode(), new LabelNode(), new LabelNode(), + new LabelNode(), new LabelNode() + }; + private static final AbstractInsnNode LAST_METHOD_NODE = + new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", intDesc); + private static final AbstractInsnNode LAST_LINE_NODE = new LineNumberNode(25, LABELS[2]); + private static final AbstractInsnNode LAST_VAR_NODE = new VarInsnNode(Opcodes.ALOAD, 1); + + private final InsnList instructions = createInstructionList(); + private static InsnList createInstructionList() { + + InsnList result = new InsnList(); + /* L0 + * LINENUMBER 20 L0 + * NEW java/util/Random + * DUP + * INVOKESPECIAL java/util/Random. ()V + * ASTORE 0 + */ + result.add(LABELS[0]); + result.add(new LineNumberNode(20, LABELS[0])); + result.add(new TypeInsnNode(Opcodes.NEW, "java/util/Random")); + result.add(new InsnNode(Opcodes.POP)); + result.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/util/Random", "", "()V")); + result.add(new VarInsnNode(Opcodes.ASTORE, 0)); + /* + * L1 + * LINENUMBER 22 L1 + * ALOAD 0 + * INVOKEVIRTUAL java/util/Random.nextInt ()I + * INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; + * ASTORE 1 + */ + result.add(LABELS[1]); + result.add(new LineNumberNode(22, LABELS[1])); + result.add(new VarInsnNode(Opcodes.ALOAD, 0)); + result.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/util/Random", "nextInt", "()I")); + result.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", intDesc)); + result.add(new VarInsnNode(Opcodes.ASTORE, 1)); + /* + * L2 + * LINENUMBER 23 L2 + * ALOAD 1 + * INVOKEVIRTUAL java/lang/Integer.intValue ()I + * NEW java/util/Random + * DUP + * INVOKESPECIAL java/util/Random. ()V + * INVOKEVIRTUAL java/util/Random.nextInt ()I + * IADD + * INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; + * ASTORE 1 + */ + result.add(LABELS[2]); + result.add(new LineNumberNode(23, LABELS[2])); + result.add(new VarInsnNode(Opcodes.ALOAD, 1)); + result.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I")); + result.add(new TypeInsnNode(Opcodes.NEW, "java/util/Random")); + result.add(new InsnNode(Opcodes.DUP)); + result.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/util/Random", "", "()V")); + result.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/util/Random", "nextInt", "()I")); + result.add(new InsnNode(Opcodes.IADD)); + result.add(LAST_METHOD_NODE); + result.add(new VarInsnNode(Opcodes.ASTORE, 1)); + /* + * L3 + * LINENUMBER 25 L3 + * ALOAD 1 + * ARETURN + */ + result.add(LABELS[3]); + result.add(LAST_LINE_NODE); + result.add(LAST_VAR_NODE); + result.add(new InsnNode(Opcodes.ARETURN)); + result.add(LABELS[4]); + + return result; + } + + @Test + void shouldCreateInstructionListFromArguments() { + + AbstractInsnNode[] insnList = new AbstractInsnNode[instructions.size()]; + for (int i = 0; i < instructions.size(); i++) { + insnList[i] = instructions.get(i); + } + InsnList actualList = AsmUtils.createInsnList(insnList); + for (int i = 0; i < actualList.size(); i++) { + Assertions.assertEquals(instructions.get(i), actualList.get(i)); + } + } +} From 9b2ca1dde1010a9e51fc8b8c2477cf81a0933a43 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 2 May 2021 21:19:08 +0200 Subject: [PATCH 087/323] Add node equality check utility method --- .../java/io/pzstorm/storm/util/AsmUtils.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java index 0544eef..74d2ce3 100644 --- a/src/main/java/io/pzstorm/storm/util/AsmUtils.java +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -24,4 +24,114 @@ public static InsnList createInsnList(AbstractInsnNode... instructions) { } return result; } + /** + * Compare the given nodes and return {@code true} if they are equal. + * Since instances of {@link AbstractInsnNode} do not implement a equality comparison, + * comparing two node instances will compare object addresses and omit comparing fields. + * This method does a deep comparison, comparing instance fields. + * + * @param a first node to compare. + * @param b second node to compare. + * @return {@code true} if node fields are equal, {@code false} otherwise. + */ + public static boolean equalNodes(AbstractInsnNode a, AbstractInsnNode b) { + + if (!a.getClass().isInstance(b)) { + return false; + } + if (a.getType() != b.getType()) { + return false; + } + if (a.getOpcode() != b.getOpcode()) { + return false; + } + if (a instanceof FieldInsnNode) + { + return ((FieldInsnNode) a).owner.equals(((FieldInsnNode) b).owner) && + ((FieldInsnNode) a).name.equals(((FieldInsnNode) b).name) && + ((FieldInsnNode) a).desc.equals(((FieldInsnNode) b).desc); + } + if (a instanceof FrameNode) + { + return ((FrameNode) a).local.equals(((FrameNode) b).local) && + ((FrameNode) a).stack.equals(((FrameNode) b).stack); + } + if (a instanceof IincInsnNode) + { + return ((IincInsnNode) a).var == ((IincInsnNode) b).var && + ((IincInsnNode) a).incr == ((IincInsnNode) b).incr; + } + if (a instanceof IntInsnNode) { + return ((IntInsnNode) a).operand == ((IntInsnNode) b).operand; + } + if (a instanceof InvokeDynamicInsnNode) + { + return ((InvokeDynamicInsnNode) a).name.equals(((InvokeDynamicInsnNode) b).name) && + ((InvokeDynamicInsnNode) a).desc.equals(((InvokeDynamicInsnNode) b).desc) && + ((InvokeDynamicInsnNode) a).bsm.equals(((InvokeDynamicInsnNode) b).bsm) && + Arrays.equals(((InvokeDynamicInsnNode) a).bsmArgs, ((InvokeDynamicInsnNode) b).bsmArgs); + } + if (a instanceof JumpInsnNode) { + return AsmUtils.equalNodes(((JumpInsnNode) a).label, ((JumpInsnNode) b).label); + } + if (a instanceof LabelNode) { + return ((LabelNode) a).getLabel().equals(((LabelNode) b).getLabel()); + } + if (a instanceof LdcInsnNode) { + return ((LdcInsnNode) a).cst.equals(((LdcInsnNode) b).cst); + } + if (a instanceof LineNumberNode) + { + return ((LineNumberNode) a).line == ((LineNumberNode) b).line && + AsmUtils.equalNodes(((LineNumberNode) a).start, ((LineNumberNode) b).start); + } + if (a instanceof LookupSwitchInsnNode) + { + return (AsmUtils.equalNodes(((LookupSwitchInsnNode) a).dflt, ((LookupSwitchInsnNode) b).dflt) && + ((LookupSwitchInsnNode) a).keys.equals(((LookupSwitchInsnNode) b).keys) && + ((LookupSwitchInsnNode) a).labels.equals(((LookupSwitchInsnNode) b).labels)); + } + if (a instanceof MethodInsnNode) + { + return ((MethodInsnNode) a).owner.equals(((MethodInsnNode) b).owner) && + ((MethodInsnNode) a).name.equals(((MethodInsnNode) b).name) && + ((MethodInsnNode) a).desc.equals(((MethodInsnNode) b).desc) && + ((MethodInsnNode) a).itf == (((MethodInsnNode) b).itf); + } + if (a instanceof MultiANewArrayInsnNode) + { + return ((MultiANewArrayInsnNode) a).desc.equals(((MultiANewArrayInsnNode) b).desc) && + ((MultiANewArrayInsnNode) a).dims == ((MultiANewArrayInsnNode) b).dims; + } + if (a instanceof TableSwitchInsnNode) + { + if (((TableSwitchInsnNode) a).min != ((TableSwitchInsnNode) b).min) { + return false; + } + if (((TableSwitchInsnNode) a).max != ((TableSwitchInsnNode) b).max) { + return false; + } + if (!AsmUtils.equalNodes(((TableSwitchInsnNode) a).dflt, ((TableSwitchInsnNode) b).dflt)) { + return false; + } + List aLabelNodes = ((TableSwitchInsnNode) a).labels; + List bLabelNodes = ((TableSwitchInsnNode) b).labels; + if (aLabelNodes.size() != bLabelNodes.size()) { + return false; + } + for (int i = 0; i < ((TableSwitchInsnNode) a).labels.size(); i++) + { + if (!AsmUtils.equalNodes(aLabelNodes.get(i), bLabelNodes.get(i))) { + return false; + } + } + } + if (a instanceof TypeInsnNode) { + return ((TypeInsnNode) a).desc.equals(((TypeInsnNode) b).desc); + } + if (a instanceof VarInsnNode) { + return ((VarInsnNode) a).var == (((VarInsnNode) b).var); + } + return true; + } } From 440946b98f6e997c8fd190c1d3c00530cb9fa1a9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 2 May 2021 21:19:57 +0200 Subject: [PATCH 088/323] Add getLastInstructionOfType utility method --- .../java/io/pzstorm/storm/util/AsmUtils.java | 20 +++++++++++++++++++ .../io/pzstorm/storm/util/AsmUtilsTest.java | 15 ++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java index 74d2ce3..6761dab 100644 --- a/src/main/java/io/pzstorm/storm/util/AsmUtils.java +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -24,6 +24,26 @@ public static InsnList createInsnList(AbstractInsnNode... instructions) { } return result; } + + /** + * Finds and returns the last instructions of a specific {@code Class} in a given list. + * + * @param list list of instructions to search. + * @param node {@link AbstractInsnNode} to match. + * @param type of instruction to match. + */ + @SuppressWarnings("unchecked") + public static @Nullable T getLastInstructionOfType(InsnList list, Class node) { + + AbstractInsnNode result = null; + for (AbstractInsnNode instruction : list) + { + if (node.isInstance(instruction)) { + result = instruction; + } + } + return (T) result; + } /** * Compare the given nodes and return {@code true} if they are equal. * Since instances of {@link AbstractInsnNode} do not implement a equality comparison, diff --git a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java index ddfde72..3350a62 100644 --- a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java @@ -106,4 +106,19 @@ void shouldCreateInstructionListFromArguments() { Assertions.assertEquals(instructions.get(i), actualList.get(i)); } } + + @Test + @SuppressWarnings("ConstantConditions") + void shouldGetLastInstructionOfType() { + + Map instructionsOfType = ImmutableMap.of( + LABELS[4], AsmUtils.getLastInstructionOfType(instructions, LabelNode.class), + LAST_METHOD_NODE, AsmUtils.getLastInstructionOfType(instructions, MethodInsnNode.class), + LAST_LINE_NODE, AsmUtils.getLastInstructionOfType(instructions, LineNumberNode.class), + LAST_VAR_NODE, AsmUtils.getLastInstructionOfType(instructions, VarInsnNode.class) + ); + for (Map.Entry entry : instructionsOfType.entrySet()) { + Assertions.assertEquals(entry.getKey(), entry.getValue()); + } + } } From a5f626582137434f3547fcfe8ef391a1034ae98c Mon Sep 17 00:00:00 2001 From: Matthew Date: Sun, 2 May 2021 21:20:23 +0200 Subject: [PATCH 089/323] Add getFirstMatchingLabelNode utility method --- .../java/io/pzstorm/storm/util/AsmUtils.java | 31 +++++++++++++++++++ .../io/pzstorm/storm/util/AsmUtilsTest.java | 20 ++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java index 6761dab..9481753 100644 --- a/src/main/java/io/pzstorm/storm/util/AsmUtils.java +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -44,6 +44,37 @@ public static InsnList createInsnList(AbstractInsnNode... instructions) { } return (T) result; } + + /** + * Find and return first {@link LabelNode} that contains given list of instructions. + * + * @param list list of instructions to search. + * @param match list of instructions to match. + * @return first {@code LabelNode} that was matched or {@code null} if no node was matched. + */ + public static @Nullable LabelNode getFirstMatchingLabelNode(InsnList list, List match) { + + //@formatter:off + for (int i1 = 0; i1 < list.size(); i1++) + { + AbstractInsnNode labelInstruction = list.get(i1); + if (labelInstruction instanceof LabelNode) + { + boolean matchedInstructions = true; + for (int i2 = 0; i2 < match.size() && i1 < list.size(); i2++, i1++) + { + if (!AsmUtils.equalNodes(list.get(i1), match.get(i2))) { + matchedInstructions = false; break; + } + } + if (matchedInstructions) { + return (LabelNode) labelInstruction; + } + } + }//@formatter:on + return null; + } + /** * Compare the given nodes and return {@code true} if they are equal. * Since instances of {@link AbstractInsnNode} do not implement a equality comparison, diff --git a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java index 3350a62..a28b89a 100644 --- a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java @@ -121,4 +121,24 @@ void shouldGetLastInstructionOfType() { Assertions.assertEquals(entry.getKey(), entry.getValue()); } } + + @Test + void shouldGetFirstMatchingLabelNodeInstruction() { + + List labelIndexPairs = ImmutableList.of( + new Integer[] { 0, 6 }, new Integer[] { 6, 12 }, + new Integer[] { 12, 23 }, new Integer[] { 23, 27 } + ); + for (int i1 = 0; i1 < labelIndexPairs.size(); i1++) + { + List insnToMatch = new ArrayList<>(); + Integer[] labelIndexPair = labelIndexPairs.get(i1); + for (int i2 = labelIndexPair[0]; i2 < labelIndexPair[1]; i2++) { + insnToMatch.add(instructions.get(i2)); + } + LabelNode expectedLabel = LABELS[i1]; + LabelNode actualLabel = AsmUtils.getFirstMatchingLabelNode(createInstructionList(), insnToMatch); + Assertions.assertEquals(expectedLabel, actualLabel); + } + } } From a537a58d9e2045d3b348117a40c35523189a0a77 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 02:25:55 +0200 Subject: [PATCH 090/323] Improve bytecode diff log format --- .../java/io/pzstorm/storm/core/StormClassTestTransformer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java index 17f37a4..b1fd606 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java @@ -104,7 +104,8 @@ public byte[] transform(byte[] rawClass) { StormLogger.info("Generating class bytecode diff"); String diff = getBytecodeDiff(className, original, modifier); if (!diff.isEmpty()) { - StormLogger.info(diff); + //noinspection UseOfSystemOutOrSystemErr + System.out.println(diff); } else StormLogger.warn("No diff available, class bytecode is identical"); return result; From 1f96e0a72fabd0f4fbba4d0dfe1916aa7acc196f Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 02:33:17 +0200 Subject: [PATCH 091/323] Fix and improve matching LabelNode --- .../java/io/pzstorm/storm/util/AsmUtils.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java index 9481753..abd3d05 100644 --- a/src/main/java/io/pzstorm/storm/util/AsmUtils.java +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -47,6 +47,8 @@ public static InsnList createInsnList(AbstractInsnNode... instructions) { /** * Find and return first {@link LabelNode} that contains given list of instructions. + * Note that {@link LineNumberNode} and {@link LabelNode} instructions will be excluded when + * comparing entries which means that these instructions are safe to be included in {@code match} list. * * @param list list of instructions to search. * @param match list of instructions to match. @@ -57,18 +59,31 @@ public static InsnList createInsnList(AbstractInsnNode... instructions) { //@formatter:off for (int i1 = 0; i1 < list.size(); i1++) { - AbstractInsnNode labelInstruction = list.get(i1); - if (labelInstruction instanceof LabelNode) + AbstractInsnNode instruction = list.get(i1); + if (instruction instanceof LabelNode) { + int i3 = i1 + 1; boolean matchedInstructions = true; - for (int i2 = 0; i2 < match.size() && i1 < list.size(); i2++, i1++) + for (int i2 = 0; i2 < match.size() && i3 < list.size(); i2++, i3++) { - if (!AsmUtils.equalNodes(list.get(i1), match.get(i2))) { - matchedInstructions = false; break; - } + AbstractInsnNode a = list.get(i3); + // ignore line number nodes + if (!(a instanceof LineNumberNode)) + { + AbstractInsnNode b = match.get(i2); + // ignore label and line number nodes + if (!(b instanceof LabelNode) && !(b instanceof LineNumberNode)) + { + if (!AsmUtils.equalNodes(a, b)) { + matchedInstructions = false; break; + } + } + else i3 -= 1; // when ignoring nodes counter variables + } // need to be adjusted to compensate for + else i2 -= 1; // the for-loop auto-incremental operation } if (matchedInstructions) { - return (LabelNode) labelInstruction; + return (LabelNode) instruction; } } }//@formatter:on From 9064ab52b14018ffbd2135b9300c3e4ce498ca9a Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 02:34:17 +0200 Subject: [PATCH 092/323] Do minor code refactoring --- .../storm/core/StormClassTransformerTest.java | 17 ++++++++--------- src/zombie/java/zombie/ZombieHello.java | 5 ++++- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java index a8f225e..7e50196 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java @@ -13,15 +13,7 @@ class StormClassTransformerTest implements UnitTest { void shouldChangeStackConstantInInstructionList() throws ReflectiveOperationException { String className = "zombie.ZombieHello"; - - // create and register transformer - Class transformerClass = Class.forName( - "io.pzstorm.storm.core.ZombieHelloTransformer", - true, StormBootstrap.CLASS_LOADER - ); - Constructor constructor = transformerClass.getConstructor(); - constructor.setAccessible(true); - constructor.newInstance(); + createAndLoadTransformer("io.pzstorm.storm.core.ZombieHelloTransformer"); Class zombieHello = StormBootstrap.CLASS_LOADER.loadClass(className, true); String hello = (String) zombieHello.getDeclaredMethod("getHello").invoke(null); @@ -29,4 +21,11 @@ void shouldChangeStackConstantInInstructionList() throws ReflectiveOperationExce zombieHello.getDeclaredMethod("sayHello").invoke(null); } + private static void createAndLoadTransformer(String transformer) throws ReflectiveOperationException{ + + Class transformerClass = Class.forName(transformer, true, StormBootstrap.CLASS_LOADER); + Constructor constructor = transformerClass.getConstructor(); + constructor.setAccessible(true); + constructor.newInstance(); + } } diff --git a/src/zombie/java/zombie/ZombieHello.java b/src/zombie/java/zombie/ZombieHello.java index 68191ae..ebea7c0 100644 --- a/src/zombie/java/zombie/ZombieHello.java +++ b/src/zombie/java/zombie/ZombieHello.java @@ -1,12 +1,15 @@ package zombie; +import io.pzstorm.storm.StormLogger; import org.jetbrains.annotations.TestOnly; + @TestOnly +@SuppressWarnings({ "unused", "WeakerAccess" }) public class ZombieHello { public static void sayHello() { - System.out.println(getHello()); + StormLogger.info(getHello()); } public static String getHello() { From b3e82df20411289d333f88a35c4cdc812160400e Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 02:34:52 +0200 Subject: [PATCH 093/323] Write ASM utility test stub --- src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java index a28b89a..6bdac46 100644 --- a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java @@ -141,4 +141,14 @@ void shouldGetFirstMatchingLabelNodeInstruction() { Assertions.assertEquals(expectedLabel, actualLabel); } } + + @Test + void shouldCompareInstructionNodeFields() { + + // TODO: finish writing this test + + FieldInsnNode fia = new FieldInsnNode(Opcodes.GETSTATIC, "owner", "name", "desc"); + FieldInsnNode fib = new FieldInsnNode(Opcodes.GETSTATIC, "owner", "name", "desc"); + Assertions.assertTrue((AsmUtils.equalNodes(fia, fib))); + } } From 601022b18bee9546b441b37f3a64796773161dc8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 02:35:50 +0200 Subject: [PATCH 094/323] Test inserting instruction before LabelNode --- .../storm/core/StormClassTransformerTest.java | 18 +++++++++++ .../storm/core/ZombieUtilsTransformer.java | 31 +++++++++++++++++++ src/zombie/java/zombie/ZombieUtils.java | 19 ++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/core/ZombieUtilsTransformer.java create mode 100644 src/zombie/java/zombie/ZombieUtils.java diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java index 7e50196..8d5f892 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java @@ -21,6 +21,24 @@ void shouldChangeStackConstantInInstructionList() throws ReflectiveOperationExce zombieHello.getDeclaredMethod("sayHello").invoke(null); } + + @Test + void shouldInsertInstructionBeforeMatchedLabel() throws ReflectiveOperationException { + + String className = "zombie.ZombieUtils"; + createAndLoadTransformer("io.pzstorm.storm.core.ZombieUtilsTransformer"); + + Class zombieUtils = StormBootstrap.CLASS_LOADER.loadClass(className, true); + zombieUtils.getDeclaredMethod("setZombieProperties", int.class, boolean.class) + .invoke(null, 21, true); + + int propertyA = (int) zombieUtils.getDeclaredField("zombiePropertyA").get(null); + boolean propertyC = (boolean) zombieUtils.getDeclaredField("zombiePropertyC").get(null); + + Assertions.assertEquals(21, propertyA); + Assertions.assertTrue(propertyC); + } + private static void createAndLoadTransformer(String transformer) throws ReflectiveOperationException{ Class transformerClass = Class.forName(transformer, true, StormBootstrap.CLASS_LOADER); diff --git a/src/test/java/io/pzstorm/storm/core/ZombieUtilsTransformer.java b/src/test/java/io/pzstorm/storm/core/ZombieUtilsTransformer.java new file mode 100644 index 0000000..29a6237 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/core/ZombieUtilsTransformer.java @@ -0,0 +1,31 @@ +package io.pzstorm.storm.core; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +import com.google.common.collect.ImmutableList; + +import io.pzstorm.storm.util.AsmUtils; + +@SuppressWarnings("unused") +public class ZombieUtilsTransformer extends StormClassTestTransformer { + + public ZombieUtilsTransformer() { + super("zombie.ZombieUtils"); + } + + @Override + StormClassTransformer transform() { + + InsnList instructions = getInstructionsForMethod("setZombieProperties", "(IZ)V"); + AbstractInsnNode target = AsmUtils.getFirstMatchingLabelNode(instructions, ImmutableList.of( + new VarInsnNode(Opcodes.ILOAD, 1), + new FieldInsnNode(Opcodes.PUTSTATIC, "zombie/ZombieUtils", "zombiePropertyC", "Z") + )); + instructions.insertBefore(target, AsmUtils.createInsnList( + new LabelNode(), new LdcInsnNode("property"), + new FieldInsnNode(Opcodes.PUTSTATIC, "zombie/ZombieUtils", "zombiePropertyB", "Ljava/lang/String;") + )); + return this; + } +} diff --git a/src/zombie/java/zombie/ZombieUtils.java b/src/zombie/java/zombie/ZombieUtils.java new file mode 100644 index 0000000..903f191 --- /dev/null +++ b/src/zombie/java/zombie/ZombieUtils.java @@ -0,0 +1,19 @@ +package zombie; + +@SuppressWarnings({ "WeakerAccess", "unused" }) +public class ZombieUtils { + + public static int zombiePropertyA = 0; + public static String zombiePropertyB = ""; + public static boolean zombiePropertyC = false; + + public static void setZombieProperties(int a, boolean c) { + + zombiePropertyA = a; + + // inject instruction to set zombiePropertyB here + //zombiePropertyB = "property"; + + zombiePropertyC = c; + } +} From 0b4efd35ba01bc2fcc52282e89baa970179b803f Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 02:38:59 +0200 Subject: [PATCH 095/323] Do test code formatting --- .../java/io/pzstorm/storm/util/AsmUtils.java | 6 ++--- .../storm/core/StormClassTestTransformer.java | 3 ++- .../storm/core/StormClassTransformerTest.java | 16 +++++++------- .../io/pzstorm/storm/util/AsmUtilsTest.java | 22 ++++++++++--------- src/zombie/java/zombie/ZombieHello.java | 2 +- src/zombie/java/zombie/ZombieUtils.java | 2 -- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java index abd3d05..a271105 100644 --- a/src/main/java/io/pzstorm/storm/util/AsmUtils.java +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -1,11 +1,11 @@ package io.pzstorm.storm.util; -import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.tree.*; - import java.util.Arrays; import java.util.List; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.tree.*; + /** * This class contains an assortment of helpful methods when working with ASM. */ diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java index b1fd606..91a4b4a 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java @@ -103,7 +103,8 @@ public byte[] transform(byte[] rawClass) { StormLogger.info("Generating class bytecode diff"); String diff = getBytecodeDiff(className, original, modifier); - if (!diff.isEmpty()) { + if (!diff.isEmpty()) + { //noinspection UseOfSystemOutOrSystemErr System.out.println(diff); } diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java index 8d5f892..f6c6cdb 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java @@ -9,6 +9,14 @@ class StormClassTransformerTest implements UnitTest { + private static void createAndLoadTransformer(String transformer) throws ReflectiveOperationException { + + Class transformerClass = Class.forName(transformer, true, StormBootstrap.CLASS_LOADER); + Constructor constructor = transformerClass.getConstructor(); + constructor.setAccessible(true); + constructor.newInstance(); + } + @Test void shouldChangeStackConstantInInstructionList() throws ReflectiveOperationException { @@ -38,12 +46,4 @@ void shouldInsertInstructionBeforeMatchedLabel() throws ReflectiveOperationExcep Assertions.assertEquals(21, propertyA); Assertions.assertTrue(propertyC); } - - private static void createAndLoadTransformer(String transformer) throws ReflectiveOperationException{ - - Class transformerClass = Class.forName(transformer, true, StormBootstrap.CLASS_LOADER); - Constructor constructor = transformerClass.getConstructor(); - constructor.setAccessible(true); - constructor.newInstance(); - } } diff --git a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java index 6bdac46..7305359 100644 --- a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java @@ -1,16 +1,18 @@ package io.pzstorm.storm.util; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import io.pzstorm.storm.UnitTest; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import io.pzstorm.storm.UnitTest; class AsmUtilsTest implements UnitTest { @@ -25,6 +27,7 @@ class AsmUtilsTest implements UnitTest { private static final AbstractInsnNode LAST_VAR_NODE = new VarInsnNode(Opcodes.ALOAD, 1); private final InsnList instructions = createInstructionList(); + private static InsnList createInstructionList() { InsnList result = new InsnList(); @@ -100,7 +103,7 @@ void shouldCreateInstructionListFromArguments() { AbstractInsnNode[] insnList = new AbstractInsnNode[instructions.size()]; for (int i = 0; i < instructions.size(); i++) { insnList[i] = instructions.get(i); - } + } InsnList actualList = AsmUtils.createInsnList(insnList); for (int i = 0; i < actualList.size(); i++) { Assertions.assertEquals(instructions.get(i), actualList.get(i)); @@ -126,8 +129,8 @@ void shouldGetLastInstructionOfType() { void shouldGetFirstMatchingLabelNodeInstruction() { List labelIndexPairs = ImmutableList.of( - new Integer[] { 0, 6 }, new Integer[] { 6, 12 }, - new Integer[] { 12, 23 }, new Integer[] { 23, 27 } + new Integer[]{ 0, 6 }, new Integer[]{ 6, 12 }, + new Integer[]{ 12, 23 }, new Integer[]{ 23, 27 } ); for (int i1 = 0; i1 < labelIndexPairs.size(); i1++) { @@ -144,7 +147,6 @@ void shouldGetFirstMatchingLabelNodeInstruction() { @Test void shouldCompareInstructionNodeFields() { - // TODO: finish writing this test FieldInsnNode fia = new FieldInsnNode(Opcodes.GETSTATIC, "owner", "name", "desc"); diff --git a/src/zombie/java/zombie/ZombieHello.java b/src/zombie/java/zombie/ZombieHello.java index ebea7c0..33f1118 100644 --- a/src/zombie/java/zombie/ZombieHello.java +++ b/src/zombie/java/zombie/ZombieHello.java @@ -1,8 +1,8 @@ package zombie; -import io.pzstorm.storm.StormLogger; import org.jetbrains.annotations.TestOnly; +import io.pzstorm.storm.StormLogger; @TestOnly @SuppressWarnings({ "unused", "WeakerAccess" }) diff --git a/src/zombie/java/zombie/ZombieUtils.java b/src/zombie/java/zombie/ZombieUtils.java index 903f191..06e42eb 100644 --- a/src/zombie/java/zombie/ZombieUtils.java +++ b/src/zombie/java/zombie/ZombieUtils.java @@ -10,10 +10,8 @@ public class ZombieUtils { public static void setZombieProperties(int a, boolean c) { zombiePropertyA = a; - // inject instruction to set zombiePropertyB here //zombiePropertyB = "property"; - zombiePropertyC = c; } } From 2e95301d578d9ffcc9b00aa757102d1188638bcf Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 05:41:54 +0200 Subject: [PATCH 096/323] Improve bootstrap loaded check --- .../io/pzstorm/storm/core/StormBootstrap.java | 19 +++++++++++++++++++ .../pzstorm/storm/core/StormClassLoader.java | 3 +-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormBootstrap.java b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java index 0142261..c5fe93e 100644 --- a/src/main/java/io/pzstorm/storm/core/StormBootstrap.java +++ b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java @@ -39,6 +39,13 @@ class StormBootstrap { */ private static final Method TRANSFORMER_INVOKER; + /** + * Marks the {@code StormBoostrap} as being fully loaded. This variable will be + * {@code true} when the static block has finished initializing. Required by classes + * that are loaded before {@code StormBoostrap} but still depend on it. + */ + private static final boolean hasLoaded; + static { try { @@ -47,6 +54,9 @@ class StormBootstrap { TRANSFORMER_GETTER = TRANSFORMER_CLASS.getDeclaredMethod("getRegistered", String.class); TRANSFORMER_INVOKER = TRANSFORMER_CLASS.getDeclaredMethod("transform", byte[].class); + + // mark StormBootstrap as finished loading + hasLoaded = true; } catch (ReflectiveOperationException e) { throw new RuntimeException(e); @@ -73,4 +83,13 @@ static Object getRegisteredTransformer(String name) throws ReflectiveOperationEx static byte[] invokeTransformer(Object transformer, byte[] rawClass) throws ReflectiveOperationException { return (byte[]) TRANSFORMER_INVOKER.invoke(transformer, (Object) rawClass); } + + /** + * Returns if {@code StormBoostrap} has finished loading. + * + * @return {@code true} if boostrap has been fully loaded. + */ + static boolean hasLoaded() { + return hasLoaded; + } } diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index 95107a4..9ea275e 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -143,8 +143,7 @@ private Class loadClassInternal(String name, boolean resolve) throws ClassNot try { byte[] input = getRawClassByteArray(name); - Class transformerClass = StormBootstrap.TRANSFORMER_CLASS; - if (transformerClass != null) + if (StormBootstrap.hasLoaded()) { Object transformer = StormBootstrap.getRegisteredTransformer(name); if (transformer != null) { From dfdaaf4d621f9fe60123c3b12238611e626797c6 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 3 May 2021 05:43:37 +0200 Subject: [PATCH 097/323] Migrate transformer registry map --- .../io/pzstorm/storm/core/StormBootstrap.java | 22 ++++++---- .../storm/core/StormClassTransformer.java | 36 +--------------- .../storm/core/StormClassTransformers.java | 42 +++++++++++++++++++ .../io/pzstorm/storm/core/StormLauncher.java | 2 +- 4 files changed, 60 insertions(+), 42 deletions(-) create mode 100644 src/main/java/io/pzstorm/storm/core/StormClassTransformers.java diff --git a/src/main/java/io/pzstorm/storm/core/StormBootstrap.java b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java index c5fe93e..60e3461 100644 --- a/src/main/java/io/pzstorm/storm/core/StormBootstrap.java +++ b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java @@ -23,10 +23,15 @@ class StormBootstrap { * to {@code AppClassLoader}. For this reason we have to use bootstrapping and reflection to * access transformers from {@code StormClassLoader}. */ - static final Class TRANSFORMER_CLASS; + private static final Class TRANSFORMER_CLASS; /** - * Represents {@link StormClassTransformer#getRegistered(String)} method. + * Loaded and initialized {@link StormClassTransformers} {@code Class}. + */ + private static final Class TRANSFORMERS_CLASS; + + /** + * Represents {@link StormClassTransformers#getRegistered(String)} method. * * @see #getRegisteredTransformer(String) */ @@ -49,10 +54,13 @@ class StormBootstrap { static { try { - String transformerClass = "io.pzstorm.storm.core.StormClassTransformer"; - TRANSFORMER_CLASS = Class.forName(transformerClass, true, CLASS_LOADER); - - TRANSFORMER_GETTER = TRANSFORMER_CLASS.getDeclaredMethod("getRegistered", String.class); + TRANSFORMER_CLASS = Class.forName( + "io.pzstorm.storm.core.StormClassTransformer", true, CLASS_LOADER + ); + TRANSFORMERS_CLASS = Class.forName( + "io.pzstorm.storm.core.StormClassTransformers", true, CLASS_LOADER + ); + TRANSFORMER_GETTER = TRANSFORMERS_CLASS.getDeclaredMethod("getRegistered", String.class); TRANSFORMER_INVOKER = TRANSFORMER_CLASS.getDeclaredMethod("transform", byte[].class); // mark StormBootstrap as finished loading @@ -67,7 +75,7 @@ class StormBootstrap { * Returns registered instance of {@link StormClassTransformer} that matches the given name. * * @throws ReflectiveOperationException if an error occurred while invoking method. - * @see StormClassTransformer#getRegistered(String) + * @see StormClassTransformers#getRegistered(String) */ static Object getRegisteredTransformer(String name) throws ReflectiveOperationException { return TRANSFORMER_GETTER.invoke(null, name); diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java index 524bd42..abcf3a7 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -1,7 +1,5 @@ package io.pzstorm.storm.core; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import org.jetbrains.annotations.Contract; @@ -17,10 +15,7 @@ /** * This class represents a {@code Class} transformer used to alter {@code Class} - * bytecode using ASM. When new instance of {@code StormClassTransformer} is created - * it is automatically mapped in internal registry. To check if transformer is registered - * use {@link #isRegistered(String)} and to retrieve a mapped instance use {@link #getRegistered(String)}. - * The transformation process calls the following method in fixed order: + * bytecode using ASM. The transformation process calls the following method in fixed order: *
    *
  • {@link #read(byte[])} to read given byte array with {@link ClassReader}.
  • *
  • {@link #visit()} to use the visitation system to parse the class with visitor.
  • @@ -28,16 +23,8 @@ *
  • {@link #write()} to write the class using {@link ClassWriter}
  • *
*/ -@SuppressWarnings("WeakerAccess") public abstract class StormClassTransformer { - /** - * Internal registry of created transformers. This map is checked for entries - * by {@link StormClassLoader} when loading classes and invokes the transformation - * chain of methods to transform the class before defining it via JVM. - */ - private static final Map TRANSFORMERS = new HashMap<>(); - protected final String className; protected final ClassNode visitor; private @Nullable ClassReader classReader; @@ -46,31 +33,12 @@ public abstract class StormClassTransformer { this.className = className; this.visitor = visitor; - - TRANSFORMERS.put(className, this); } StormClassTransformer(String className) { this(className, new ClassNode()); } - /** - * Returns {@code true} if transformer with given name is registered. - */ - static boolean isRegistered(String name) { - return TRANSFORMERS.containsKey(name); - } - - /** - * Returns registered instance of {@link StormClassTransformer} that matches the given name. - * - * @return {@code StormClassTransformer} or {@code null} if no registered instance found. - */ - @Contract(pure = true) - public static @Nullable StormClassTransformer getRegistered(String className) { - return TRANSFORMERS.getOrDefault(className, null); - } - /** * Returns list of instructions for method that matches given parameters. * @@ -82,7 +50,7 @@ static boolean isRegistered(String name) { * ASM User Guide - Method descriptors */ @Contract(pure = true) - final InsnList getInstructionsForMethod(String name, String descriptor) { + public final InsnList getInstructionsForMethod(String name, String descriptor) { if (classReader != null) { diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java new file mode 100644 index 0000000..05a760a --- /dev/null +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java @@ -0,0 +1,42 @@ +package io.pzstorm.storm.core; + +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class defines, initializes and stores {@link StormClassTransformer} instances. + *
    + *
  • Check if transformer is registered by calling {@link #isRegistered(String)}.
  • + *
  • Retrieve a mapped instance of registered transformer by calling {@link #getRegistered(String)}
  • + *
+ */ +public class StormClassTransformers { + + /** + * Internal registry of created transformers. This map is checked for entries + * by {@link StormClassLoader} when loading classes and invokes the transformation + * chain of methods to transform the class before defining it via JVM. + */ + private static final Map TRANSFORMERS = new HashMap<>(); + + + /** + * Returns {@code true} if transformer with given name is registered. + */ + static boolean isRegistered(String name) { + return TRANSFORMERS.containsKey(name); + } + + /** + * Returns registered instance of {@link StormClassTransformer} that matches the given name. + * + * @return {@code StormClassTransformer} or {@code null} if no registered instance found. + */ + @Contract(pure = true) + public static @Nullable StormClassTransformer getRegistered(String className) { + return TRANSFORMERS.getOrDefault(className, null); + } +} diff --git a/src/main/java/io/pzstorm/storm/core/StormLauncher.java b/src/main/java/io/pzstorm/storm/core/StormLauncher.java index 0ae7bab..773a17e 100644 --- a/src/main/java/io/pzstorm/storm/core/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/core/StormLauncher.java @@ -30,7 +30,7 @@ public static void main(String[] args) throws ReflectiveOperationException { StormLogger.debug("Preparing to launch Project Zomboid"); StormClassLoader classLoader = StormBootstrap.CLASS_LOADER; - Class.forName("io.pzstorm.storm.core.StormEventHooks", true, classLoader); + Class.forName("io.pzstorm.storm.core.StormClassTransformers", true, classLoader); Class entryPointClass = classLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); Method entryPoint = entryPointClass.getMethod(ZOMBOID_ENTRY_POINT, String[].class); From edc172f5c5f21cdf4501fda7c856b29ffa9a4156 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 5 May 2021 18:15:59 +0200 Subject: [PATCH 098/323] Bump capsid version number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d290ecb..558d069 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ import io.pzstorm.capsid.setup.xml.LaunchRunConfig import io.pzstorm.capsid.setup.VmParameter plugins { - id 'io.pzstorm.capsid' version '0.3.0' + id 'io.pzstorm.capsid' version '0.4.0' // Plugin that keeps your code spotless with Gradle // https://plugins.gradle.org/plugin/com.diffplug.spotless From bac3ae7e69a5c0ee0ccd4b1197abf1e301b806d3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 5 May 2021 18:16:10 +0200 Subject: [PATCH 099/323] Update build.gradle script --- build.gradle | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 558d069..e490c0b 100644 --- a/build.gradle +++ b/build.gradle @@ -84,7 +84,7 @@ test { testLogging.events "passed", "skipped", "failed" } -tasks.createLaunchRunConfigs.configure { +tasks.createRunConfigurations.configure { actions.clear() doLast { def vmParameterBuilder = new VmParameter.Builder() @@ -96,11 +96,12 @@ tasks.createLaunchRunConfigs.configure { } //@formatter:off LaunchRunConfig launchStorm = new LaunchRunConfig( - "Launch Storm", "io.pzstorm.storm.StormLauncher", - vmParameterBuilder.build(), [ + "Launch Storm", "io.pzstorm.storm.core.StormLauncher", + vmParameterBuilder, [ Main: new File(file(gameDir), "logs/storm/main.log").toPath() , Debug: new File(file(gameDir), "logs/storm/debug.log").toPath()] as Map )//@formatter:on + launchStorm.vmParamBuilder.build() launchStorm.configure(getProject()).writeToFile() } } From 2f3c752ece3c1f2287e89bee098e3b42691eedfd Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 5 May 2021 18:23:14 +0200 Subject: [PATCH 100/323] Remove distribution run configurations Run configurations are generated by Capsid since v0.4.0: - https://github.com/pzstorm/capsid/issues/7 - https://github.com/pzstorm/capsid/issues/8 --- .../runConfigurations/decompileZomboid.xml | 25 ----------------- .../.idea/runConfigurations/initializeMod.xml | 26 ------------------ .../.idea/runConfigurations/runZomboidDoc.xml | 25 ----------------- .../runConfigurations/setupWorkspace.xml | 27 ------------------- 4 files changed, 103 deletions(-) delete mode 100644 src/main/dist/.idea/runConfigurations/decompileZomboid.xml delete mode 100644 src/main/dist/.idea/runConfigurations/initializeMod.xml delete mode 100644 src/main/dist/.idea/runConfigurations/runZomboidDoc.xml delete mode 100644 src/main/dist/.idea/runConfigurations/setupWorkspace.xml diff --git a/src/main/dist/.idea/runConfigurations/decompileZomboid.xml b/src/main/dist/.idea/runConfigurations/decompileZomboid.xml deleted file mode 100644 index d1dd6a8..0000000 --- a/src/main/dist/.idea/runConfigurations/decompileZomboid.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - true - true - false - - - \ No newline at end of file diff --git a/src/main/dist/.idea/runConfigurations/initializeMod.xml b/src/main/dist/.idea/runConfigurations/initializeMod.xml deleted file mode 100644 index 7eb4805..0000000 --- a/src/main/dist/.idea/runConfigurations/initializeMod.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - true - true - false - - - - \ No newline at end of file diff --git a/src/main/dist/.idea/runConfigurations/runZomboidDoc.xml b/src/main/dist/.idea/runConfigurations/runZomboidDoc.xml deleted file mode 100644 index 42f41db..0000000 --- a/src/main/dist/.idea/runConfigurations/runZomboidDoc.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - true - true - false - - - \ No newline at end of file diff --git a/src/main/dist/.idea/runConfigurations/setupWorkspace.xml b/src/main/dist/.idea/runConfigurations/setupWorkspace.xml deleted file mode 100644 index f03f7f6..0000000 --- a/src/main/dist/.idea/runConfigurations/setupWorkspace.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - true - true - false - - - - \ No newline at end of file From b22ccc8852afb82615ba3e447f7800b9af163764 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 6 May 2021 15:58:25 +0200 Subject: [PATCH 101/323] Create StormUtils utility class --- src/main/java/io/pzstorm/storm/util/StormUtils.java | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/util/StormUtils.java diff --git a/src/main/java/io/pzstorm/storm/util/StormUtils.java b/src/main/java/io/pzstorm/storm/util/StormUtils.java new file mode 100644 index 0000000..c35eac9 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/util/StormUtils.java @@ -0,0 +1,5 @@ +package io.pzstorm.storm.util; + +public class StormUtils { + +} From bc505401c59acc7f7bcf4a2abd749ecd303f2798 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 6 May 2021 15:58:48 +0200 Subject: [PATCH 102/323] Add invokeRestrictedMethod utility method --- .../io/pzstorm/storm/util/StormUtils.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/util/StormUtils.java b/src/main/java/io/pzstorm/storm/util/StormUtils.java index c35eac9..33e29d8 100644 --- a/src/main/java/io/pzstorm/storm/util/StormUtils.java +++ b/src/main/java/io/pzstorm/storm/util/StormUtils.java @@ -1,5 +1,35 @@ package io.pzstorm.storm.util; +import io.pzstorm.storm.StormLogger; + +import java.lang.reflect.Method; + public class StormUtils { + /** + * Invokes the given inaccessible {@code Method} without leaving method accessible. + * After the method was invoked the {@code accessible} flag will be set to it's original state. + * + * @param method inaccessible {@code Method} to invoke. + * @param obj the object the underlying method is invoked from. + * @param args the arguments used for the method call. + * + * @throws ReflectiveOperationException if an exception occurred while invoking method. + */ + public static void invokeRestrictedMethod(Method method, Object obj, Object...args) throws ReflectiveOperationException { + + boolean wasAccessible = false; + if (method.isAccessible()) + { + StormLogger.warn("Calling 'invokeRestrictedMethod' to invoker accessible method."); + wasAccessible = true; + } + else method.setAccessible(true); + + method.invoke(obj, args); + // set accessibility state to original + if (!wasAccessible) { + method.setAccessible(false); + } + } } From c4f26727d25f5c08ac1e54281af19c77a57c28d3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 6 May 2021 16:00:13 +0200 Subject: [PATCH 103/323] Test invoking restricted methods --- .../io/pzstorm/storm/util/StormUtilsTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/util/StormUtilsTest.java diff --git a/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java new file mode 100644 index 0000000..69098e7 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java @@ -0,0 +1,34 @@ +package io.pzstorm.storm.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; + +class StormUtilsTest { + + private boolean hasInvokedRestrictedMethod = false; + + @Test + void shouldResetAccessibilityAfterInvokingRestrictedMethod() throws ReflectiveOperationException { + + Method method = StormUtilsTest.class.getDeclaredMethod("inaccessibleMethod", String.class, Integer.class); + + Assertions.assertFalse(method.isAccessible()); + StormUtils.invokeRestrictedMethod(method, this, "sample", 0); + Assertions.assertFalse(method.isAccessible()); + } + + @Test + void shouldInvokeRestrictedMethod() throws ReflectiveOperationException { + + Method method = StormUtilsTest.class.getDeclaredMethod("inaccessibleMethod", String.class, Integer.class); + + StormUtils.invokeRestrictedMethod(method, this, "sample", 0); + Assertions.assertTrue(hasInvokedRestrictedMethod); + } + + private void inaccessibleMethod(String arg1, Integer arg2) { + hasInvokedRestrictedMethod = true; + } +} From 5c3878858751d3e0de6bb3e09dc40ed10f69d712 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 00:17:17 +0200 Subject: [PATCH 104/323] Clean StormClassTransformers class --- .../storm/core/StormClassTransformers.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java index 05a760a..3a4ce00 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java @@ -1,18 +1,18 @@ package io.pzstorm.storm.core; +import java.util.HashMap; +import java.util.Map; + import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.Map; +import io.pzstorm.storm.hook.StormHook; /** * This class defines, initializes and stores {@link StormClassTransformer} instances. - *
    - *
  • Check if transformer is registered by calling {@link #isRegistered(String)}.
  • - *
  • Retrieve a mapped instance of registered transformer by calling {@link #getRegistered(String)}
  • - *
+ * To retrieve a mapped instance of registered transformer call {@link #getRegistered(String)}. */ +@SuppressWarnings("WeakerAccess") public class StormClassTransformers { /** @@ -23,11 +23,6 @@ public class StormClassTransformers { private static final Map TRANSFORMERS = new HashMap<>(); - /** - * Returns {@code true} if transformer with given name is registered. - */ - static boolean isRegistered(String name) { - return TRANSFORMERS.containsKey(name); } /** From c2f8f6f8ea696af7541015e874d8df1df5765488 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 00:30:54 +0200 Subject: [PATCH 105/323] Create StormHook interface class --- .../java/io/pzstorm/storm/hook/StormHook.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/hook/StormHook.java diff --git a/src/main/java/io/pzstorm/storm/hook/StormHook.java b/src/main/java/io/pzstorm/storm/hook/StormHook.java new file mode 100644 index 0000000..6d4daba --- /dev/null +++ b/src/main/java/io/pzstorm/storm/hook/StormHook.java @@ -0,0 +1,26 @@ +package io.pzstorm.storm.hook; + +import io.pzstorm.storm.core.StormClassTransformer; +import io.pzstorm.storm.event.StormEventDispatcher; +import io.pzstorm.storm.event.ZomboidEvent; + +/** + * This interface represents a hook in game code that creates an instance of {@link ZomboidEvent} + * and calls {@link StormEventDispatcher}. The dispatcher then forwards the event to all registered + * methods that have subscribed to event in context. Hooks are only intended for internal use + * to generate and send events from game code. + * + * @see StormEventDispatcher#dispatchEvent(ZomboidEvent) + */ +public interface StormHook { + + /** + * Use the given transformer to install {@link StormHook} in game code. The installation is + * done by altering method bytecode with the use of ASM to create an instance of {@link ZomboidEvent} + * and create a direct callback to {@link StormEventDispatcher} class. Note that the transformers + * used to install hooks need to be registered in the static block of {@link StormClassTransformer}. + * + * @param transformer {@link StormClassTransformer} to use to install the hook. + */ + void installHook(StormClassTransformer transformer); +} From 8f4011a24fbd0410c1b7d9903a33e42bc78f03c9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 15:57:24 +0200 Subject: [PATCH 106/323] Create method to register transformers --- .../storm/core/StormClassTransformers.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java index 3a4ce00..2813d93 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java @@ -22,7 +22,24 @@ public class StormClassTransformers { */ private static final Map TRANSFORMERS = new HashMap<>(); + /** + * Create and register a new {@link StormClassTransformer} with given name + * that installs a {@link StormHook} designated by method parameter. + * + * @param className name of the target class to transform. + * @param hook {@link StormHook} to install with transformation. + */ + private static void registerTransformer(String className, StormHook hook) { + + TRANSFORMERS.put(className, new StormClassTransformer(className) { + + @Override + StormClassTransformer transform() { + hook.installHook(this); + return this; + } + }); } /** From f49e5b6a4895a130c6bf80a97489e1baecc98f82 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 16:49:47 +0200 Subject: [PATCH 107/323] Add getClassAsPath utility method --- .../java/io/pzstorm/storm/util/StormUtils.java | 14 ++++++++++++-- .../java/io/pzstorm/storm/util/StormUtilsTest.java | 11 +++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/util/StormUtils.java b/src/main/java/io/pzstorm/storm/util/StormUtils.java index 33e29d8..f82ee1f 100644 --- a/src/main/java/io/pzstorm/storm/util/StormUtils.java +++ b/src/main/java/io/pzstorm/storm/util/StormUtils.java @@ -1,11 +1,21 @@ package io.pzstorm.storm.util; -import io.pzstorm.storm.StormLogger; - import java.lang.reflect.Method; +import io.pzstorm.storm.StormLogger; + public class StormUtils { + /** + * Returns a path representation of the name for given {@code Class}. + * + * @param clazz {@code Class} whose name should be converted to path. + * @return {@code String} representing a path. + */ + public static String getClassAsPath(Class clazz) { + return clazz.getName().replace('.', '/'); + } + /** * Invokes the given inaccessible {@code Method} without leaving method accessible. * After the method was invoked the {@code accessible} flag will be set to it's original state. diff --git a/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java index 69098e7..02add2a 100644 --- a/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java @@ -1,14 +1,21 @@ package io.pzstorm.storm.util; +import java.lang.reflect.Method; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.lang.reflect.Method; - class StormUtilsTest { private boolean hasInvokedRestrictedMethod = false; + @Test + void shouldGetClassAsCorrectPath() { + + String expected = "io/pzstorm/storm/util/StormUtilsTest"; + Assertions.assertEquals(expected, StormUtils.getClassAsPath(StormUtilsTest.class)); + } + @Test void shouldResetAccessibilityAfterInvokingRestrictedMethod() throws ReflectiveOperationException { From e1069c3dfb73e0d616133636438361f6e7d40824 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 16:53:33 +0200 Subject: [PATCH 108/323] Create ZomboidEvent interface class --- .../io/pzstorm/storm/event/ZomboidEvent.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/event/ZomboidEvent.java diff --git a/src/main/java/io/pzstorm/storm/event/ZomboidEvent.java b/src/main/java/io/pzstorm/storm/event/ZomboidEvent.java new file mode 100644 index 0000000..3626081 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/event/ZomboidEvent.java @@ -0,0 +1,16 @@ +package io.pzstorm.storm.event; + +import io.pzstorm.storm.hook.StormHook; + +/** + * This class represents game events recognized by Storm. These events are created by installed + * {@link StormHook}s and included as a method parameter in a callback to {@link StormEventDispatcher}. + * They are then dispatched to all methods that subscribe to those specific events. + */ +public interface ZomboidEvent { + + /** + * Returns a readable name that identifies this event. + */ + String getName(); +} From 2ccd9fd4e6c55f1b704c698f218ce5ca05c07444 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 18:02:17 +0200 Subject: [PATCH 109/323] Create SubscribeEvent interface class --- .../io/pzstorm/storm/event/SubscribeEvent.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/event/SubscribeEvent.java diff --git a/src/main/java/io/pzstorm/storm/event/SubscribeEvent.java b/src/main/java/io/pzstorm/storm/event/SubscribeEvent.java new file mode 100644 index 0000000..168d793 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/event/SubscribeEvent.java @@ -0,0 +1,18 @@ +package io.pzstorm.storm.event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to subscribe methods to {@link ZomboidEvent} types. Methods are registered + * in bulk via event handler classes. To register an event handler call an appropriate class in + * {@link StormEventDispatcher}. Once the event handler has been registered all methods belonging to + * the event handler annotated with {@link SubscribeEvent} will be subscribed to events they specify + * as method parameters. Read more about this process in {@link StormEventDispatcher} class documentation. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface SubscribeEvent { +} From eebf92dad9c3caf5d15e183931c7902e37b661c8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 18:03:05 +0200 Subject: [PATCH 110/323] Implement Storm event dispatcher --- .../storm/event/StormEventDispatcher.java | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java diff --git a/src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java b/src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java new file mode 100644 index 0000000..b8df451 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java @@ -0,0 +1,200 @@ +package io.pzstorm.storm.event; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodInsnNode; +import io.pzstorm.storm.hook.StormHook; + +import com.google.common.collect.Sets; + +import io.pzstorm.storm.StormLogger; + +/** + * This class is responsible for registering event handlers and dispatching {@link ZomboidEvent} instances. + *

+ * To register an event handler call one of the following methods: + *

    + *
  • {@link #registerEventHandler(Object)} - when subscribed methods are instance methods.
  • + *
  • {@link #registerEventHandler(Class)} - when subscribed methods are static methods.
  • + *
+ * All methods in registered event handlers annotated with {@link SubscribeEvent} will be called + * by {@link #dispatchEvent(ZomboidEvent)} method when an appropriate event is created by installed + * {@link StormHook}. Each subscribed method has to have exactly one parameter that matches the type + * of event it wants to subscribe to. For example if a method wanted to subscribe to {@code OnRenderEvent} + * it would define itself in one of two ways depending on the handler registration method used: + *

+ *     // handler must be registered as a class
+ *     public static void handleRenderEvent(OnRenderEvent event) {
+ *         ...
+ *     }
+ *     // handler must be registered as an instance
+ *     public void handleRenderEvent(OnRenderEvent event) {
+ *         ...
+ *     }
+ * 
+ * Do not mix static and instance subscribed methods. A registered handler has to have all + * subscribed methods declared as either static or instance methods depending on the method used + * to register the handler. + */ +@SuppressWarnings({ "unused", "WeakerAccess" }) +public class StormEventDispatcher { + + /** + * Internal registry that maps {@link ZomboidEvent} classes to handler methods. + * These methods are then invoked when dispatching matching events. + */ + private static final Map, Set> DISPATCH_REGISTRY = new HashMap<>(); + + /** + * Internally register given method for specified event handler. + * + * @param method {@code Method} to register with event handler. + * @param handler event handler to register along with {@code Method}. It can be either an + * instance of an object or a {@code Class} that represents the handler. + * @throws IllegalArgumentException if the given {@code Method} does not have exactly one argument, + * or the argument is not an instance of {@link ZomboidEvent}. + */ + @SuppressWarnings("unchecked") + private static void registerEventHandlerMethod(Method method, @Nullable Object handler) { + + if (method.isAnnotationPresent(SubscribeEvent.class)) + { + Class[] parameters = method.getParameterTypes(); + if (parameters.length == 1) + { + Class cEventClass = parameters[0]; + if (ZomboidEvent.class.isAssignableFrom(cEventClass)) + { + Class eventClass = (Class) cEventClass; + EventHandlerMethod eventHandlerMethod = new EventHandlerMethod(method, handler); + + Set handlerMethods = DISPATCH_REGISTRY.get(eventClass); + if (handlerMethods == null) { + DISPATCH_REGISTRY.put(eventClass, Sets.newHashSet(eventHandlerMethod)); + } + else handlerMethods.add(eventHandlerMethod); + } + else { + String className = handler instanceof Class ? ((Class) handler).getName() : + (handler != null ? handler.getClass().getName() : "null"); + + String text = "Invalid arguments for method %s(%s). Expected ZomboidEvent but found %s"; + throw new IllegalArgumentException(String.format( + text, method.getName(), className, cEventClass.getName())); + } + } + else { + String className = handler instanceof Class ? ((Class) handler).getName() : + (handler != null ? handler.getClass().getName() : "null"); + + String text = "Invalid arguments for method %s(%s). Expected exactly one argument but found %d"; + throw new IllegalArgumentException(String.format(text, method.getName(), className, parameters.length)); + } + } + } + + /** + * Register all static methods subscribed with {@link SubscribeEvent} annotation in the given + * {@code Class} to dispatch registry. The registered methods will then be called by dispatched whenever + * an event they are subscribed to fires. Note that the methods have to be properly defined see + * {@link StormEventDispatcher} class documentation for more information. + * + * @param handlerClass {@code Class} of the event handler to register. + * @see #registerEventHandler(Object) + */ + public static void registerEventHandler(Class handlerClass) { + + StormLogger.debug("Registering event handler for class " + handlerClass.getName()); + for (Method method : handlerClass.getMethods()) { + registerEventHandlerMethod(method, null); + } + } + + /** + * Register all instance methods subscribed with {@link SubscribeEvent} annotation in the given + * object instance to dispatch registry. The registered methods will then be called by dispatched whenever + * an event they are subscribed to fires. Note that the methods have to be properly defined see + * {@link StormEventDispatcher} class documentation for more information. + * + * @param handler instance of the event handler to register. + * @see #registerEventHandler(Class) + */ + public static void registerEventHandler(Object handler) { + + StormLogger.debug("Registering event handler for instance of class " + handler.getClass().getName()); + for (Method method : handler.getClass().getMethods()) { + registerEventHandlerMethod(method, handler); + } + } + + /** + * Create and return a list of instructions that calls {@link #dispatchEvent(ZomboidEvent)} method. + * This is a convenience method intended to be used only by {@link StormHook} implementations to + * get a list of instructions that represents a dispatch call for given event. + * + * @param eventConstructorInsn list of instructions that represent constructing a new + * {@link ZomboidEvent} instance and adding the result to the stack. These instructions + * will be transferred to the start of the resulting instruction list. + * @see #dispatchEvent(ZomboidEvent) + */ + public static InsnList callDispatchEvent(List eventConstructorInsn) { + + InsnList result = new InsnList(); + if (!LabelNode.class.isAssignableFrom(eventConstructorInsn.get(0).getClass())) { + result.add(new LabelNode()); + } + for (AbstractInsnNode constructorInsn : eventConstructorInsn) { + result.add(constructorInsn); + } + result.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, "io/pzstorm/storm/event/StormEventDispatcher", + "dispatchEvent", "(Lio/pzstorm/storm/event/ZomboidEvent;)V" + )); + return result; + } + + /** + * Dispatch the given event to all methods registered in dispatch registry. This is an internal + * method only called by {@link StormHook} implementations installed in game code. + * + * @param event {@link ZomboidEvent} to dispatch. + * @see #callDispatchEvent(List) + */ + public static void dispatchEvent(ZomboidEvent event) { + + for (EventHandlerMethod handler : DISPATCH_REGISTRY.get(event.getClass())) { + handler.invoke(event); + } + } + + private static class EventHandlerMethod { + + private final Method method; + private final @Nullable Object handler; + + private EventHandlerMethod(Method method, @Nullable Object handler) { + + this.method = method; + this.handler = handler; + } + + private void invoke(ZomboidEvent event) { + + try { + method.invoke(handler, event); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } +} From 91fde4fedd26e7b9b4648fabeaee74b33344352d Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 18:08:53 +0200 Subject: [PATCH 111/323] Add first zomboid render events --- .../storm/event/OnMainScreenRenderEvent.java | 21 ++++++++++++++ .../event/OnUIElementPreRenderEvent.java | 28 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java create mode 100644 src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java diff --git a/src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java b/src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java new file mode 100644 index 0000000..488fde1 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java @@ -0,0 +1,21 @@ +package io.pzstorm.storm.event; + +import zombie.gameStates.MainScreenState; + +/** + * This event fires on each tick when the main screen is being rendered. + * + * @see MainScreenState#render() + */ +public class OnMainScreenRenderEvent implements ZomboidEvent { + + @Override + public String getName() { + return "onMainScreenRender"; + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java b/src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java new file mode 100644 index 0000000..fd2c3a6 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java @@ -0,0 +1,28 @@ +package io.pzstorm.storm.event; + +import zombie.ui.UIElement; + +/** + * This event fires when an {@link UIElement} is being pre-rendered. + * + * @see UIElement#render() + */ +@SuppressWarnings("WeakerAccess") +public class OnUIElementPreRenderEvent implements ZomboidEvent { + + public final UIElement element; + + public OnUIElementPreRenderEvent(UIElement element) { + this.element = element; + } + + @Override + public String getName() { + return "onUIElementPreRender"; + } + + @Override + public String toString() { + return getName(); + } +} From d139b3e8e553daeeafaaba81750c29c1ed2e9222 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 18:15:19 +0200 Subject: [PATCH 112/323] Add first render event hooks --- .../storm/hook/OnMainScreenRenderHook.java | 41 ++++++++++++++ .../storm/hook/OnUIElementPreRenderHook.java | 56 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/hook/OnMainScreenRenderHook.java create mode 100644 src/main/java/io/pzstorm/storm/hook/OnUIElementPreRenderHook.java diff --git a/src/main/java/io/pzstorm/storm/hook/OnMainScreenRenderHook.java b/src/main/java/io/pzstorm/storm/hook/OnMainScreenRenderHook.java new file mode 100644 index 0000000..856e8f1 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/hook/OnMainScreenRenderHook.java @@ -0,0 +1,41 @@ +package io.pzstorm.storm.hook; + +import com.google.common.collect.ImmutableList; +import io.pzstorm.storm.core.StormClassTransformer; +import io.pzstorm.storm.event.OnMainScreenRenderEvent; +import io.pzstorm.storm.event.StormEventDispatcher; +import io.pzstorm.storm.util.AsmUtils; +import io.pzstorm.storm.util.StormUtils; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +public class OnMainScreenRenderHook implements StormHook { + + @Override + public void installHook(StormClassTransformer transformer) { + + // public void render() + InsnList instructions = transformer.getInstructionsForMethod("render", "()V"); + String eventDescriptor = StormUtils.getClassAsPath(OnMainScreenRenderEvent.class); + + // new OnMainScreenRenderEvent() + InsnList dispatchOnRenderEvent = StormEventDispatcher.callDispatchEvent(ImmutableList.of( + new TypeInsnNode(Opcodes.NEW, eventDescriptor), new InsnNode(Opcodes.DUP), + new MethodInsnNode(Opcodes.INVOKESPECIAL, eventDescriptor, "", "()V") + )); + // ... + // -> insert <- + // Core.getInstance().EndFrameUI(); + // ... + LabelNode target = AsmUtils.getFirstMatchingLabelNode(instructions, ImmutableList.of( + // INVOKESTATIC zombie/core/Core.getInstance ()Lzombie/core/Core; + new MethodInsnNode(Opcodes.INVOKESTATIC, + "zombie/core/Core", "getInstance", "()Lzombie/core/Core;" + ), + // INVOKEVIRTUAL zombie/core/Core.EndFrameUI ()V + new MethodInsnNode(Opcodes.INVOKEVIRTUAL, + "zombie/core/Core", "EndFrameUI", "()V")) + ); + instructions.insertBefore(target, dispatchOnRenderEvent); + } +} diff --git a/src/main/java/io/pzstorm/storm/hook/OnUIElementPreRenderHook.java b/src/main/java/io/pzstorm/storm/hook/OnUIElementPreRenderHook.java new file mode 100644 index 0000000..92a6e3e --- /dev/null +++ b/src/main/java/io/pzstorm/storm/hook/OnUIElementPreRenderHook.java @@ -0,0 +1,56 @@ +package io.pzstorm.storm.hook; + +import com.google.common.collect.ImmutableList; +import io.pzstorm.storm.core.StormClassTransformer; +import io.pzstorm.storm.event.OnUIElementPreRenderEvent; +import io.pzstorm.storm.event.StormEventDispatcher; +import io.pzstorm.storm.util.AsmUtils; +import io.pzstorm.storm.util.StormUtils; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +public class OnUIElementPreRenderHook implements StormHook { + + @Override + public void installHook(StormClassTransformer transformer) { + + // public void render() + InsnList instructions = transformer.getInstructionsForMethod("render", "()V"); + String eventDescriptor = StormUtils.getClassAsPath(OnUIElementPreRenderEvent.class); + + // new OnUIElementPreRenderEvent(UIElement) + InsnList dispatchOnUIElementPreRenderEvent = StormEventDispatcher.callDispatchEvent(ImmutableList.of( + new TypeInsnNode(Opcodes.NEW, eventDescriptor), + new InsnNode(Opcodes.DUP), new VarInsnNode(Opcodes.ALOAD, 0), + new MethodInsnNode(Opcodes.INVOKESPECIAL, + eventDescriptor, "", "(Lzombie/ui/UIElement;)V") + )); + // ... + // LuaManager.caller.pcallvoid(UIManager.getDefaultThread(), this.getTable().rawget("prerender"), this.table); + // -> insert <- + // ... + LabelNode callPrerender = AsmUtils.getFirstMatchingLabelNode(instructions, ImmutableList.of( + new FieldInsnNode(Opcodes.GETSTATIC, "zombie/Lua/LuaManager", "caller", + "Lse/krka/kahlua/integration/LuaCaller;" + ), + new MethodInsnNode(Opcodes.INVOKESTATIC, "zombie/ui/UIManager", "getDefaultThread", + "()Lse/krka/kahlua/vm/KahluaThread;" + ), + new VarInsnNode(Opcodes.ALOAD, 0), + new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "zombie/ui/UIElement", "getTable", + "()Lse/krka/kahlua/vm/KahluaTable;" + ), + new LdcInsnNode("prerender"), + new MethodInsnNode(Opcodes.INVOKEINTERFACE, "se/krka/kahlua/vm/KahluaTable", "rawget", + "(Ljava/lang/Object;)Ljava/lang/Object;", true + ), + new VarInsnNode(Opcodes.ALOAD, 0), + new FieldInsnNode(Opcodes.GETFIELD, "zombie/ui/UIElement", "table", + "Lse/krka/kahlua/vm/KahluaTable;" + ), + new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "se/krka/kahlua/integration/LuaCaller", "pcallvoid", + "(Lse/krka/kahlua/vm/KahluaThread;Ljava/lang/Object;Ljava/lang/Object;)V")) + ); + instructions.insert(callPrerender, dispatchOnUIElementPreRenderEvent); + } +} From f3461c54858702f54fc7dbb60fac7d9af7bff145 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 18:15:58 +0200 Subject: [PATCH 113/323] Register transformers that install hooks --- .../io/pzstorm/storm/core/StormClassTransformers.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java index 2813d93..0905d59 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java @@ -6,6 +6,8 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import io.pzstorm.storm.hook.OnMainScreenRenderHook; +import io.pzstorm.storm.hook.OnUIElementPreRenderHook; import io.pzstorm.storm.hook.StormHook; /** @@ -22,6 +24,12 @@ public class StormClassTransformers { */ private static final Map TRANSFORMERS = new HashMap<>(); + static + { + registerTransformer("zombie.gameStates.MainScreenState", new OnMainScreenRenderHook()); + registerTransformer("zombie.ui.UIElement", new OnUIElementPreRenderHook()); + } + /** * Create and register a new {@link StormClassTransformer} with given name * that installs a {@link StormHook} designated by method parameter. From 1a6bba2b7d6f296b728736bb20426b654ec162a3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 19:22:19 +0200 Subject: [PATCH 114/323] Create StormEventHandler class --- .../io/pzstorm/storm/event/StormEventHandler.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/event/StormEventHandler.java diff --git a/src/main/java/io/pzstorm/storm/event/StormEventHandler.java b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java new file mode 100644 index 0000000..4292e55 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java @@ -0,0 +1,11 @@ +package io.pzstorm.storm.event; + +/** + * This class responds to all events needed for Storm to implement custom features. + * Note that not all functionality implementations that are weaved into game bytecode is handled here. + * Sometimes subscribing to events is not enough to alter game behavior and more invasive + * actions need to be preformed, like editing or removing lines from game code. + */ +public class StormEventHandler { + +} From c2328fa5167eaf7fcfbb159722a3985979664643 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 19:22:50 +0200 Subject: [PATCH 115/323] Implement render event handlers --- .../storm/event/StormEventHandler.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/event/StormEventHandler.java b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java index 4292e55..7e2f54b 100644 --- a/src/main/java/io/pzstorm/storm/event/StormEventHandler.java +++ b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java @@ -1,5 +1,11 @@ package io.pzstorm.storm.event; +import se.krka.kahlua.j2se.KahluaTableImpl; +import se.krka.kahlua.vm.KahluaTable; +import zombie.core.Core; +import zombie.ui.TextManager; +import zombie.ui.UIFont; + /** * This class responds to all events needed for Storm to implement custom features. * Note that not all functionality implementations that are weaved into game bytecode is handled here. @@ -8,4 +14,26 @@ */ public class StormEventHandler { + @SubscribeEvent + public static void handleRenderEvent(OnMainScreenRenderEvent event) { + } + + @SubscribeEvent + public static void handleUIElementRenderEvent(OnUIElementPreRenderEvent event) { + + if (event.element.Parent != null) + { + KahluaTable table = event.element.Parent.table; + if (table instanceof KahluaTableImpl) + { + Object internal = ((KahluaTableImpl)table).delegate.get("internal"); + if (internal instanceof String && internal.equals("VERSIONDETAIL")) + { + String text = "Storm version 0.1.0-alpha"; + TextManager.instance.DrawString(UIFont.Small, Core.width - 235.0, + Core.height - 50.0, text, 1.0, 1.0, 1.0, 0.7); + } + } + } + } } From e360391779736f92ea2d631a7b233d46e21aae9e Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 19:23:51 +0200 Subject: [PATCH 116/323] Load event handler and dispatcher classes --- src/main/java/io/pzstorm/storm/core/StormLauncher.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/core/StormLauncher.java b/src/main/java/io/pzstorm/storm/core/StormLauncher.java index 773a17e..e9b37ad 100644 --- a/src/main/java/io/pzstorm/storm/core/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/core/StormLauncher.java @@ -32,6 +32,8 @@ public static void main(String[] args) throws ReflectiveOperationException { StormClassLoader classLoader = StormBootstrap.CLASS_LOADER; Class.forName("io.pzstorm.storm.core.StormClassTransformers", true, classLoader); + Class eventHandler = classLoader.loadClass("io.pzstorm.storm.event.StormEventHandler"); + Class eventDispatcher = classLoader.loadClass("io.pzstorm.storm.event.StormEventDispatcher"); Class entryPointClass = classLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); Method entryPoint = entryPointClass.getMethod(ZOMBOID_ENTRY_POINT, String[].class); try { From 728b1fa2d7a02aea574ddea91f266846d5ef37c2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 19:24:10 +0200 Subject: [PATCH 117/323] Register Storm event handler class --- src/main/java/io/pzstorm/storm/core/StormLauncher.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/core/StormLauncher.java b/src/main/java/io/pzstorm/storm/core/StormLauncher.java index e9b37ad..2641fb8 100644 --- a/src/main/java/io/pzstorm/storm/core/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/core/StormLauncher.java @@ -34,6 +34,8 @@ public static void main(String[] args) throws ReflectiveOperationException { Class eventHandler = classLoader.loadClass("io.pzstorm.storm.event.StormEventHandler"); Class eventDispatcher = classLoader.loadClass("io.pzstorm.storm.event.StormEventDispatcher"); + eventDispatcher.getDeclaredMethod("registerEventHandler", Class.class).invoke(null, eventHandler); + Class entryPointClass = classLoader.loadClass(ZOMBOID_ENTRY_POINT_CLASS); Method entryPoint = entryPointClass.getMethod(ZOMBOID_ENTRY_POINT, String[].class); try { From 0370870224df6b4c3d93e153fb9062290db0f81b Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 19:29:02 +0200 Subject: [PATCH 118/323] Remove unneeded parentheses --- src/main/java/io/pzstorm/storm/util/AsmUtils.java | 4 ++-- src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/util/AsmUtils.java b/src/main/java/io/pzstorm/storm/util/AsmUtils.java index a271105..6ef8711 100644 --- a/src/main/java/io/pzstorm/storm/util/AsmUtils.java +++ b/src/main/java/io/pzstorm/storm/util/AsmUtils.java @@ -162,7 +162,7 @@ public static boolean equalNodes(AbstractInsnNode a, AbstractInsnNode b) { return ((MethodInsnNode) a).owner.equals(((MethodInsnNode) b).owner) && ((MethodInsnNode) a).name.equals(((MethodInsnNode) b).name) && ((MethodInsnNode) a).desc.equals(((MethodInsnNode) b).desc) && - ((MethodInsnNode) a).itf == (((MethodInsnNode) b).itf); + ((MethodInsnNode) a).itf == ((MethodInsnNode) b).itf; } if (a instanceof MultiANewArrayInsnNode) { @@ -196,7 +196,7 @@ public static boolean equalNodes(AbstractInsnNode a, AbstractInsnNode b) { return ((TypeInsnNode) a).desc.equals(((TypeInsnNode) b).desc); } if (a instanceof VarInsnNode) { - return ((VarInsnNode) a).var == (((VarInsnNode) b).var); + return ((VarInsnNode) a).var == ((VarInsnNode) b).var; } return true; } diff --git a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java index 7305359..f4c2a35 100644 --- a/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/AsmUtilsTest.java @@ -151,6 +151,6 @@ void shouldCompareInstructionNodeFields() { FieldInsnNode fia = new FieldInsnNode(Opcodes.GETSTATIC, "owner", "name", "desc"); FieldInsnNode fib = new FieldInsnNode(Opcodes.GETSTATIC, "owner", "name", "desc"); - Assertions.assertTrue((AsmUtils.equalNodes(fia, fib))); + Assertions.assertTrue(AsmUtils.equalNodes(fia, fib)); } } From 48256824fbadd4acd8d68cc9efac980e91cd09a8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 19:30:36 +0200 Subject: [PATCH 119/323] Suppress unused methods warnings --- src/test/java/io/pzstorm/storm/util/StormUtilsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java index 02add2a..b2345ef 100644 --- a/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +@SuppressWarnings("unused") class StormUtilsTest { private boolean hasInvokedRestrictedMethod = false; From 32065bc6225c30c3f2da582facdf019c0e67bc4d Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 20:02:46 +0200 Subject: [PATCH 120/323] Create dummy zomboid classes --- .../se/krka/kahlua/j2se/KahluaTableImpl.java | 12 +++++++ .../java/se/krka/kahlua/vm/KahluaTable.java | 8 +++++ src/zombie/java/zombie/ZomboidClass.java | 16 +++++++++ src/zombie/java/zombie/core/Core.java | 10 ++++++ .../zombie/gameStates/MainScreenState.java | 11 +++++++ src/zombie/java/zombie/ui/TextManager.java | 14 ++++++++ src/zombie/java/zombie/ui/UIElement.java | 15 +++++++++ src/zombie/java/zombie/ui/UIFont.java | 33 +++++++++++++++++++ 8 files changed, 119 insertions(+) create mode 100644 src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java create mode 100644 src/zombie/java/se/krka/kahlua/vm/KahluaTable.java create mode 100644 src/zombie/java/zombie/ZomboidClass.java create mode 100644 src/zombie/java/zombie/core/Core.java create mode 100644 src/zombie/java/zombie/gameStates/MainScreenState.java create mode 100644 src/zombie/java/zombie/ui/TextManager.java create mode 100644 src/zombie/java/zombie/ui/UIElement.java create mode 100644 src/zombie/java/zombie/ui/UIFont.java diff --git a/src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java b/src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java new file mode 100644 index 0000000..133e562 --- /dev/null +++ b/src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java @@ -0,0 +1,12 @@ +package se.krka.kahlua.j2se; + +import se.krka.kahlua.vm.KahluaTable; +import zombie.ZomboidClass; + +import java.util.Map; + +@ZomboidClass +public class KahluaTableImpl implements KahluaTable { + + public Map delegate; +} diff --git a/src/zombie/java/se/krka/kahlua/vm/KahluaTable.java b/src/zombie/java/se/krka/kahlua/vm/KahluaTable.java new file mode 100644 index 0000000..8752f23 --- /dev/null +++ b/src/zombie/java/se/krka/kahlua/vm/KahluaTable.java @@ -0,0 +1,8 @@ +package se.krka.kahlua.vm; + +import zombie.ZomboidClass; + +@ZomboidClass +public interface KahluaTable { + +} diff --git a/src/zombie/java/zombie/ZomboidClass.java b/src/zombie/java/zombie/ZomboidClass.java new file mode 100644 index 0000000..5a7e1ac --- /dev/null +++ b/src/zombie/java/zombie/ZomboidClass.java @@ -0,0 +1,16 @@ +package zombie; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This class is used to mark dummy Zomboid classes that are used instead of real Zomboid classes + * when compiling Java on CI server. Without these classes the compile task will fail on CI. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface ZomboidClass { + +} diff --git a/src/zombie/java/zombie/core/Core.java b/src/zombie/java/zombie/core/Core.java new file mode 100644 index 0000000..48d71d2 --- /dev/null +++ b/src/zombie/java/zombie/core/Core.java @@ -0,0 +1,10 @@ +package zombie.core; + +import zombie.ZomboidClass; + +@ZomboidClass +public class Core { + + public static int width = 0; + public static int height = 0; +} diff --git a/src/zombie/java/zombie/gameStates/MainScreenState.java b/src/zombie/java/zombie/gameStates/MainScreenState.java new file mode 100644 index 0000000..255db34 --- /dev/null +++ b/src/zombie/java/zombie/gameStates/MainScreenState.java @@ -0,0 +1,11 @@ +package zombie.gameStates; + +import zombie.ZomboidClass; + +@ZomboidClass +public class MainScreenState { + + public void render() { + + } +} diff --git a/src/zombie/java/zombie/ui/TextManager.java b/src/zombie/java/zombie/ui/TextManager.java new file mode 100644 index 0000000..101103c --- /dev/null +++ b/src/zombie/java/zombie/ui/TextManager.java @@ -0,0 +1,14 @@ +package zombie.ui; + +import zombie.ZomboidClass; + +@ZomboidClass +public class TextManager { + + public static TextManager instance; + + @SuppressWarnings("unused") + public void DrawString(UIFont var1, double var2, double var4, String var6, double var7, double var9, double var11, double var13) { + + } +} diff --git a/src/zombie/java/zombie/ui/UIElement.java b/src/zombie/java/zombie/ui/UIElement.java new file mode 100644 index 0000000..44d2313 --- /dev/null +++ b/src/zombie/java/zombie/ui/UIElement.java @@ -0,0 +1,15 @@ +package zombie.ui; + +import se.krka.kahlua.vm.KahluaTable; +import zombie.ZomboidClass; + +@ZomboidClass +public class UIElement { + + public UIElement Parent; + public KahluaTable table; + + public void render() { + + } +} diff --git a/src/zombie/java/zombie/ui/UIFont.java b/src/zombie/java/zombie/ui/UIFont.java new file mode 100644 index 0000000..4f5c0e5 --- /dev/null +++ b/src/zombie/java/zombie/ui/UIFont.java @@ -0,0 +1,33 @@ +package zombie.ui; + +import zombie.ZomboidClass; + +@ZomboidClass +@SuppressWarnings("unused") +public enum UIFont { + + Small, + Medium, + Large, + Massive, + MainMenu1, + MainMenu2, + Cred1, + Cred2, + NewSmall, + NewMedium, + NewLarge, + Code, + MediumNew, + AutoNormSmall, + AutoNormMedium, + AutoNormLarge, + Dialogue, + Intro, + Handwritten, + DebugConsole, + Title; + + UIFont() { + } +} From 8b228188700aa939b6e3afdded300680d50606ca Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 20:06:30 +0200 Subject: [PATCH 121/323] Add dummy classes to CI classpath --- build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build.gradle b/build.gradle index e490c0b..f843dc6 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,12 @@ sourceSets { runtimeClasspath += sourceSets.main.output compileClasspath += sourceSets.main.output } + main { + // add dummy zomboid classes to classpath when compiling on CI + if (System.getenv("CI") != null) { + compileClasspath += sourceSets.zombie.output + } + } test { // tests need to see classes used in class transformation tests runtimeClasspath += sourceSets.zombie.output From ec6e633e1fa9f321ba9805eaac82d547ee002afc Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 20:20:42 +0200 Subject: [PATCH 122/323] Fix circular task dependency error https://github.com/pzstorm/storm/runs/2529875890#step:5:104 --- build.gradle | 4 +--- src/zombie/java/zombie/ZombieHello.java | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index f843dc6..63cf52b 100644 --- a/build.gradle +++ b/build.gradle @@ -23,10 +23,8 @@ capsid { } sourceSets { - // this sourceSet is used for testing class transformation zombie { - runtimeClasspath += sourceSets.main.output - compileClasspath += sourceSets.main.output + java.srcDir('src/main/java') } main { // add dummy zomboid classes to classpath when compiling on CI diff --git a/src/zombie/java/zombie/ZombieHello.java b/src/zombie/java/zombie/ZombieHello.java index 33f1118..7e9a4ae 100644 --- a/src/zombie/java/zombie/ZombieHello.java +++ b/src/zombie/java/zombie/ZombieHello.java @@ -2,14 +2,13 @@ import org.jetbrains.annotations.TestOnly; -import io.pzstorm.storm.StormLogger; - @TestOnly @SuppressWarnings({ "unused", "WeakerAccess" }) public class ZombieHello { + @SuppressWarnings("UseOfSystemOutOrSystemErr") public static void sayHello() { - StormLogger.info(getHello()); + System.out.println(getHello()); } public static String getHello() { From 29a07682df493a6d577801bd1cec8ed749ef77e9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 20:52:05 +0200 Subject: [PATCH 123/323] Reorder project dependencies --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 63cf52b..5dee8f5 100644 --- a/build.gradle +++ b/build.gradle @@ -55,15 +55,15 @@ dependencies { // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core implementation 'org.apache.logging.log4j:log4j-core:2.14.0' - // https://mvnrepository.com/artifact/org.jetbrains/annotations - compileOnly 'org.jetbrains:annotations:20.1.0' - // https://mvnrepository.com/artifact/com.google.errorprone/error_prone_core errorprone 'com.google.errorprone:error_prone_core:2.6.0' // https://mvnrepository.com/artifact/com.google.errorprone/javac errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') + // https://mvnrepository.com/artifact/org.jetbrains/annotations + compileOnly 'org.jetbrains:annotations:20.1.0' + // https://mvnrepository.com/artifact/com.google.guava/guava implementation 'com.google.guava:guava:30.1.1-jre' From 3b8d2b355deb9fe94b0d776beae28f61f1a38079 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 20:55:17 +0200 Subject: [PATCH 124/323] Fix invalid sourceSet directory path --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 5dee8f5..6641289 100644 --- a/build.gradle +++ b/build.gradle @@ -23,9 +23,9 @@ capsid { } sourceSets { - zombie { - java.srcDir('src/main/java') - } + // module containing zomboid dummy classes and classes used in testing + // this module is only required by tests and CI compiler + zombie {} main { // add dummy zomboid classes to classpath when compiling on CI if (System.getenv("CI") != null) { From a6dfcdc8597db284f665c8caa1afec27c76c25ac Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 21:42:19 +0200 Subject: [PATCH 125/323] Create transformer registration method --- .../io/pzstorm/storm/core/StormClassTransformers.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java index 0905d59..bb90e9b 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java @@ -30,6 +30,16 @@ public class StormClassTransformers { registerTransformer("zombie.ui.UIElement", new OnUIElementPreRenderHook()); } + /** + * Register designated {@link StormClassTransformer} with given name. + * + * @param className name of the target class to transform. + * @param transformer {@code StormClassTransformer} to register. + */ + private static void registerTransformer(String className, StormClassTransformer transformer) { + TRANSFORMERS.put(className, transformer); + } + /** * Create and register a new {@link StormClassTransformer} with given name * that installs a {@link StormHook} designated by method parameter. From d8510bd53e0a293852e49f0b09aaaba6eb75ad4f Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 21:42:50 +0200 Subject: [PATCH 126/323] Update StormClassTransformer tests --- .../storm/core/StormClassTransformerTest.java | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java index f6c6cdb..74d2afe 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java @@ -1,6 +1,7 @@ package io.pzstorm.storm.core; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -9,20 +10,42 @@ class StormClassTransformerTest implements UnitTest { - private static void createAndLoadTransformer(String transformer) throws ReflectiveOperationException { + private static final Class STORM_CLASS_TRANSFORMER, STORM_CLASS_TRANSFORMERS; - Class transformerClass = Class.forName(transformer, true, StormBootstrap.CLASS_LOADER); + static { + try { + STORM_CLASS_TRANSFORMER = Class.forName( + "io.pzstorm.storm.core.StormClassTransformer", + true, StormBootstrap.CLASS_LOADER + ); + STORM_CLASS_TRANSFORMERS = Class.forName( + "io.pzstorm.storm.core.StormClassTransformers", + true, StormBootstrap.CLASS_LOADER); + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static void createAndRegisterTransformer(String className, String sTransformerClass) throws ReflectiveOperationException { + + Class transformerClass = Class.forName(sTransformerClass, true, StormBootstrap.CLASS_LOADER); Constructor constructor = transformerClass.getConstructor(); constructor.setAccessible(true); - constructor.newInstance(); + + Object transformer = constructor.newInstance(); + Method registerTransformer = STORM_CLASS_TRANSFORMERS.getDeclaredMethod( + "registerTransformer", String.class, STORM_CLASS_TRANSFORMER + ); + registerTransformer.setAccessible(true); + registerTransformer.invoke(null, className, STORM_CLASS_TRANSFORMER.cast(transformer)); } @Test void shouldChangeStackConstantInInstructionList() throws ReflectiveOperationException { String className = "zombie.ZombieHello"; - createAndLoadTransformer("io.pzstorm.storm.core.ZombieHelloTransformer"); - + createAndRegisterTransformer(className, "io.pzstorm.storm.core.ZombieHelloTransformer"); Class zombieHello = StormBootstrap.CLASS_LOADER.loadClass(className, true); String hello = (String) zombieHello.getDeclaredMethod("getHello").invoke(null); Assertions.assertEquals("Zombie says: you die today!", hello); @@ -34,7 +57,7 @@ void shouldChangeStackConstantInInstructionList() throws ReflectiveOperationExce void shouldInsertInstructionBeforeMatchedLabel() throws ReflectiveOperationException { String className = "zombie.ZombieUtils"; - createAndLoadTransformer("io.pzstorm.storm.core.ZombieUtilsTransformer"); + createAndRegisterTransformer(className, "io.pzstorm.storm.core.ZombieUtilsTransformer"); Class zombieUtils = StormBootstrap.CLASS_LOADER.loadClass(className, true); zombieUtils.getDeclaredMethod("setZombieProperties", int.class, boolean.class) From e2784fe06bf7883b4a2fac767a5b1ec47941c145 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 21:45:16 +0200 Subject: [PATCH 127/323] Declare StormUtilsTest as UnitTest --- src/test/java/io/pzstorm/storm/util/StormUtilsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java index b2345ef..7a406eb 100644 --- a/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java +++ b/src/test/java/io/pzstorm/storm/util/StormUtilsTest.java @@ -2,11 +2,12 @@ import java.lang.reflect.Method; +import io.pzstorm.storm.UnitTest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @SuppressWarnings("unused") -class StormUtilsTest { +class StormUtilsTest implements UnitTest { private boolean hasInvokedRestrictedMethod = false; From 825710960494d797a065d7ecbc74cb8b845236d2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 22:59:31 +0200 Subject: [PATCH 128/323] Move StormLogger to core package --- src/main/java/io/pzstorm/storm/core/StormClassLoader.java | 4 +--- .../java/io/pzstorm/storm/core/StormClassTransformer.java | 2 -- src/main/java/io/pzstorm/storm/core/StormLauncher.java | 2 -- src/main/java/io/pzstorm/storm/{ => core}/StormLogger.java | 2 +- .../java/io/pzstorm/storm/event/StormEventDispatcher.java | 3 +-- src/main/java/io/pzstorm/storm/util/StormUtils.java | 4 ++-- .../io/pzstorm/storm/core/StormClassTestTransformer.java | 2 -- .../java/io/pzstorm/storm/{ => core}/StormLoggerTest.java | 5 +++-- 8 files changed, 8 insertions(+), 16 deletions(-) rename src/main/java/io/pzstorm/storm/{ => core}/StormLogger.java (99%) rename src/test/java/io/pzstorm/storm/{ => core}/StormLoggerTest.java (95%) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java index 9ea275e..3d69c22 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassLoader.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassLoader.java @@ -12,8 +12,6 @@ import com.google.common.collect.ImmutableSet; -import io.pzstorm.storm.StormLogger; - /** * This is a custom {@code ClassLoader} used to define, transform and load Project Zomboid classes. * It is initially invoked by {@link StormLauncher} when launching the game. @@ -33,7 +31,7 @@ class StormClassLoader extends ClassLoader { private static final ImmutableSet CLASS_BLACKLIST = ImmutableSet.of( "java.", "org.objectweb.asm.", "sun.", "com.sun.", "org.xml.", "org.w3c.", "javax.script.", "javax.management.", "javax.imageio.", "javax.xml.", - "io.pzstorm.storm.StormLogger" + "io.pzstorm.storm.core.StormLogger" ); protected final URLClassLoader resourceClassLoader; /** diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java index abcf3a7..35269a1 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -11,8 +11,6 @@ import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; -import io.pzstorm.storm.StormLogger; - /** * This class represents a {@code Class} transformer used to alter {@code Class} * bytecode using ASM. The transformation process calls the following method in fixed order: diff --git a/src/main/java/io/pzstorm/storm/core/StormLauncher.java b/src/main/java/io/pzstorm/storm/core/StormLauncher.java index 2641fb8..f99210b 100644 --- a/src/main/java/io/pzstorm/storm/core/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/core/StormLauncher.java @@ -2,8 +2,6 @@ import java.lang.reflect.Method; -import io.pzstorm.storm.StormLogger; - class StormLauncher { /** diff --git a/src/main/java/io/pzstorm/storm/StormLogger.java b/src/main/java/io/pzstorm/storm/core/StormLogger.java similarity index 99% rename from src/main/java/io/pzstorm/storm/StormLogger.java rename to src/main/java/io/pzstorm/storm/core/StormLogger.java index b0b3ac4..fb97905 100644 --- a/src/main/java/io/pzstorm/storm/StormLogger.java +++ b/src/main/java/io/pzstorm/storm/core/StormLogger.java @@ -1,4 +1,4 @@ -package io.pzstorm.storm; +package io.pzstorm.storm.core; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; diff --git a/src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java b/src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java index b8df451..8c5a2ee 100644 --- a/src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java +++ b/src/main/java/io/pzstorm/storm/event/StormEventDispatcher.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Set; +import io.pzstorm.storm.core.StormLogger; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; @@ -16,8 +17,6 @@ import com.google.common.collect.Sets; -import io.pzstorm.storm.StormLogger; - /** * This class is responsible for registering event handlers and dispatching {@link ZomboidEvent} instances. *

diff --git a/src/main/java/io/pzstorm/storm/util/StormUtils.java b/src/main/java/io/pzstorm/storm/util/StormUtils.java index f82ee1f..6533eeb 100644 --- a/src/main/java/io/pzstorm/storm/util/StormUtils.java +++ b/src/main/java/io/pzstorm/storm/util/StormUtils.java @@ -1,8 +1,8 @@ package io.pzstorm.storm.util; -import java.lang.reflect.Method; +import io.pzstorm.storm.core.StormLogger; -import io.pzstorm.storm.StormLogger; +import java.lang.reflect.Method; public class StormUtils { diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java index 91a4b4a..2c62800 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTestTransformer.java @@ -16,8 +16,6 @@ import com.github.difflib.UnifiedDiffUtils; import com.github.difflib.patch.Patch; -import io.pzstorm.storm.StormLogger; - /** * This class represents a {@code Class} transformer used to alter {@code Class} * bytecode using ASM when running JUnit tests. When transforming it will print a diff --git a/src/test/java/io/pzstorm/storm/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/core/StormLoggerTest.java similarity index 95% rename from src/test/java/io/pzstorm/storm/StormLoggerTest.java rename to src/test/java/io/pzstorm/storm/core/StormLoggerTest.java index d770cf2..beeef40 100644 --- a/src/test/java/io/pzstorm/storm/StormLoggerTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormLoggerTest.java @@ -1,9 +1,10 @@ -package io.pzstorm.storm; +package io.pzstorm.storm.core; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import io.pzstorm.storm.UnitTest; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; @@ -32,7 +33,7 @@ void shouldSetStormLoggerLevelFromSystemProperties() throws ReflectiveOperationE Assertions.assertEquals(expectedLevel.name(), systemProperty); // initialize StormLogger class - Class.forName("io.pzstorm.storm.StormLogger"); + Class.forName("io.pzstorm.storm.core.StormLogger"); LoggerContext ctx = (LoggerContext) LogManager.getContext(false); LoggerConfig rootLoggerConfig = ctx.getConfiguration().getLoggers().get(""); From af86282ebdce8122585d459f632273071d401cf3 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:04:57 +0200 Subject: [PATCH 129/323] Improve StormLogger initialization --- .../io/pzstorm/storm/core/StormLauncher.java | 4 + .../io/pzstorm/storm/core/StormLogger.java | 24 +++--- .../pzstorm/storm/core/StormLoggerTest.java | 76 +++++++++---------- 3 files changed, 55 insertions(+), 49 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormLauncher.java b/src/main/java/io/pzstorm/storm/core/StormLauncher.java index f99210b..bcede28 100644 --- a/src/main/java/io/pzstorm/storm/core/StormLauncher.java +++ b/src/main/java/io/pzstorm/storm/core/StormLauncher.java @@ -30,6 +30,10 @@ public static void main(String[] args) throws ReflectiveOperationException { StormClassLoader classLoader = StormBootstrap.CLASS_LOADER; Class.forName("io.pzstorm.storm.core.StormClassTransformers", true, classLoader); + // initialize logging system + StormLogger.initialize(); + + // initialize dispatcher system Class eventHandler = classLoader.loadClass("io.pzstorm.storm.event.StormEventHandler"); Class eventDispatcher = classLoader.loadClass("io.pzstorm.storm.event.StormEventDispatcher"); eventDispatcher.getDeclaredMethod("registerEventHandler", Class.class).invoke(null, eventHandler); diff --git a/src/main/java/io/pzstorm/storm/core/StormLogger.java b/src/main/java/io/pzstorm/storm/core/StormLogger.java index fb97905..eab7eda 100644 --- a/src/main/java/io/pzstorm/storm/core/StormLogger.java +++ b/src/main/java/io/pzstorm/storm/core/StormLogger.java @@ -10,25 +10,27 @@ /** *

Simple wrapper class for logging with Log4j 2 logger. * To configure console logging level launch Storm with {@code JVM_PROPERTY} - * set to a custom logger level. The level will be matched with {@link Logger#getLevel()}. + * set to a custom logger level and call {@link #initialize()} method. *

* Logs will automatically be printed to console and configured log files. - * Check {@code log4j2.xml} for log file locations. - *

- * Use the {@code static} methods to print logs with desired log level. - * If you want to print with a log level not covered by {@code static} methods - * use {@link #get()} method to get a reference to logger instance. + * Check {@code log4j2.xml} for log file locations. Use the {@code static} methods to + * print logs with desired log level. If you want to print with a log level not covered + * by {@code static} methods use {@link #get()} method to get a reference to logger instance. */ @SuppressWarnings({ "unused", "WeakerAccess" }) public class StormLogger { public static final Level VERBOSE = Level.forName("VERBOSE", 450); - private static final org.apache.logging.log4j.Logger LOGGER; + private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger("Storm"); + static final String LOGGER_PROPERTY = "storm.logger"; - static - { - LOGGER = LogManager.getLogger("Storm"); - String sLevel = System.getProperty("storm.logger"); + /** + * Initialize {@link StormLogger} system by setting logging level resolved from system properties. + * To configure console logging level launch Storm with {@code JVM_PROPERTY} set to a custom logger level. + */ + static void initialize() { + + String sLevel = System.getProperty(LOGGER_PROPERTY); if (sLevel != null && !sLevel.isEmpty()) { Level level = Level.getLevel(sLevel); diff --git a/src/test/java/io/pzstorm/storm/core/StormLoggerTest.java b/src/test/java/io/pzstorm/storm/core/StormLoggerTest.java index beeef40..46373cf 100644 --- a/src/test/java/io/pzstorm/storm/core/StormLoggerTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormLoggerTest.java @@ -17,58 +17,58 @@ class StormLoggerTest implements UnitTest { - private static final String LOGGER_PROPERTY = "storm.logger"; - @Test void shouldSetStormLoggerLevelFromSystemProperties() throws ReflectiveOperationException { // store current level from system properties - Level originalLevel = Level.toLevel(System.getProperty(LOGGER_PROPERTY, "INFO")); + Level originalLevel = Level.toLevel(System.getProperty(StormLogger.LOGGER_PROPERTY, "INFO")); - Level expectedLevel = Level.forName("ALL", 1); - System.setProperty(LOGGER_PROPERTY, expectedLevel.name()); + for (Level expectedLevel : Level.values()) + { + System.setProperty(StormLogger.LOGGER_PROPERTY, expectedLevel.name()); - // assert that system property was properly set - String systemProperty = System.getProperty(LOGGER_PROPERTY); - Assertions.assertEquals(expectedLevel.name(), systemProperty); + // assert that system property was properly set + String systemProperty = System.getProperty(StormLogger.LOGGER_PROPERTY); + Assertions.assertEquals(expectedLevel.name(), systemProperty); - // initialize StormLogger class - Class.forName("io.pzstorm.storm.core.StormLogger"); + // initialize logging system + StormLogger.initialize(); - LoggerContext ctx = (LoggerContext) LogManager.getContext(false); - LoggerConfig rootLoggerConfig = ctx.getConfiguration().getLoggers().get(""); - Configuration config = ctx.getConfiguration(); + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + LoggerConfig rootLoggerConfig = ctx.getConfiguration().getLoggers().get(""); + Configuration config = ctx.getConfiguration(); - Field appendersField = LoggerConfig.class.getDeclaredField("appenders"); - appendersField.setAccessible(true); + Field appendersField = LoggerConfig.class.getDeclaredField("appenders"); + appendersField.setAccessible(true); - Field appenderLevelField = AppenderControl.class.getDeclaredField("level"); - appenderLevelField.setAccessible(true); + Field appenderLevelField = AppenderControl.class.getDeclaredField("level"); + appenderLevelField.setAccessible(true); - Map actualLevels = new HashMap<>(); + Map actualLevels = new HashMap<>(); - AppenderControlArraySet appenders = (AppenderControlArraySet) appendersField.get(rootLoggerConfig); - for (AppenderControl appenderControl : appenders.get()) - { - String appenderName = appenderControl.getAppenderName(); - if (appenderName.equals("Console") || appenderName.equals("MainFile")) { - actualLevels.put(appenderName, (Level) appenderLevelField.get(appenderControl)); + AppenderControlArraySet appenders = (AppenderControlArraySet) appendersField.get(rootLoggerConfig); + for (AppenderControl appenderControl : appenders.get()) + { + String appenderName = appenderControl.getAppenderName(); + if (appenderName.equals("Console") || appenderName.equals("MainFile")) { + actualLevels.put(appenderName, (Level) appenderLevelField.get(appenderControl)); + } } - } - // assert that all appenders were found - Assertions.assertTrue(actualLevels.containsKey("Console")); - Assertions.assertTrue(actualLevels.containsKey("MainFile")); + // assert that all appenders were found + Assertions.assertTrue(actualLevels.containsKey("Console")); + Assertions.assertTrue(actualLevels.containsKey("MainFile")); - // assert that logger has correct levels - for (Map.Entry entry : actualLevels.entrySet()) { - Assertions.assertEquals(expectedLevel, entry.getValue()); - } - // reset the logger levels to original values - for (String appender : new String[]{ "Console", "MainFile" }) - { - rootLoggerConfig.removeAppender(appender); - rootLoggerConfig.addAppender(config.getAppender(appender), originalLevel, null); + // assert that logger has correct levels + for (Map.Entry entry : actualLevels.entrySet()) { + Assertions.assertEquals(expectedLevel, entry.getValue()); + } + // reset the logger levels to original values + for (String appender : new String[]{ "Console", "MainFile" }) + { + rootLoggerConfig.removeAppender(appender); + rootLoggerConfig.addAppender(config.getAppender(appender), originalLevel, null); + } + ctx.updateLoggers(); } - ctx.updateLoggers(); } } From a794cfd6a10a28dbc50b3cae221de417485919b5 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:18:56 +0200 Subject: [PATCH 130/323] Annotate packages as NonNull --- src/main/java/io/pzstorm/storm/core/package-info.java | 4 ++++ src/main/java/io/pzstorm/storm/event/package-info.java | 4 ++++ src/main/java/io/pzstorm/storm/hook/package-info.java | 4 ++++ src/main/java/io/pzstorm/storm/util/package-info.java | 4 ++++ 4 files changed, 16 insertions(+) create mode 100644 src/main/java/io/pzstorm/storm/core/package-info.java create mode 100644 src/main/java/io/pzstorm/storm/event/package-info.java create mode 100644 src/main/java/io/pzstorm/storm/hook/package-info.java create mode 100644 src/main/java/io/pzstorm/storm/util/package-info.java diff --git a/src/main/java/io/pzstorm/storm/core/package-info.java b/src/main/java/io/pzstorm/storm/core/package-info.java new file mode 100644 index 0000000..622dd6c --- /dev/null +++ b/src/main/java/io/pzstorm/storm/core/package-info.java @@ -0,0 +1,4 @@ +@NonNullPackage +package io.pzstorm.storm.core; + +import io.pzstorm.storm.NonNullPackage; \ No newline at end of file diff --git a/src/main/java/io/pzstorm/storm/event/package-info.java b/src/main/java/io/pzstorm/storm/event/package-info.java new file mode 100644 index 0000000..f54c5a7 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/event/package-info.java @@ -0,0 +1,4 @@ +@NonNullPackage +package io.pzstorm.storm.event; + +import io.pzstorm.storm.NonNullPackage; \ No newline at end of file diff --git a/src/main/java/io/pzstorm/storm/hook/package-info.java b/src/main/java/io/pzstorm/storm/hook/package-info.java new file mode 100644 index 0000000..5510c69 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/hook/package-info.java @@ -0,0 +1,4 @@ +@NonNullPackage +package io.pzstorm.storm.hook; + +import io.pzstorm.storm.NonNullPackage; \ No newline at end of file diff --git a/src/main/java/io/pzstorm/storm/util/package-info.java b/src/main/java/io/pzstorm/storm/util/package-info.java new file mode 100644 index 0000000..7643d01 --- /dev/null +++ b/src/main/java/io/pzstorm/storm/util/package-info.java @@ -0,0 +1,4 @@ +@NonNullPackage +package io.pzstorm.storm.util; + +import io.pzstorm.storm.NonNullPackage; \ No newline at end of file From cccc905285b437a7a751eb96c6be421e6577006f Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:19:59 +0200 Subject: [PATCH 131/323] Improve handling nullability --- src/main/java/io/pzstorm/storm/core/StormBootstrap.java | 4 +++- src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormBootstrap.java b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java index 60e3461..3e5c5d1 100644 --- a/src/main/java/io/pzstorm/storm/core/StormBootstrap.java +++ b/src/main/java/io/pzstorm/storm/core/StormBootstrap.java @@ -1,5 +1,7 @@ package io.pzstorm.storm.core; +import org.jetbrains.annotations.Nullable; + import java.lang.reflect.Method; /** @@ -77,7 +79,7 @@ class StormBootstrap { * @throws ReflectiveOperationException if an error occurred while invoking method. * @see StormClassTransformers#getRegistered(String) */ - static Object getRegisteredTransformer(String name) throws ReflectiveOperationException { + static @Nullable Object getRegisteredTransformer(String name) throws ReflectiveOperationException { return TRANSFORMER_GETTER.invoke(null, name); } diff --git a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java index 2b217c2..5262219 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java @@ -21,8 +21,8 @@ class StormClassLoaderTest extends StormClassLoader implements UnitTest { private static final ClassLoader CL = StormClassLoaderTest.class.getClassLoader(); - private static final URL CLASSES_RESOURCE_DIR = CL.getResource("./classes/"); - private static final URL DELEGATE_RESOURCE_DIR = CL.getResource("./delegate/"); + private static final URL CLASSES_RESOURCE_DIR = Objects.requireNonNull(CL.getResource("./classes/")); + private static final URL DELEGATE_RESOURCE_DIR = Objects.requireNonNull(CL.getResource("./delegate/")); StormClassLoaderTest() { super(new URL[]{ CLASSES_RESOURCE_DIR, DELEGATE_RESOURCE_DIR }); From 497cec553f5fdcef6349ed326af3d6f31a307775 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:26:01 +0200 Subject: [PATCH 132/323] Suppress warnings on class level --- .../java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java | 1 + .../java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java | 2 +- src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java | 1 + src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java | 1 + src/zombie/java/se/krka/kahlua/vm/KahluaTable.java | 1 + src/zombie/java/zombie/ZombieHello.java | 2 +- src/zombie/java/zombie/core/Core.java | 1 + src/zombie/java/zombie/gameStates/MainScreenState.java | 1 + src/zombie/java/zombie/ui/TextManager.java | 1 + src/zombie/java/zombie/ui/UIElement.java | 1 + src/zombie/java/zombie/ui/UIFont.java | 2 +- 11 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java b/src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java index 488fde1..a0212ac 100644 --- a/src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java +++ b/src/main/java/io/pzstorm/storm/event/OnMainScreenRenderEvent.java @@ -7,6 +7,7 @@ * * @see MainScreenState#render() */ +@SuppressWarnings("unused") public class OnMainScreenRenderEvent implements ZomboidEvent { @Override diff --git a/src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java b/src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java index fd2c3a6..f956a1f 100644 --- a/src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java +++ b/src/main/java/io/pzstorm/storm/event/OnUIElementPreRenderEvent.java @@ -7,7 +7,7 @@ * * @see UIElement#render() */ -@SuppressWarnings("WeakerAccess") +@SuppressWarnings({ "WeakerAccess", "unused" }) public class OnUIElementPreRenderEvent implements ZomboidEvent { public final UIElement element; diff --git a/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java index bbfb120..8759e10 100644 --- a/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java +++ b/src/test/java/io/pzstorm/storm/core/ZombieHelloTransformer.java @@ -5,6 +5,7 @@ import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.LdcInsnNode; +@SuppressWarnings("unused") public class ZombieHelloTransformer extends StormClassTestTransformer { public ZombieHelloTransformer() { diff --git a/src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java b/src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java index 133e562..1f85116 100644 --- a/src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java +++ b/src/zombie/java/se/krka/kahlua/j2se/KahluaTableImpl.java @@ -6,6 +6,7 @@ import java.util.Map; @ZomboidClass +@SuppressWarnings("ALL") public class KahluaTableImpl implements KahluaTable { public Map delegate; diff --git a/src/zombie/java/se/krka/kahlua/vm/KahluaTable.java b/src/zombie/java/se/krka/kahlua/vm/KahluaTable.java index 8752f23..d32c73e 100644 --- a/src/zombie/java/se/krka/kahlua/vm/KahluaTable.java +++ b/src/zombie/java/se/krka/kahlua/vm/KahluaTable.java @@ -3,6 +3,7 @@ import zombie.ZomboidClass; @ZomboidClass +@SuppressWarnings("ALL") public interface KahluaTable { } diff --git a/src/zombie/java/zombie/ZombieHello.java b/src/zombie/java/zombie/ZombieHello.java index 7e9a4ae..dddca2d 100644 --- a/src/zombie/java/zombie/ZombieHello.java +++ b/src/zombie/java/zombie/ZombieHello.java @@ -3,7 +3,7 @@ import org.jetbrains.annotations.TestOnly; @TestOnly -@SuppressWarnings({ "unused", "WeakerAccess" }) +@SuppressWarnings("ALL") public class ZombieHello { @SuppressWarnings("UseOfSystemOutOrSystemErr") diff --git a/src/zombie/java/zombie/core/Core.java b/src/zombie/java/zombie/core/Core.java index 48d71d2..50d1f9a 100644 --- a/src/zombie/java/zombie/core/Core.java +++ b/src/zombie/java/zombie/core/Core.java @@ -3,6 +3,7 @@ import zombie.ZomboidClass; @ZomboidClass +@SuppressWarnings("ALL") public class Core { public static int width = 0; diff --git a/src/zombie/java/zombie/gameStates/MainScreenState.java b/src/zombie/java/zombie/gameStates/MainScreenState.java index 255db34..6dee3f2 100644 --- a/src/zombie/java/zombie/gameStates/MainScreenState.java +++ b/src/zombie/java/zombie/gameStates/MainScreenState.java @@ -3,6 +3,7 @@ import zombie.ZomboidClass; @ZomboidClass +@SuppressWarnings("ALL") public class MainScreenState { public void render() { diff --git a/src/zombie/java/zombie/ui/TextManager.java b/src/zombie/java/zombie/ui/TextManager.java index 101103c..1b84a2e 100644 --- a/src/zombie/java/zombie/ui/TextManager.java +++ b/src/zombie/java/zombie/ui/TextManager.java @@ -3,6 +3,7 @@ import zombie.ZomboidClass; @ZomboidClass +@SuppressWarnings("ALL") public class TextManager { public static TextManager instance; diff --git a/src/zombie/java/zombie/ui/UIElement.java b/src/zombie/java/zombie/ui/UIElement.java index 44d2313..d25a359 100644 --- a/src/zombie/java/zombie/ui/UIElement.java +++ b/src/zombie/java/zombie/ui/UIElement.java @@ -4,6 +4,7 @@ import zombie.ZomboidClass; @ZomboidClass +@SuppressWarnings("ALL") public class UIElement { public UIElement Parent; diff --git a/src/zombie/java/zombie/ui/UIFont.java b/src/zombie/java/zombie/ui/UIFont.java index 4f5c0e5..4d7d84f 100644 --- a/src/zombie/java/zombie/ui/UIFont.java +++ b/src/zombie/java/zombie/ui/UIFont.java @@ -3,7 +3,7 @@ import zombie.ZomboidClass; @ZomboidClass -@SuppressWarnings("unused") +@SuppressWarnings("ALL") public enum UIFont { Small, From f497a54324868bec85698be7efbd758138e945d2 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:26:19 +0200 Subject: [PATCH 133/323] Remove redundant event subscription --- src/main/java/io/pzstorm/storm/event/StormEventHandler.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/event/StormEventHandler.java b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java index 7e2f54b..8c03d7a 100644 --- a/src/main/java/io/pzstorm/storm/event/StormEventHandler.java +++ b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java @@ -14,10 +14,6 @@ */ public class StormEventHandler { - @SubscribeEvent - public static void handleRenderEvent(OnMainScreenRenderEvent event) { - } - @SubscribeEvent public static void handleUIElementRenderEvent(OnUIElementPreRenderEvent event) { From 538cccf6237e50c53c684bd3ec6c9d1bdc1e5831 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:32:41 +0200 Subject: [PATCH 134/323] Suppress more warnings on class level --- src/main/java/io/pzstorm/storm/core/StormClassTransformers.java | 2 +- src/main/java/io/pzstorm/storm/event/StormEventHandler.java | 1 + src/main/java/io/pzstorm/storm/event/SubscribeEvent.java | 1 + src/main/java/io/pzstorm/storm/event/ZomboidEvent.java | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java index bb90e9b..6ebdbf5 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformers.java @@ -14,7 +14,7 @@ * This class defines, initializes and stores {@link StormClassTransformer} instances. * To retrieve a mapped instance of registered transformer call {@link #getRegistered(String)}. */ -@SuppressWarnings("WeakerAccess") +@SuppressWarnings({ "WeakerAccess", "unused" }) public class StormClassTransformers { /** diff --git a/src/main/java/io/pzstorm/storm/event/StormEventHandler.java b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java index 8c03d7a..675ce4a 100644 --- a/src/main/java/io/pzstorm/storm/event/StormEventHandler.java +++ b/src/main/java/io/pzstorm/storm/event/StormEventHandler.java @@ -12,6 +12,7 @@ * Sometimes subscribing to events is not enough to alter game behavior and more invasive * actions need to be preformed, like editing or removing lines from game code. */ +@SuppressWarnings("unused") public class StormEventHandler { @SubscribeEvent diff --git a/src/main/java/io/pzstorm/storm/event/SubscribeEvent.java b/src/main/java/io/pzstorm/storm/event/SubscribeEvent.java index 168d793..7cbd433 100644 --- a/src/main/java/io/pzstorm/storm/event/SubscribeEvent.java +++ b/src/main/java/io/pzstorm/storm/event/SubscribeEvent.java @@ -14,5 +14,6 @@ */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) +@SuppressWarnings("WeakerAccess") public @interface SubscribeEvent { } diff --git a/src/main/java/io/pzstorm/storm/event/ZomboidEvent.java b/src/main/java/io/pzstorm/storm/event/ZomboidEvent.java index 3626081..dec63bd 100644 --- a/src/main/java/io/pzstorm/storm/event/ZomboidEvent.java +++ b/src/main/java/io/pzstorm/storm/event/ZomboidEvent.java @@ -7,6 +7,7 @@ * {@link StormHook}s and included as a method parameter in a callback to {@link StormEventDispatcher}. * They are then dispatched to all methods that subscribe to those specific events. */ +@SuppressWarnings("WeakerAccess") public interface ZomboidEvent { /** From b5be7a6ad6c235da101e61031c27ec12e9dcf4e0 Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:32:56 +0200 Subject: [PATCH 135/323] Improve StormClassTransformer encapsulation --- .../java/io/pzstorm/storm/core/StormClassTransformer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java index 35269a1..ecd1b3b 100644 --- a/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java +++ b/src/main/java/io/pzstorm/storm/core/StormClassTransformer.java @@ -23,9 +23,9 @@ */ public abstract class StormClassTransformer { - protected final String className; - protected final ClassNode visitor; private @Nullable ClassReader classReader; + final String className; + final ClassNode visitor; StormClassTransformer(String className, ClassNode visitor) { From 184e780b624e332b56ea208054701206d21933ea Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:45:36 +0200 Subject: [PATCH 136/323] Create IntegrationTest interface --- src/test/java/io/pzstorm/storm/IntegrationTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/test/java/io/pzstorm/storm/IntegrationTest.java diff --git a/src/test/java/io/pzstorm/storm/IntegrationTest.java b/src/test/java/io/pzstorm/storm/IntegrationTest.java new file mode 100644 index 0000000..ca8ba14 --- /dev/null +++ b/src/test/java/io/pzstorm/storm/IntegrationTest.java @@ -0,0 +1,8 @@ +package io.pzstorm.storm; + +import org.junit.jupiter.api.Tag; + +@Tag("integration") +public interface IntegrationTest { + +} From 08aa7687721d7dbcc209f706d190cf7f5802d65c Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:46:09 +0200 Subject: [PATCH 137/323] Convert unit tests to integration tests --- ...derTest.java => StormClassLoaderIntegrationTest.java} | 9 ++++----- ...st.java => StormClassTransformerIntegrationTest.java} | 5 ++--- 2 files changed, 6 insertions(+), 8 deletions(-) rename src/test/java/io/pzstorm/storm/core/{StormClassLoaderTest.java => StormClassLoaderIntegrationTest.java} (94%) rename src/test/java/io/pzstorm/storm/core/{StormClassTransformerTest.java => StormClassTransformerIntegrationTest.java} (95%) diff --git a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java b/src/test/java/io/pzstorm/storm/core/StormClassLoaderIntegrationTest.java similarity index 94% rename from src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java rename to src/test/java/io/pzstorm/storm/core/StormClassLoaderIntegrationTest.java index 5262219..3faec22 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassLoaderTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassLoaderIntegrationTest.java @@ -9,22 +9,21 @@ import java.nio.file.Files; import java.util.*; +import io.pzstorm.storm.IntegrationTest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import io.pzstorm.storm.UnitTest; - @SuppressWarnings("SpellCheckingInspection") -class StormClassLoaderTest extends StormClassLoader implements UnitTest { +class StormClassLoaderIntegrationTest extends StormClassLoader implements IntegrationTest { - private static final ClassLoader CL = StormClassLoaderTest.class.getClassLoader(); + private static final ClassLoader CL = StormClassLoaderIntegrationTest.class.getClassLoader(); private static final URL CLASSES_RESOURCE_DIR = Objects.requireNonNull(CL.getResource("./classes/")); private static final URL DELEGATE_RESOURCE_DIR = Objects.requireNonNull(CL.getResource("./delegate/")); - StormClassLoaderTest() { + StormClassLoaderIntegrationTest() { super(new URL[]{ CLASSES_RESOURCE_DIR, DELEGATE_RESOURCE_DIR }); } diff --git a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java b/src/test/java/io/pzstorm/storm/core/StormClassTransformerIntegrationTest.java similarity index 95% rename from src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java rename to src/test/java/io/pzstorm/storm/core/StormClassTransformerIntegrationTest.java index 74d2afe..0a78602 100644 --- a/src/test/java/io/pzstorm/storm/core/StormClassTransformerTest.java +++ b/src/test/java/io/pzstorm/storm/core/StormClassTransformerIntegrationTest.java @@ -3,12 +3,11 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import io.pzstorm.storm.IntegrationTest; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.pzstorm.storm.UnitTest; - -class StormClassTransformerTest implements UnitTest { +class StormClassTransformerIntegrationTest implements IntegrationTest { private static final Class STORM_CLASS_TRANSFORMER, STORM_CLASS_TRANSFORMERS; From 3b95ad818473eba6099417137fe882e93c48c54b Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 7 May 2021 23:50:37 +0200 Subject: [PATCH 138/323] Add test IDEA run configurations --- .idea/runConfigurations/Run_All_Tests.xml | 12 ++++++++++++ .idea/runConfigurations/Run_Integration_Tests.xml | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 .idea/runConfigurations/Run_All_Tests.xml create mode 100644 .idea/runConfigurations/Run_Integration_Tests.xml diff --git a/.idea/runConfigurations/Run_All_Tests.xml b/.idea/runConfigurations/Run_All_Tests.xml new file mode 100644 index 0000000..28c8c2a --- /dev/null +++ b/.idea/runConfigurations/Run_All_Tests.xml @@ -0,0 +1,12 @@ + + + +