Skip to content

Instantly share code, notes, and snippets.

@Barteks2x
Last active May 9, 2017 19:16
Show Gist options
  • Save Barteks2x/421d69c3090d87d7d69a117482bdbcd0 to your computer and use it in GitHub Desktop.
Save Barteks2x/421d69c3090d87d7d69a117482bdbcd0 to your computer and use it in GitHub Desktop.
A hacky piece of code to allow effective debug mode without debug mode in Minecraft/Forge, and without the restrictions of hotswapping. And the slowness and bugginess of DCEVM. And I know the naming is nowhere near standard. This is intentional.
package DEBUG;
import net.minecraftforge.fml.common.gameevent.TickEvent;
// NOTE: This needs to be the first class listed after implements, and it cannot use fully qualified class name, cannot be split into multiple lines
// (the string "implements __MAIN" is matched)
public interface __MAIN {
void __MAIN(int modifier, int key, TickEvent.WorldTickEvent evt);
default void __TICK(TickEvent.WorldTickEvent evt) {}
default int[] GET_MOD() {return new int[]{};}
default int[] GET_KEY() {return new int[]{};}
}
package DEBUG;
import static org.apache.commons.io.FileUtils.listFiles;
import static org.lwjgl.input.Keyboard.KEY_D;
import static org.lwjgl.input.Keyboard.KEY_LCONTROL;
import static org.lwjgl.input.Keyboard.KEY_RCONTROL;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.input.Keyboard;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UncheckedIOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
/**
* A hack to dynamically compile, load and run code from external classes.
*
* The easiest way to get it working is to put your custom classes into tests directory so you get all IDE features there, but without your IDE
* compiling ti and adding it to classpath.
*
* Known limitations:
* * I'm not sure if these classes are ever unloaded
* * One of your classes has to implement __MAIN and provide it's own key-modifier pair to be activated, or implement __TICK. First initial
* activation should be done using ctrl+D.
*/
public class D {
private static final Logger log = LogManager.getLogger("DEBUG_HACK");
private static int[] modifiers = {};
private static int[] allowKeys = {};
private static __MAIN currentMain = (k1, k2, evt) -> {
};
public static void init() {
final String classpath = "/home/bartosz/Desktop/dev/java/Minecraft/Genesis/Genesis/src/test/java/";
// TODO: DEBUG. DELETE THIS BEFORE COMMIT
MinecraftForge.EVENT_BUS.register(new Object() {
public Class<?> makeClass() {
Collection<File> inputClasses = findFileList(classpath);
File mainClass = findMain(inputClasses);
if (mainClass == null) {
return null;
}
String mainClassName = findClassName(mainClass, classpath);
if (mainClassName == null) {
return null;
}
try {
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
List<String> optionList = new ArrayList<>();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + ";dist/InlineCompiler.jar");
Iterable<? extends JavaFileObject> compilationUnit = fileManager.getJavaFileObjectsFromFiles(inputClasses);
JavaCompiler.CompilationTask task = compiler.getTask(
null,
fileManager,
diagnostics,
optionList,
null,
compilationUnit);
log("Trying to compile classes...");
if (task.call()) {
log("Compilation successful!");
URLClassLoader classLoader = new URLClassLoader(new URL[]{new
File(classpath).toURI().toURL()},
D.class.getClassLoader());
Class<?> loadedClass = classLoader.loadClass(mainClassName);
fileManager.close();
return loadedClass;
} else {
diagnostics.getDiagnostics().forEach(diagnostic -> {
log("Error on line {} in {}",
diagnostic.getLineNumber(),
diagnostic.getSource().toUri());
log("Kind: {}, message: {}", diagnostic.getKind(), diagnostic.getMessage(Locale.ENGLISH));
});
fileManager.close();
cleanup(classpath);
return null;
}
} catch (IOException | UncheckedIOException | ClassNotFoundException exp) {
throw new Error(exp);
}
}
@SubscribeEvent
public void onWorldTick(TickEvent.WorldTickEvent evt) {
currentMain.__TICK(evt);
int modifier = getKey(modifiers);
int key = getKey(allowKeys);
boolean isForced = isForced();
if (!isForced && (modifier == 0 || key == 0)) {
return;
}
try {
Class<? extends __MAIN> cl = (Class<? extends __MAIN>) makeClass();
currentMain = cl.newInstance();
if (isForced) {
log("Getting new key data...");
modifiers = currentMain.GET_MOD();
allowKeys = currentMain.GET_KEY();
log("Done getting key data!");
}
if (modifier != 0 && key != 0) {
log("Running the code...");
currentMain.__MAIN(modifier, key, evt);
log("Done!");
}
} catch (Exception e) {
e.printStackTrace();
return;
} finally {
cleanup(classpath);
}
}
});
}
private static void cleanup(String classpath) {
listFiles(new File(classpath), new String[]{"class"}, true).forEach(f -> {
f.delete();
});
}
private static boolean isForced() {
return (Keyboard.isKeyDown(KEY_RCONTROL) || Keyboard.isKeyDown(KEY_LCONTROL)) && Keyboard.isKeyDown(KEY_D);
}
private static int getKey(int[] keys) {
for (int i : keys) {
if (Keyboard.isKeyDown(i)) {
return i;
}
}
return 0;
}
private static Collection<File> findFileList(String classpath) {
log("Finding classes...");
Collection<File> col = listFiles(new File(classpath), new String[]{"java"}, true);
col.forEach(f -> log("Found {}", f.getPath()));
return col;
}
private static File findMain(Collection<File> inputClasses) {
log("Finding main class...");
final File[] __mainFile = {null};
inputClasses.forEach(cf -> {
try (Scanner scanner = new Scanner(cf)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.contains("implements __MAIN")) {
if (__mainFile[0] != null) {
log("!!!!!!!! ERROR! Found more than one MAIN class! Ignoring... !!!!!!!!");
} else {
__mainFile[0] = cf;
}
}
}
} catch (FileNotFoundException e) {
throw new UncheckedIOException(e);
}
});
if (__mainFile[0] == null) {
log("!!!!!!!! ERROR! No main class found, IT WON'T WORK (printing stacktrace, not throwing) !!!!!!!!");
new NullPointerException().printStackTrace();
}
return __mainFile[0];
}
private static String findClassName(File mainClass, String classpath) {
String path = mainClass.getPath();
if (!path.startsWith(classpath)) {
log("ERROR! Class file name {} doesn't start with path {}", path, classpath);
return null;
}
String name = path.substring(classpath.length());
if (name.startsWith("/") || name.startsWith("\\")) {
name = name.substring(1);
}
if (name.endsWith(".java")) {
name = name.substring(0, name.length() - ".java".length());
}
name = name.replaceAll("[/\\\\]", ".");
log("Found main class name: {}", name);
return name;
}
private static void log(String fmt, Object... data) {
log.info(fmt, data);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment