Last active
May 9, 2017 19:16
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[]{};} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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