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).

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

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?).

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

Posted

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.

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.

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

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

Posted

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).

 

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).

 

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.

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 :)

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.

Spoiler

Logs (Most issues require logs to diagnose):

Spoiler

Please post logs using one of the following sites (Thank you Lumber Wizard for the list):

https://gist.github.com/100MB Requires member (Free)

https://pastebin.com/: 512KB as guest, 10MB as Pro ($$$)

https://hastebin.com/: 400KB

Do NOT use sites like Mediafire, Dropbox, OneDrive, Google Drive, or a site that has a countdown before offering downloads.

 

What to provide:

...for Crashes and Runtime issues:

Minecraft 1.14.4 and newer:

Post debug.log

Older versions:

Please update...

 

...for Installer Issues:

Post your installer log, found in the same place you ran the installer

This log will be called either installer.log or named the same as the installer but with .log on the end

Note for Windows users:

Windows hides file extensions by default so the installer may appear without the .jar extension then when the .log is added the log will appear with the .jar extension

 

Where to get it:

Mojang Launcher: When using the Mojang launcher debug.log is found in .minecraft\logs.

 

Curse/Overwolf: If you are using the Curse Launcher, their configurations break Forge's log settings, fortunately there is an easier workaround than I originally thought, this works even with Curse's installation of the Minecraft launcher as long as it is not launched THROUGH Twitch:

Spoiler
  1. Make sure you have the correct version of Forge installed (some packs are heavily dependent on one specific build of Forge)
  2. Make a launcher profile targeting this version of Forge.
  3. Set the launcher profile's GameDir property to the pack's instance folder (not the instances folder, the folder that has the pack's name on it).
  4. Now launch the pack through that profile and follow the "Mojang Launcher" instructions above.

Video:

Spoiler

 

 

 

or alternately, 

 

Fallback ("No logs are generated"):

If you don't see logs generated in the usual place, provide the launcher_log.txt from .minecraft

 

Server Not Starting:

Spoiler

If your server does not start or a command window appears and immediately goes away, run the jar manually and provide the output.

 

Reporting Illegal/Inappropriate Adfocus Ads:

Spoiler

Get a screenshot of the URL bar or copy/paste the whole URL into a thread on the General Discussion board with a description of the Ad.

Lex will need the Ad ID contained in that URL to report it to Adfocus' support team.

 

Posting your mod as a GitHub Repo:

Spoiler

When you have an issue with your mod the most helpful thing you can do when asking for help is to provide your code to those helping you. The most convenient way to do this is via GitHub or another source control hub.

When setting up a GitHub Repo it might seem easy to just upload everything, however this method has the potential for mistakes that could lead to trouble later on, it is recommended to use a Git client or to get comfortable with the Git command line. The following instructions will use the Git Command Line and as such they assume you already have it installed and that you have created a repository.

 

  1. Open a command prompt (CMD, Powershell, Terminal, etc).
  2. Navigate to the folder you extracted Forge’s MDK to (the one that had all the licenses in).
  3. Run the following commands:
    1. git init
    2. git remote add origin [Your Repository's URL]
      • In the case of GitHub it should look like: https://GitHub.com/[Your Username]/[Repo Name].git
    3. git fetch
    4. git checkout --track origin/master
    5. git stage *
    6. git commit -m "[Your commit message]"
    7. git push
  4. Navigate to GitHub and you should now see most of the files.
    • note that it is intentional that some are not synced with GitHub and this is done with the (hidden) .gitignore file that Forge’s MDK has provided (hence the strictness on which folder git init is run from)
  5. Now you can share your GitHub link with those who you are asking for help.

[Workaround line, please ignore]

 

Guest
This topic is now closed to further replies.

Announcements



×
×
  • Create New...

Important Information

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