AI Scripting for Players?

I am thinking about adding AI scripting functionality to my game (http://www.ageofconquest.com). The goal is to let players create their own AI for the game. The game is turn-based, so the AI script will be called every time a new game turn is calculated.

AI Input:

  • a game object
    AI Output:
  • a list of actions to execute on the game
    Constraints:
  • Required: AI Script can be executed in Java (platform independent)
  • Required: prevent malicious code (e.g. prevent access to file system etc.)
  • Desired: limit AI functionality to just reading the game object and creating actions
  • Desired: AI editor with syntax coloring (what’s good?)

What’s the best strategy to create AI scripting for players? Use an existing language? Which one? Or roll my own?

Let me know if my question is not clear. Thanks :slight_smile:

Probably the easiest (for you) is to launch a separate sandboxed JVM and kill it after a second, if it hasn’t terminated yet. You simply serialize some of your game objects, and load them in ‘their’ JVM (as read-only as it gets, no matter what they write/modify, it won’t alter your real game JVM).

It might sound stupid, but it’s like 30 minutes to implement, and that’s it! Compare that to anything else.

How about launching the code in a separate thread instead of a sandboxed JVM? I would kill the thread if it doesn’t finish after a second (like you proposed for the JVM). I can pass in a copy of the game object. It wouldn’t matter if the AI would modify the game object copy.

Now, how can a prevent the AI code to access the file system/doing malicious stuff? If I load the AI script via a modified ClassLoader, would I be able to prevent the bad from happening?

How about the following for a start ???


...
// Contains the actual Java source code for the AI
String code = ...;

// this is the copy of our game
Game gameCopy = game.copy();

// execute script in separate monitored thread
Thread scriptRunner = new Thread() {
  public void run() {
    // compile and load script via custom class loader
    Script script = ScriptClassLoader.compileAndLoad(code);

    // obtain the actions
    ActionList actions = script.createActions(gameCopy);
    
    // store the actions
    database.store(actions);
  }
}
scriptRunner.start();

// wait for 1 second script to terminate
Thread.sleep(1000);
if (scriptRunner.isActive()) {
  scriptRunner.interrupt();
}
...

Actually, Riven, for your TinyCode competition, what did you do to prevent people from hacking your server?

TinyCode Competition
http://www.java-gaming.org/index.php/topic,17591.30.html

:slight_smile: I didn’t allow any capticals in the sourcecode (also checking unicodes – \uHEXX) and I stop()ed the Thread after 100ms. I remember Abuse came up with some obscure class in the JRE that started with a lowercase character, that did something really bad, and I limited the max source length.

So… not really a useful strategy to you, unfortunately.

FYI: scriptRunner.interrupt(); does nothing. you really must use stop() which will not properly release mutexes.

http://www.java-gaming.org/index.php/topic,21136.msg172468.html#msg172468

In regards to security: did you try to use the SecureClassLoader in Java when loading and executing classes? I think it would limit access to system resources for the class???

In regards to Pnuts: it looks cool, but how is it possible to pass in values/objects?

Well, if you look at the classes in java.io.* and java.net.*, you’ll see that those permissions are only checked if there is a SecurityManager installed. That SecurityManager will interfere with your game too. Hence the idea of launching an external (sandboxed) JVM.

maybe a stupid idea…dono… cant you use a custom class loader (force it to the user script) ? and only allow safe classes by checking if it is in your safe classes a list ? dont know hw it is possible but for example your script could only allow getSafeClass(“Name”) and prevent any Blabla.something() or new Something(), just a search and replace in the user source.

EDIT:

something like :

“Something.” => not enabled if Something not in safe list
"new "=> not enabled
getClass().newInstance() => replaced at compil time by getSafeClassInstance();

I dug deep and found the AccessController:
http://java.sun.com/j2se/1.5.0/docs/api/java/security/AccessController.html

Here is how it would look:

   somemethod() {
         AccessController.doPrivileged(new PrivilegedAction() {
              public Object run() {
                 // Code goes here. Any permission checks within this
                 // run method will require that the intersection of the
                 // callers protection domain and the snapshot's
                 // context have the desired permission.
              }
         }, context);
         ...normal code here...
   }

context is AccessControlContext with the access permissions.

I think that should do it? The context would be very limited to prevent access to file system etc. Or is my assumption wrong?

What i tested some days ago was to use Janino to dynamically compile scripts and run them with a SecurityManager that only permits using your game classes. you can’t hide classes but you can prevent moders from using them and with Janino you can tell it to compile only class bodies and prevent the use of the import instruction.

You can’t do that with a SecurityManager.

[quote=“zingbat,post:11,topic:34405”]
You can use fully qualified class names.


Package pkg = new Package();
pkg.set("someVariable".intern(), "any java object");
pkg.setConstant("someConstant".intern(), "any java object");
Context context = new Context(pkg);
Pnuts.load(scriptStreamUrlReaderOrString, context);

You can control whether scripts are interpreted, compiled, cached, etc with context.setImplementation:
http://pnuts.org/apidoc/pnuts/lang/PnutsImpl.html
Also, context.setVerbose is useful, and I like to call context.setWriter(new PrintWriter(System.out, true)); (the default PrintWriter doesn’t autoflush for whatever reason).

You can also pass in Pnuts functions in addition to objects:

pkg.setConstant("moo".intern(), new PnutsFunction () {
	public boolean defined (int nargs) {
		return nargs == 1;
	}

	protected Object exec (Object[] args, Context context) {
		if (!defined(args.length)) undefined(args, context);
		String someString = (String)args[0];
		// Do stuff and return stuff.
		return null;
	}

	public String toString () {
		return "function moo(String)";
	}
});

Another way to provide functionality to your scripts is the Pnuts module system. It is powerful and easy to use.
http://pnuts.org/1.2.1/snapshot/20070724/doc/lang.html#modules

Here is how you’d use a modified classloader with Pnuts, as kingaschi mentioned:


context.setClassLoader(new ClassLoader() {
	protected synchronized Class<? > loadClass (String name, boolean resolve) throws ClassNotFoundException {
		if (name.startsWith("com.example.your.game.")) {
			Class c = getParent().loadClass(name);
			if (resolve) resolveClass(c);
			return c;
		}
		throw new ClassNotFoundException("Unable to load class: " + name);
	}
});

That seems a pretty secure way to prevent malicious classes from being loaded, but I would definitely hesitate to claim it is bulletproof without a lot more thought.

From your point in your initial post:
- Required: prevent malicious code (e.g. prevent access to file system etc.)

So I assume the code will be running on the server, or in remote clients.

Where ever your potentially malicious code is running, would you really think the best solution is to ‘plug all security holes you find’ ? You’ll be surprised how much effort people put into trying to find a vulnerability. In the end it is your game, and it is your responsibility that nobody gets they files corrupted or their creditcard number stolen.

My advice: play it safe and launch 1 sandboxed JVM for each AI player. In that sandboxed JVM you can kill threads each turn (if needed), for each player.

Package pkg = new Package();
pkg.set("someVariable".intern(), "any java object");
pkg.setConstant("someConstant".intern(), "any java object");
Context context = new Context(pkg);
Pnuts.load(scriptStreamUrlReaderOrString, context);

Thanks, that looks like this could work?

Let me see if I understand it correctly?


// 1. retrieve the game object to manipulate
Game game = ...

// 2. obtain Pnuts script (throws ParseException for syntax errors)
Pnuts script = Pnuts.parse(sourceCode);

// 3. execute script on game object
Package pkg = new Package();
pkg.set("game".intern(), game);
Context context = new Context(pkg);
script.run(context);

Question: script.run can manipulate the game object I passed in??? I.e. after the script is done, the game object has changed?

[quote=“kingaschi,post:1,topic:34405”]
I did a serious 3 month project on dynamic scripting (ie an agent’s script changes depending on its circumstances) and I found that home-brew was best. No compiler security issues, easier to read script (because it’s dedicated to the context), and no WTFs from rhino, lua, pnuts &c…

If your game object has methods that can be called to change the internal state of the object, then yes, the script could have modified it. Either don’t provide those methods or make a copy. It is possible to intercept method invocations and other things using a Configuration and context.setConfiguration. This might be used in addition to the classloader.

For simpler scripting there is fscript:
http://fscript.sourceforge.net/

Riven makes a good point. How are you going to be certain you are really secure? If mostly secure is good enough, the classloader is probably fine.

[quote]If your game object has methods that can be called to change the internal state of the object, then yes, the script could have modified it.
[/quote]
Yes, that’s what I actually want! Seems like Pnuts is the way to go. So, my assumptions from above how to create and run a script are basically correct?

I also checked FScript which looks nice but is probably too limited? Then rolling my own would take lots of time to implement. My goal isn’t to create a scripting language but to add one to my game. Using an existing solution is probably the most feasible solution.

Once thing I noted about Pnuts is, it doesn’t have any updates since 2007? Is there an official forum for it too?

Yeah, the code you outlined should do what you expect.

Honestly, you could use any JVM scripting language (Rhino, JRuby, Jython, Groovy, blah). I like Pnuts a lot though! It has nice syntax that doesn’t stray too far from Java (compared to the ridiculousness in Groovy). I also like that the Pnuts type system is the Java type system, many languages converted to run on the JVM come with their own type system.

Development on Pnuts started in 1997. It is stable and actively maintained. The java.net Pnuts mailing list is the best way to ask questions or discuss bugs. Any bugs found are usually fixed with 24 hours. You might check out this page:
http://pnuts.org/articles/pnutsHighlights.html

[quote=“kingaschi,post:1,topic:34405”]
hi! I don’t know if someone already told you, but if JavaScript is ok for your purposes you can use Java Scripting technologies: http://java.sun.com/javase/6/docs/technotes/guides/scripting/index.html

I’m making a game that uses it.

code example:


String scriptCode = ... ; // contains JavaScript code
ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");
scriptEngine.put("out", System.out);
scriptEngine.eval(scriptCode)

script example:


out.println("Hello, world!");

very easy :slight_smile: