Java-Gaming.org Hi !
Featured games (91)
games approved by the League of Dukes
Games in Showcase (762)
Games in Android Showcase (229)
games submitted by our members
Games in WIP (846)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Using ClassLoader to make your game moddable  (Read 1492 times)
0 Members and 1 Guest are viewing this topic.
Offline SkyAphid
« Posted 2018-06-21 14:08:20 »

Have any of you tried this before? I'm wanting to toy around with using ClassLoader to let players load in custom dialogue trees for our game. I'm aware that you could use XML and similar solutions to do it, but I'm just experimenting with all avenues to see which one I think works the best for us specifically.

Anyway, so what I'm looking to do is actually pass in classes from the game (the textbox manager) and let the player call commands on it to make nested dialogue trees.

I.E.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
textBoxRenderer.newTextBox(skyler, "Hello [[PLAYER]]!");

textBoxRenderer.newTextBoxOptionDeck(new TextBoxOptionDeck(
   "Mornin’",
   "Good morning beautiful.",
   "Oh... It’s you."
   ){
                       
   snip
});


I'm interested to hear anyone's stories on if they've tried this and how it went for them. One question I also have in particular is if you can actually limit what packages the class that's loaded in is allowed to reference.

it just werks
Offline CommanderKeith
« Reply #1 - Posted 2018-06-21 14:49:19 »

Hi SkyAphid,
I'm not an expert at classloaders, but I recieved some very interesting advice about trying to secure client submitted code in this thread where @Riven, @Roquen, @nsigma and @Abuse gave some very interesting pointers:

http://www.java-gaming.org/topics/java-s-built-in-scripting-engine/31183/msg/289552/view.html#msg289552

My skills were not up to the challenge to implement the AST or byte code injection solutions to try to secure arbitrary code, if that is even possible,. But I always thought that solving this problem would be a very interesting challenge with wider appeal than just games.
Many other people's solutions to this problem appears to be to give the user some sort of hobbled, limited or otherwise sub optimal language or runtime such as JavaScript that can't do much damage rather than Java or whatever the original language is.

Online princec

« JGO Spiffy Duke »


Medals: 1038
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #2 - Posted 2018-06-21 15:43:19 »

I did try it in Revenge of the Titans, though I never documented it nor explained it to anyone Smiley
Source code's kicking about somewhere...

Cas Smiley

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Damocles
« Reply #3 - Posted 2018-06-21 15:45:59 »

Another way how to offer complex modding without exposing your code, or loading in potentially unsafe code:

You could create a Server Socket, that takes request from an external client, and provides data about your game.

That could be realized with an available or proprietary protocol.

It a bit complicated to create the protocol and hook it up to the game logic, but the advantage is that you only need to expose the protocol to others, and have the game set up a Server Socket.
The modders can create a client in any language they want, and handle the internals themselves.
The agreement is just the protocol.

An example would be a client that can trigger events if the player is at certain positions.
The server provides the players position via a request (defined in the protocol), and the mod-client sends some event to the game (that changes some state in the game)

This approach is not locking in the modders into a specific language or environment. They can go as crazy complex with it as they want to.
(You could still provide a simple Java-Client example as starting point)
Offline SkyAphid
« Reply #4 - Posted 2018-06-21 21:31:16 »

I spent the whole day going down a rabbit-hole of reading about this stuff, and have concluded it's probably best to just add support for lua and only whitelist certain functionality lol. But thank you guys for all the help!

it just werks
Offline nsigma
« Reply #5 - Posted 2018-06-21 23:44:53 »

Interesting thread!  And interesting to read the thread @CommanderKeith linked to and remember what I was working on at the time!  Smiley  Since then, PraxisCORE (the runtime from PraxisLIVE) has become an actor-model system where every actor has its own ClassLoader and every actor accepts it's behaviour as a String of Java code that can be defined and redefined as it's running.  It's fun, and can be really powerful for certain uses, and is almost certainly not suitable for untrusted code in any form whatsoever!  Wink

But what's the point of securing your code anyway?  If it's just for users to mod things for themselves, what's the problem with just providing an API that they can do anything with?  On the other hand, if your aim is that people can share mods and you feel you need to lock things down for the safety of other users then I'd suggest being careful of any executable code, whatever the language.  Interesting that today I also read about the plan to deprecate serialization in the JDK because of its potential as an attack vector for arbitrary code execution.  If this lockdown is important to you, perhaps a simple DSL (domain specific language), not Turing complete, as declarative as possible, is the way to go?

Praxis LIVE - hybrid visual IDE for (live) creative coding
Offline SkyAphid
« Reply #6 - Posted 2018-06-22 00:41:26 »

I played with LuaJ a bit today and found out it has some very powerful tools for calling Java functions straight from the lua. I started out with a lua file that's something simple like this:

1  
2  
3  
function Main(GameTools, WorldTools, HUDTools, PlayerTools)
  print('Hello World from lua file! Player Name: ' .. PlayerTools:getName())
end


And it successfully printed:

1  
Hello World from lua file! Player Name: Cody


I wanted to see how far down I could take the rabbit hole and ended up trying this:

1  
2  
3  
function Main(GameTools, WorldTools, HUDTools, PlayerTools)
  print('Hello World from lua file! First Item in Player Inventory: ' .. PlayerTools:getPlayer():getInventory():get(0):getName())
end


And holy hell, it printed out this:

1  
Hello World from lua file! First Item in Player Inventory: Old Hover-Bike


It went all the way down into the EntityPlayer, down it his Inventory, and then got an Item out if its ArrayList and was able to successfully call getName(). That's pretty much exactly what I was looking for in my mod support functionality. Of course, I'll probably abstract and limit what the lua file can call so that the modders can't go too far down the rabbit hole and accidentally break something, but it's still cool that it has that potential.

I'll have to keep reading up to make sure the lua support can't be used maliciously though. I haven't used lua since my teenage source modding days, so I can't really remember what kind of functionality it has.

@nsigma
I'm not really a stickler on protecting my code, but it's definitely about protecting the users from douchebags who will hide viruses in their mods. The Java VM is pretty powerful so I imagine giving full control over to the modder could end up being pretty dangerous.

it just werks
Offline nsigma
« Reply #7 - Posted 2018-06-22 08:22:54 »

I'll have to keep reading up to make sure the lua support can't be used maliciously though. I haven't used lua since my teenage source modding days, so I can't really remember what kind of functionality it has.

@nsigma
I'm not really a stickler on protecting my code, but it's definitely about protecting the users from douchebags who will hide viruses in their mods. The Java VM is pretty powerful so I imagine giving full control over to the modder could end up being pretty dangerous.

Yes, I'm interested to know how this fixes your "problem" rather than just providing a different language for people to write their exploits in?!

Obviously it's in text not bytecode, but that's the same as the Java options talked about earlier and really relies on the user understanding or trusting the code source.  You're likely to still need to look into ClassLoader white-listing and/or SecurityManagers with this?  Unless LuaJ adds this already on top of the normal Java scripting support?

Incidentally, another Java option that might be interesting to look at embedding in a game engine might be JShell?

Praxis LIVE - hybrid visual IDE for (live) creative coding
Online princec

« JGO Spiffy Duke »


Medals: 1038
Projects: 3
Exp: 20 years


Eh? Who? What? ... Me?


« Reply #8 - Posted 2018-06-22 08:53:15 »

Wouldn't Javascript also be a good option for allowing user scripty stuff?

Cas Smiley

Offline nsigma
« Reply #9 - Posted 2018-06-22 09:01:52 »

Wouldn't Javascript also be a good option for allowing user scripty stuff?

Seems the obvious solution with Nashorn there, assuming you're not building a "turtles all the way down" system like myself - ie. where you're running performance stuff in the main loop.

It still doesn't solve the security problem though.  And these days it's possible to write a Java API that's more readable and less verbose than a JavaScript one - now that's a refreshing change!  Grin

Praxis LIVE - hybrid visual IDE for (live) creative coding
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline h.pernpeintner

JGO Knight


Medals: 90



« Reply #10 - Posted 2018-06-22 12:09:22 »

Have any of you tried this before? [...] I'm interested to hear anyone's stories on if they've tried this and how it went for them. One question I also have in particular is if you can actually limit what packages the class that's loaded in is allowed to reference.

My engine has a Component that can load JavaScript or Java source code. JavaScript is plugged into Nashorn (this is very simple and I think a viable scripting solution for most of the use cases out there), Java is compiled at runtime and loaded with the given classloader...just like every other sane module system does it. For the Java components I implement, I compile against a small API that can be used on provided scope in maven (component interface has init(EngineContext context), update(float deltaSeconds) functions and so on). You can limit the objects that the foreign component can use right there, by the interface definitions you provide. You still have the problem that one can execute arbitrary Java code in his component. Having different classloaders is optional here and has to be used when you want to isolate multiple extensions (external modules) from each other.

Another approach would be to go modular with the JPMS...with this you could export modules only to your own modules, but you can't prevent arbitrary code execution. All in all, I don't think it's worth the hassle at all.
Offline nsigma
« Reply #11 - Posted 2018-06-22 12:43:15 »

You can limit the objects that the foreign component can use right there, by the interface definitions you provide. You still have the problem that one can execute arbitrary Java code in his component.

And can reflect on and access private fields of anything you do pass in unless you've locked that down?

Having different classloaders is optional here and has to be used when you want to isolate multiple extensions (external modules) from each other.

Besides the security aspects of using classloaders, it also depends if you're allowing runtime code updates.  Throwaway classloaders are then useful because they get GC'd when the old class goes out of scope.

Praxis LIVE - hybrid visual IDE for (live) creative coding
Offline Abuse

JGO Ninja


Medals: 68


falling into the abyss of reality


« Reply #12 - Posted 2018-06-22 13:31:19 »

Building secure sandboxes:

1) is (very) hard
2) will almost certainly have holes in it.
3) takes significant effort
4) Increases complexity (both for you, and potential modders)
5) limits what modders can achieve
6) can be trivially bypassed by simple out-of-app instructions that most users will blindly follow (e.g. "Mod installation steps: 1) Run this executable installer")

A better approach is IMO to allow mods to do anything they wish, and simply warn the user of the risks within your game's UI.
If you cannot make a sandbox absolutely secure (you can't), the illusion of security becomes more dangerous than an absence of security.
Offline h.pernpeintner

JGO Knight


Medals: 90



« Reply #13 - Posted 2018-06-22 13:43:20 »

And can reflect on and access private fields of anything you do pass in unless you've locked that down?

Of course, like I said, arbitrary code execution is something that is not really possible to prevent, when you give the user the chance to plug in his own Java code.

But if you provide an interface, for example let your user implement a component like
1  
2  
3  
interface DialogExtension {
  List<DialogOptions> getDialogOptions()
}


and your user can only use your interfaces to compile against, then the attack vector is very small, isn't it? For example he can not access your engine instance. Or the dialog manager. Is this worth something?
Offline SkyAphid
« Reply #14 - Posted 2018-06-22 14:24:13 »

Building secure sandboxes:

1) is (very) hard
2) will almost certainly have holes in it.
3) takes significant effort
4) Increases complexity (both for you, and potential modders)
5) limits what modders can achieve
6) can be trivially bypassed by simple out-of-app instructions that most users will blindly follow (e.g. "Mod installation steps: 1) Run this executable installer")

A better approach is IMO to allow mods to do anything they wish, and simply warn the user of the risks within your game's UI.

reads post
looks at profile name

Checks out. Lol.

Jokes aside, he has a point. I'm probably just going to leave this lua thing as is unless I find a huge gaping problem. I didn't expect it to work this well though, it just kind of works out of the box, and I figured I'd have to do a lot of abstracting to get it to work. The parser is literally like 40 lines.

After reading about the ClassLoader, I really don't think it'd be good for mods though. You have to still compile the project into a jar and go through all of that, but it's hard to give the player the dependencies they need to do it. You'd pretty much have to split your project into two projects, where one has the usable API by plugins and then the other is just your normal game (so that the plugins can't sneak into the main game functionality and break something). Then again some people would argue you'd want to actually give them that functionality. I'm kinda on the ehhh of giving them unrestricted control rather than tightly controlled getters and setters.

It just seems like ClassLoader would be more trouble than its worth in a game scenario. I can see its applications in more serious software though, of course. You probably want something more lightweight for games that's easy to pick up. Just my two cents.

it just werks
Offline Abuse

JGO Ninja


Medals: 68


falling into the abyss of reality


« Reply #15 - Posted 2018-06-22 14:38:23 »

And can reflect on and access private fields of anything you do pass in unless you've locked that down?

Of course, like I said, arbitrary code execution is something that is not really possible to prevent, when you give the user the chance to plug in his own Java code.

But if you provide an interface, for example let your user implement a component like
1  
2  
3  
interface DialogExtension {
  List<DialogOptions> getDialogOptions()
}


and your user can only use your interfaces to compile against, then the attack vector is very small, isn't it? For example he can not access your engine instance. Or the dialog manager. Is this worth something?

The user doesn't just have your code to compile against, they have the entire Java API too.
Not just the public APIs, but all the private stuff too.
Through these classes there's a whole host of ways to interact with a running VM, (Reflection, Unsafe, runtime attachment of a javaagent, etc etc), the file system, and the OS.

You can block it with a restrictive parent class loader, or security manager, but unless you're extremely thorough, there will be holes.
Offline Longor1996

JGO Wizard


Medals: 115
Projects: 2
Exp: 8 years


The cake is probably a lie.


« Reply #16 - Posted 2018-06-22 14:46:18 »

How about implementing a LISPy-dialect specific to your game/engine on top of a ASM library? Doesn't work properly on Android/iOS though...

Offline nsigma
« Reply #17 - Posted 2018-06-22 15:00:42 »

After reading about the ClassLoader, I really don't think it'd be good for mods though. You have to still compile the project into a jar and go through all of that,

Huh, what?!  Someone really should have told me that sooner!  Grin

I used to use Janino for this - https://janino-compiler.github.io/janino/ Great, incredibly easy to embed, but no lambda support  Emo  It also has code to use the Compiler interface, but that means you have to include javac.  No compile to jar with Janino at all though - doesn't even go to a file - straight from String to Java bytecode at runtime.

I'm using some seriously forked code from Janino still in PraxisCORE.

Praxis LIVE - hybrid visual IDE for (live) creative coding
Offline DesertCoockie
« Reply #18 - Posted 2018-07-05 21:38:02 »

I don't know if I'm a little bit late to the party and if that's actually what you want, but I've used this to just dynamically load and initialize objects from jars:
1  
2  
3  
4  
URLClassLoader classLoader = URLClassLoader.newInstance( new URL[] { fileInJar.toURI().toURL() } );
Class<?> clazz = classLoader.loadClass( className );
Constructor<?> constructor = clazz.getConstructor();
Mod createdInstance = (Mod)constructor.newInstance();
Offline SkyAphid
« Reply #19 - Posted 2018-07-09 18:37:44 »

Just to kind of report back in with my usage of Lua to mod Java games, not only can you access Java from Lua, but I've also found a way to actually restrict the usable packages to specific ones.

I recommend checking out LuaJ if you haven't already:
http://www.luaj.org/luaj/3.0/README.html

To make a whitelist of packages in LuaJ, simply extend LuajavaLib and override this function:

1  
2  
3  
4  
5  
   protected Class<?> classForName(String name) throws ClassNotFoundException {
      Class<?> clazz = Class.forName(name, true, ClassLoader.getSystemClassLoader());

      return (isInWhitelist(clazz) ? clazz : null);
   }

it just werks
Pages: [1]
  ignore  |  Print  
 
 

 
EgonOlsen (361 views)
2018-06-10 19:43:48

EgonOlsen (363 views)
2018-06-10 19:43:44

EgonOlsen (300 views)
2018-06-10 19:43:20

DesertCoockie (535 views)
2018-05-13 18:23:11

nelsongames (866 views)
2018-04-24 18:15:36

nelsongames (847 views)
2018-04-24 18:14:32

ivj94 (1295 views)
2018-03-24 14:47:39

ivj94 (418 views)
2018-03-24 14:46:31

ivj94 (1080 views)
2018-03-24 14:43:53

Solater (430 views)
2018-03-17 05:04:08
Java Gaming Resources
by philfrei
2017-12-05 19:38:37

Java Gaming Resources
by philfrei
2017-12-05 19:37:39

Java Gaming Resources
by philfrei
2017-12-05 19:36:10

Java Gaming Resources
by philfrei
2017-12-05 19:33:10

List of Learning Resources
by elect
2017-03-13 14:05:44

List of Learning Resources
by elect
2017-03-13 14:04:45

SF/X Libraries
by philfrei
2017-03-02 08:45:19

SF/X Libraries
by philfrei
2017-03-02 08:44:05
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!