Jump to content

[1.8][Java] Nashorn vs. Forge vs. Me!


Recommended Posts

Posted

So basically I wanted to have Nashorn script engine, but its quite annoying that everything typed in .js would have to follow MC/Forge obfuscation.

 

Is there any way to make it look nice?

 

My idea was to wrap everything with interfaces (e.g you don't use EntityLivingBase but IEEP interface and that interface holds shitload of getter methods that return EntityLivingBase's data and/or next wrapped classes). Problem with it would be next shitload of code and issue of not everything be "wrappable" i think.

 

What I am thinking of is write scripts in deobf format and then during loading script somehow replace names with obf ones. Any hints on this would be nice (reseaching process).

  Quote

1.7.10 is no longer supported by forge, you are on your own.

Posted

What I did for my project was to create wrappers for everything the script-writer would need. This way FG would reobfuscate the internal references to MC code but the names of my wrappers would stay the same. However, this may not be feasible if you want to provide access to a large amount of stuff.

 

P.S. Do you know if Nashorn has been updated yet so the ClassFilter interface exists in the JRE not just the JDK?

 

Edit: P.P.S. Feel free to look at/reuse my code.

Don't make mods if you don't know Java.

Check out my website: http://shadowfacts.net

Developer of many mods

Posted

Pretty much what I had in mind at first (make wrappers).

 

I don't know answer to your question, but it would be easy to check, wouldn't it? :)

 

Thanks for offering your code, I might almost-copy some of it, but I need to add a lot of mod-specific stuff anyway so... :D

  Quote

1.7.10 is no longer supported by forge, you are on your own.

Posted

@shadowfacts

Did you have any problems with NoClassDefFound when trying to access Nashorn?

 

You code:

NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
scriptEngine = factory.getScriptEngine(new SJSClassFilter());

 

Would give me NCDF saying Nashorn is not loaded by ClassLoader (wtf?).

 

I tried using ScriptEngineManager#getEngineByName("nashorn"); but that returned "null" - so basically, again, Nashorn was not loaded and not registered as engine.

 

Then I managed to make it work with "new ScriptEngineManager(null)" - where null (god knows why) magically made everything work. Something to do with ClassLoaders obviously, but if anyone knows the deal, I'd be happy to hear it (tho issue is resolved?).

  Quote

1.7.10 is no longer supported by forge, you are on your own.

Posted
  On 12/11/2015 at 10:39 PM, shadowfacts said:

Can you post the whole stacktrace? Last time I checked (a couple months ago) Nashorn was loading fine.

 

Also, is this in the dev-env or normal?

 

It was in dev, seems like problem is quite common (after googling). I will look into this later, for now - if anyone deals in JS/Nashorn - you could answer some of those:

http://stackoverflow.com/questions/34239629/lifespan-of-code-loaded-with-nashorn-evalfile-string

 

I honestly have too little exp in JS to be 100% sure just by reading documentations.

  Quote

1.7.10 is no longer supported by forge, you are on your own.

Posted

God... those Bindings and Contextes are so confusing...

 

@shadowfacts

I just saw (your repo) you also started using the "way of Manager" (new ScriptEngineManager(null)). Does this mean that "NashornScriptEngineFactory" also doesn't work for you anymore?

Because of that I now can't apply ClassFilter on ScriptEngine. Have you found any workarounds (without reflection)?

 

I kinda feel like this (the problem) originates from MC/Forge because hell - I cannot replicate it anywhere outside Forge workspace.

  Quote

1.7.10 is no longer supported by forge, you are on your own.

Posted

Anyway, seems like noone is remotely close (talking about non-forge forums) to solving this shit so here goes in-your-face-java way:

 

public void initScriptEngine()
{
try
{
	ClassLoader cl = Launcher.getLauncher().getClassLoader(); // sun.misc.Launcher, seems like only loader that has access to jdk.nashorn.* packages.
	Class<?> factoryClass = cl.loadClass("jdk.nashorn.api.scripting.NashornScriptEngineFactory");
	Class<?> filterClass = cl.loadClass("jdk.nashorn.api.scripting.ClassFilter");
	Class<?> jsFilterClass = cl.loadClass("something.ernio.core.script.JSClassFilter"); // I also have to load my own classes, since this class extends nashorn's package class and using it directly will also crash.

	ScriptEngineFactory sef = (ScriptEngineFactory) factoryClass.getConstructor().newInstance(); // Obvious thing to do.

	Method getScriptEngine = sef.getClass().getMethod("getScriptEngine", filterClass);

	Object filter = jsFilterClass.newInstance(); // Initialize my filter.

	this.scriptEngine = (ScriptEngine) getScriptEngine.invoke(sef, filter); // BOOM - da engine runz!
}
catch (ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
		e.printStackTrace(); // Obvious bad try-catch code is obvious.
}
}

 

Note that while above gives what we want it to give, it is: 1. Almost badly coded; 2. I'd say rather not really secure; 3. WTF?!

 

Note: I have no idea what can happen outside dev. env. - above is used as a fallback method of getting engine to work after trying to initialize it normal way.

 

So yeah... thread is open if someone finds a way. :D

  Quote

1.7.10 is no longer supported by forge, you are on your own.

Posted
  On 12/15/2015 at 2:02 AM, Ernio said:

I now can't apply ClassFilter on ScriptEngine. Have you found any workarounds (without reflection)?

 

In order to set Nashorn's permissions (to e.g have access only to packages of your choice) you need to create ClassFilter and apply it to "getScriptEngine(ClassFilter)" method that is ONLY available in nashorn's class (NashornScriptEngineFactory).

 

  Quote

You are interacting with the scripting API wrongly. You shouldn't directly interact with the nashorn classes like that.

 

It is officially stated that you can and should interact with NashornScriptEngineFactory in order to apply this kind of ClassFilter, BUT Forge Mod-loading system doesn't allow you to do so (don't ask me why :C), because who-knows-why - while nashorn's classes are loaded and working fine, Forge doesn't seem to recognise them (like they are not loaded).

 

  Quote

ScriptEngineManager manager = new ScriptEngineManager(ClassLoader.getSystemClassLoader());
ScriptEngine engine = manager.getEngineByName("JavaScript");
engine.eval("print(\"hello world\")");

 

This doesn't allow you to apply ClassFilter, since ClassFilter is nashorn-specific.

  Quote

1.7.10 is no longer supported by forge, you are on your own.

  • 1 year later...
Posted (edited)

I got the same results while developing a mod in which one can use javascript to interact through a block with the world. 

 

The problem is that the JRE jars are not in the class path at runtime. 

One can test this with a few lines of code inside the mods constructor or preInit (etc.) method: 

 

URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
URL[] urls = systemClassLoader.getURLs();
for (URL item : urls) {
	System.out.println(item.toString());
}

 

This prints all the paths to the necessary jar files (like nashorn.jar, located in ${JAVA_HOME}jre/libs/ext) into the stdout.

Within IntelliJ Idea (via GradleStart) everything is fine and the class path is intact. 

 

However, running a minecraft forge server, only the jars located in the mods folder are in the class path. The "why" is a miracle to me.. :|

A workaround is to just fix the class path via reflection: 

 

try {
	String javaHomeDir = System.getenv("JAVA_HOME");
	if (StringUtils.isNullOrEmpty(javaHomeDir)) {
		System.out.println("Unable to add java jars to the system class loader: JAVA_HOME is not set.");
	} else {
		if (!javaHomeDir.endsWith("/")) {
			javaHomeDir += "/";
		}

		File javaLibraryFolder = new File(javaHomeDir + "jre/lib");
		Collection<File> jarFiles = FileUtils.listFiles(javaLibraryFolder, new String[]{"jar"}, true);

		URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
		Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{ URL.class });
		// This might fail if a security manager is active 
		method.setAccessible(true);

		for (File jarFile : jarFiles) {
			System.out.println("Adding " + jarFile + " to system class loader.");

			method.invoke(systemClassLoader, new Object[] { jarFile.toURI().toURL() });
		}
	}
} catch (Throwable ex) {
	throw new RuntimeException("Unable to add java libraries from java home to system class loader: " + ex.getMessage(), ex);
}

 

After adding them back, one can use the nashorn specific classes again. 

Edit: Made a mistake, this does not work. Reporting back if a solution has been found.

Edit 2: Found a workaround. Put a symlink to the "nashorn.jar" into the "mods" folder: 

// Linux
ln -s ${JAVA_HOME}/jre/lib/ext/nashorn.jar nashorn.jar

// Windows 
mklink nashorn.jar "C:\Program Files\Java\jdk1.8.0_92\jre\lib\ext\nashorn.jar"

 

P.S.: Using the argument "-cp" or setting the environment variable "CLASSPATH" does not seem to have any effect. 

 

 

Edited by Lunatix
Posted

Holy cow! What are the odds?!

I've been gone for like a year now (busy with other stuff, sadly) and I just kinda enter the forums (first time in months) and BAM! I see this on front page (ghosts of the past :P)

 

And by the way - random hello to you guys (especially veterans) - you have a nice thing going on here :)

  Quote

1.7.10 is no longer supported by forge, you are on your own.

Posted

Yeah I think the odds are quite low since I just begun with my mod a few weeks ago ;) 

We started playing Minecraft again and I remembered a mod named "ComputerCraft" which used LUA as the scripting language.. and decided to try my luck and write my own mod. 

 

However, if you decide to work again on your mod and have any questions in how to get rhino running (or anything else) feel free to consult me.  

 

Oh and if someone else knows how to put a reference to a library without that symlink hack or how to tell forge that my mod requires the entire JDK in the classpath, that would be nice!

 

 

 

  • 1 year later...
Posted

I have found the solution to this problem.

Check out my code:-

/**
 * Uses hack to get a NashornScriptEngineFactory object.
 * @param nashornUsedClasses all classes detected in the NashornScriptEngineFactory class are added to this map where the key is the full name of the class.
 * @return the NashornScriptEngineFactory object
 */
private static ScriptEngineFactory getNashornScriptEngineFactory(Map < String, Class << ? >> nashornUsedClasses) {
    // First create a new ScriptEngineManager with null class loader to force it to load classes automatically.
    LOGGER.log(Level.INFO, "Attempting hack to get NashornScriptEngineFactory object.");
    ScriptEngineManager s = new ScriptEngineManager(null);
    LOGGER.log(Level.DEBUG, "New ScriptEngineManager with 'null' classloader created.");

    // DEBUG
    LOGGER.log(Level.DEBUG, "Loaded ScriptEngineFactory count = " + s.getEngineFactories().size());
    LOGGER.log(Level.DEBUG, "getEngineByName(\"nashorn\") = " + s.getEngineByName("nashorn"));

    // Loop through list of ScriptEngineFactory to find NashornScriptEngineFactory
    for (ScriptEngineFactory f: s.getEngineFactories()) {
        // Check name
        if (f.getEngineName().endsWith("Nashorn")) {
            LOGGER.log(Level.INFO, "Found Nashorn ScriptEngineFactory.");
            LOGGER.log(Level.INFO, "Factory engine name = " + f.getEngineName());
            LOGGER.log(Level.INFO, "Factory class = " + f.getClass());

            // List of methods to check for obfuscation
            LOGGER.log(Level.INFO, "Factory Method list:-");
            for (Method method: f.getClass().getDeclaredMethods()) {
                LOGGER.log(Level.INFO, method.getName());
                for (Class << ? > c : method.getParameterTypes()) {
                    LOGGER.log(Level.DEBUG, c.getName());
                    nashornUsedClasses.put(c.getName(), c);
                }
            }

            return f;
        }
    }
    return null;
}

 The above function gives us a NashornScriptEngineFactory instance and populates the nashornUsedClasses map with the class objects detected in the NashornScriptEngineFactory class.

 

We then use this code:-

ScriptEngine engine;
HashMap < String, Class < ? >> nashornUsedClasses = new HashMap < > ();
ScriptEngineFactory nashornFactory = getNashornScriptEngineFactory(nashornUsedClasses);

// Get the ClassFilter class object
Class classFilter = nashornUsedClasses.get("jdk.nashorn.api.scripting.ClassFilter");

if (nashornFactory != null && classFilter != null) {
    // Use Dynamic Proxy to create an instance of ClassFilter(interface)
    Object classFilterInstance = Proxy.newProxyInstance(classFilter.getClassLoader(), new Class < ? > [] {
        classFilter
    }, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("exposeToScripts")) {
                String className = (String) args[0];
                return !(className.startsWith("java") || className.startsWith("jdmcmods") || className.startsWith("javax") || className.equals("net.minecraft.util.Session"));
            } else return false;
        }
    });
    try {
        Method getScriptEngine = nashornFactory.getClass().getMethod("getScriptEngine", classFilter);
        engine = (ScriptEngine) getScriptEngine.invoke(nashornFactory, classFilterInstance);
    } catch (NoSuchMethodException e) {
        LOGGER.log(Level.ERROR, "Couldn't find getScriptEngine(ClassLoader) method in nashornFactory.");
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        LOGGER.log(Level.ERROR, "Couldn't access NashornScriptEngineFacroty's getScriptEngine(ClassFilter) function.");
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        LOGGER.log(Level.ERROR, "Couldn't create NashornScriptEngine from factory using custom ClassLoader.");
        e.printStackTrace();
    }
}

At the end our 'engine' variable has the nashorn engine with our custom ClassFilter.

Posted

Thread is 2.5 years old, Locking...

This is my Forum Signature, I am currently attempting to transform it into a small guide for fixing easier issues using spoiler blocks to keep things tidy.

 

As the most common issue I feel I should put this outside the main bulk:

The only official source for Forge is https://files.minecraftforge.net, and the only site I trust for getting mods is CurseForge.

If you use any site other than these, please take a look at the StopModReposts project and install their browser extension, I would also advise running a virus scan.

 

For players asking for assistance with Forge please expand the spoiler below and read the appropriate section(s) in its/their entirety.

  Reveal hidden contents

 

Guest
This topic is now closed to further replies.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Cracked Launchers are not supported
    • Hi, I have a problem in minecraft java (only in forge 1.20.1), When I start the game after a moment the game crashed with code 1 this only in forge 1.20.1 , I tried to reinstall java, Upgrade java to 17, update the drivers to the latest version, downgrade the drivers to the pervious version, deleting .minecraft and reinstall it , but none of these ways working.   here is the log:   [Launcher] Launching Minecraft... I'm hiding! mods after C:\Users\Windows\AppData\Roaming\.minecraft\mods\tl_skin_cape_forge_1.20_1.20.1-1.32.jar [InnerMinecraftServersImpl]  search changers of the servers read servers from servers.dat [] [InnerMinecraftServersImpl]  prepare inner servers save servers to servers.dat [Launcher] Game skin type: TLAUNCHER [Launcher] Starting Minecraft Forge 1.20.1... [Launcher] Launching in: C:\Users\Windows\AppData\Roaming\.minecraft Starting garbage collector: 96 / 227 MB Garbage collector completed: 60 / 214 MB [Launcher] Processing post-launch actions. Assist launch: true =============================================================================================== [05:29:03] [main/INFO]: ModLauncher running: args [--username, *********, --version, Forge 1.20.1, --gameDir, C:\Users\Windows\AppData\Roaming\.minecraft, --assetsDir, C:\Users\Windows\AppData\Roaming\.minecraft\assets, --assetIndex, 5, --uuid, *************************************, --accessToken, вќ„вќ„вќ„вќ„вќ„вќ„вќ„вќ„, --clientId, null, --xuid, null, --userType, mojang, --versionType, modified, --width, 925, --height, 530, --launchTarget, forgeclient, --fml.forgeVersion, 47.3.22, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412] [05:29:04] [main/INFO]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.12 by Oracle Corporation; OS Windows 10 arch amd64 version 10.0 [05:29:15] [main/INFO]: Loading ImmediateWindowProvider fmlearlywindow [05:29:24] [main/INFO]: Trying GL version 4.6 [05:29:60] [main/INFO]: Requested GL version 4.6 got version 4.6 [05:29:67] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/Windows/AppData/Roaming/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%23100!/ Service=ModLauncher Env=CLIENT FATAL ERROR in native method: Thread[pool-2-thread-1,5,main]: No context is current or a function that is not available in the current context was called. The JVM will abort execution.     at org.lwjgl.opengl.GL11C.nglGetString(org.lwjgl.opengl@3.3.1+7/Native Method)     at org.lwjgl.opengl.GL11C.glGetString(org.lwjgl.opengl@3.3.1+7/GL11C.java:978)     at net.minecraftforge.fml.earlydisplay.DisplayWindow.initRender(fmlearlydisplay@1.20.1-47.3.22/DisplayWindow.java:209)     at net.minecraftforge.fml.earlydisplay.DisplayWindow.lambda$start$5(fmlearlydisplay@1.20.1-47.3.22/DisplayWindow.java:292)     at net.minecraftforge.fml.earlydisplay.DisplayWindow$$Lambda$437/0x000001fab120a618.run(fmlearlydisplay@1.20.1-47.3.22/Unknown Source)     at java.util.concurrent.Executors$RunnableAdapter.call(java.base@17.0.12/Executors.java:539)     at java.util.concurrent.FutureTask.run(java.base@17.0.12/FutureTask.java:264)     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(java.base@17.0.12/ScheduledThreadPoolExecutor.java:304)     at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17.0.12/ThreadPoolExecutor.java:1136)     at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17.0.12/ThreadPoolExecutor.java:635)     at java.lang.Thread.run(java.base@17.0.12/Thread.java:842) Here I am! [VersionManager] Refreshing versions locally... [VersionManager] Versions has been refreshed (6 ms) [Launcher] Launcher exited. [Launcher] Minecraft closed with exit code: 1 flush now [Launcher] [Crash] Signature "Bad video drivers" matches! [Crash] Signature "Bad video drivers" matches! [Launcher] [Crash] Crash has been recognized! [Crash] Crash has been recognized! flush now
    • https://mclo.gs/9Byd16j Hi, I've had my BetterMC world for a couple days now (1.19.2 vers & Fabric loader) but recently whenever I try to open the profile the minecraft launcher crashes and provides this error code. I've checked both this forum and google and haven't found any similar problems or solution to my problem. I'm not the best at reading crash logs but I gathered that there's an issue with fabric possibly, so I re-downloaded the same one on the modpack, then the latest version for 1.19.2 fabric and the issue still occurred. What can I do now?
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.