Search Unity

[Released] Dynamic C# - Runtime C# Scripting

Discussion in 'Assets and Asset Store' started by scottyboy805, Feb 8, 2017.

  1. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Dynamic C# allows loading of assemblies and scripts at runtime making it trivial to add modding support to you game by allowing your users to write C# code. In addition, Dynamic C# also allows strict security restrictions to be enforced as specified by the developer meaning that external code can run safely.

    Features

    • Compile and run C# scripts at runtime.
    • Fast Execution - once compiled, external scripts run as fast as game scripts
    • Allows for modding support to be easily added
    • Pre-load security checks mean that unsafe code can be identified and discarded
    • Support for loading assemblies and C# scripts
    • All scripts use custom namespaces to prevent clashing type names
    • Support for non-concrete communication using script proxies
    • Simple and clean API for accessing types and proxies
    • Cached member tables for quick reflection
    • Automatic construction of types using the correct method (AddComponent, CreateInstance, new)
    • Comprehensive .chm documentation of the API for quick and easy reference
    • Fully commented C# source code included
    Limitations
    • Cannot run under '.Net 2.0 Subset'. (Requires '.Net 2.0)
    • AOT platforms such as IOS are not supported
    • Scripts must be compiled before they can be executed
    PC, Mac and Linux platforms are supported.

    September, 2017: We have now added the Script Evaluator to Dynamic C#. Take a look here for more info!


    Need a code editor for your game?

    FeatureTrimmed.png
    Check out our other asset asset 'InGame Code Editor' which is an advanced text input field with line numbering and fully customizable syntax highlighting for C# and many other languages. Check out the asset store or forum page for more info.

     

    Attached Files:

    Last edited: May 7, 2019
  2. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    The review process took longer than expected but I am now pleased to announce that Dynamic C# is available in the asset store.
     
    N00MKRAD, one_one and JBR-games like this.
  3. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Version 1.0.3 has been submitted to the asset store for review and should be available in a few days.
    • Added some extra example scripts
    • Fixed a bug in the 'ScriptProxyExample' script
    • Fixed a bug when creating instances of non-MonoBehaviour types where the constructor could not be found (MissingMethodException)
    • Fixed errors on import when webplayer is set as the current build target.
     
  4. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Version 1.0.3 is now available in the asset store.
     
  5. ZeoGames

    ZeoGames

    Joined:
    Feb 3, 2016
    Posts:
    2
    Hello,
    Does this support WebGL?
    Thanks. :)
     
  6. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Hi,
    Sorry for the delay.

    I did a bit of testing and I was able to build for web gl, however I have not yet been able to compile a script at runtime. The compiler always throws an argument exception about an invalid path even when compiling a script from memory. For now the answer is no but I want to look into this further to see if I can get it working.
     
  7. ZeoGames

    ZeoGames

    Joined:
    Feb 3, 2016
    Posts:
    2
    Thank you scotty,
    I will look forward to purchasing thing this once it works for WebGL.
    Compiling script from memory sounds like the best solution.
     
  8. BenLighthouseReeves

    BenLighthouseReeves

    Joined:
    Apr 11, 2017
    Posts:
    5
    This project looks very interesting. Does it work with Windows Store apps?

    One problem I found with assetbundles is that they do not support loading of scripts at runtime in Windows Store apps. I'm hoping that Dynamic C# can do that.
     
  9. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Unfortunately Window Store cannot be supported because Dynamic C# relies on a few types from 'System.Reflection' and 'System.CodeDom' which are not available on the platform.
     
  10. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    @ZeoGames

    After some more testing it looks like we are not going to be able to support WebGL. The exception I mentioned in a previous post was caused by the compiler attempting to access the local file system and this is not allowed in WebGL. At the minute we cannot see any way around this because the compiler requires file system access even when compiling from source files that are already in memory.

    Hopefully you can find an alternative solution to achieve your goal.
     
  11. BenLighthouseReeves

    BenLighthouseReeves

    Joined:
    Apr 11, 2017
    Posts:
    5
    Thanks for the quick response @scottyboy805 . Would this also be the case for a UWP application? I'm a little confused about the differences between Windows Store apps and UWP.
     
  12. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Yes, same problem for UWP. As far as I am aware, all of the windows app/phone platforms use the same reduced framework so we cant support any of those platforms.
     
  13. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Whitebrim likes this.
  14. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Version 1.0.4 has been submitted to the asset store.

    This version fixes an error in 5.6.0 where an ambiguous reference prevents the Dynamic C# source code from compiling. If anyone else is getting this error you can email your invoice number to info@trivialinteractive.co.uk and we will send you the updated package direct so you don't have to wait for it to be approved by the asset store team.
     
  15. heroes1

    heroes1

    Joined:
    Nov 16, 2015
    Posts:
    17
    Hi scottyboy805,

    I'm interested in this asset (Dynamic C#), however before buying this asset, i would like to test out the Tank game.
    Possible to build it into .exe and share?

    Thanks.
     
  16. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    I have built a Win32 version of the demo scene for you to try out which you can download from here :)
     
    N00MKRAD likes this.
  17. heroes1

    heroes1

    Joined:
    Nov 16, 2015
    Posts:
    17
    Thanks for sharing. :)
     
  18. heroes1

    heroes1

    Joined:
    Nov 16, 2015
    Posts:
    17
    Hi scottyboy805,

    Dynamic C# does looks great for develop education C# coding games. I have a question, will you be planning on making IDE for more user friendly for using Dynamic C#. I mean in future. :) I have tried to find is there any asset that will change unity input field into IDE, but no luck.

    This is what i found for web uses which is good for IDE however the Dynamic does not support WebGL https://ace.c9.io/#nav=about . Do you have any idea where i can find a useful asset that can do IDE, otherwise i need to make it myself.
     
  19. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    We don't plan on making a UI IDE for Dynamic C# as it would require a lot of work to create a stable and user friendly interface and is not the main focus of Dynamic C#. I also couldn't find any code editor controls on the asset store but as an alternative perhaps you could use the web IDE you linked inside of Unity via a web browser renderer, something like this maybe that supports java script? I know its a paid asset and quite pricey but maybe there are other similar assets. The only other alternative I can see is to create your own text editor as you mentioned which will certainly be a lot of work.
     
  20. heroes1

    heroes1

    Joined:
    Nov 16, 2015
    Posts:
    17
    Hi scottyboy805,

    I have purchased this asset. So far so good at the moment. However, i would how to stop script if the user suddenly run an infinity loop? at the moment it will just crash and stop working.

    Thanks
     
  21. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Unfortunately there is no easy way to stop already executing code. Mono provides no way of stopping executing code and in fact the problem exists with Unity also (Infinite loops in game will freeze the editor).

    There is this asset on the asset store which is able to break out of any infinite loop which has good reviews. I am not entirely sure how it works but I think it maybe something to do with native exceptions or assertions which causes the runtime to jump out of the executing code. Perhaps you can find more information about the asset and how it works and maybe implement a similar system. Another problem will be actually detecting infinite loops in code which I imaging would also be quite tricky.
     
  22. heroes1

    heroes1

    Joined:
    Nov 16, 2015
    Posts:
    17
    Hi scottyboy,

    I manage to stop infinity loop by using this
    Code (CSharp):
    1. Thread thread = new Thread( TankMain );
    2.             thread.Start();
    3.             Boolean success = thread.Join( 1000 );
    4.             if (!success) {
    5.                 thread.Abort ();
    6.                 Debug.LogWarning ("Runtime error");
    7.             }
     
  23. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    You should be very careful using this approach. The Unity API is not thread safe and will cause problems if the external code uses it. If you are running non-unity code however then it should be fine.
     
    one_one likes this.
  24. Eitz

    Eitz

    Joined:
    Jun 30, 2017
    Posts:
    1
    Hello.

    Are Coroutines and Enums supported in you asset?
     
  25. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    There is a bug in the MCS compiler that Dynamic C# uses which causes the compiler to fail if an enum is defined. Unfortunately this means that you cannot define an enum in scripts that you want to compile but you can still use enums that are defined in a shared interface assembly. Take a look at the documentation for information about interface assemblies.

    Co-routines on the other hand are partially supported. You can define and call a co-routine as normal provided that you call the method in the external code using 'StartCoroutine'. The part that is not currently supported is calling a co-routine from game code. Dynamic C# will always call the method normally meaning that it executes immediately and yielding does not wait. This does sound like a useful feature though so we will be sure to add support for calling co-routines from game code in an update.

    I hope this answers your question. Let me know if anything is unclear.
     
    Eitz likes this.
  26. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Version 1.0.6 has been submitted to the asset store.

    This version adds correct calling support for co-routine methods using the 'ScriptProxy' API ('Call' and 'SafeCall' methods). By default, any non-static method defined on a monobehaviour script that returns 'IEnumerator' will now be treated as a co-routine and will be called using 'StartCoroutine'. We have also added better support for calling static methods. There are a number of new methods such as 'CallStatic' and 'SafeCallStatic' which can be used to invoke static methods via a 'ScriptType' reference.
     
    Eitz likes this.
  27. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Version 1.0.7 has been submitted to the asset store.

    You will now be able to create instances of non-unity types without needing a default constructor. Dynamic C# will attempt to use the default constructor when creating an instance but if one is not found then it will fall back to creating an uninitialized instance with all fields set to their default values. If you create an instance by passing arguments then Dynamic C# will still fail if a matching constructor is not found.
     
  28. DoubleIsLoveDoubleIsLife

    DoubleIsLoveDoubleIsLife

    Joined:
    Nov 29, 2014
    Posts:
    29
    So I bought your asset and it's great, but I have one issue. I can't seem to reference anything in the UnityEngine namespace? For example, Debug.Log throws an exception: "The name `Debug' does not exist in the current context in <Unknown> at [5, 2]". I have the script set to use the UnityEngine namespace. If I call UnityEngine.Debug.Log it still throws an error about Debug not being a recognised type.
     
    Last edited: Sep 13, 2017
  29. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Hi,
    Thanks for purchasing our asset and I am glad you like it :)

    Have you added an assembly reference to UnityEngine.dll? You can do this in Dynamic C# settings which can be found at 'Tools -> Dynamic C# -> Settings'. Under the 'Assembly References' heading, make sure that 'UnityEngine.dll' is present. If it is not then simply add it using the '+' plus button. Note that the '.dll' extension is important.. Also I assume you have added 'using UnityEngine;' to the top of your script from your description.
     
  30. DoubleIsLoveDoubleIsLife

    DoubleIsLoveDoubleIsLife

    Joined:
    Nov 29, 2014
    Posts:
    29
    Yes and yes.
    I can run code referencing my actual game code fine, it just doesn't compile if I use anything from UnityEngine.
     

    Attached Files:

    Last edited: Sep 13, 2017
  31. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    OK, Thanks for checking.

    In that case would you be able to send the code you are trying to compile to info@trivialinteractive.co.uk so we can do some testing. Also could you try the tank demo that is included with the package and let me know if that works for you since it also uses the UnityEngine assembly.
     
  32. DoubleIsLoveDoubleIsLife

    DoubleIsLoveDoubleIsLife

    Joined:
    Nov 29, 2014
    Posts:
    29
    The tank example works without issues, but the LoadScriptExample script causes the same exceptions where it can't recognise Debug. Just tried on a blank project. Maybe my Unity version is causing it for some reason? 2017.2.0b6?
     
  33. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    The version should not affect anything unless they have modified the Debug class between versions which I highly doubt. I am downloading that version now to try. Looks like a beta version though?
     
  34. DoubleIsLoveDoubleIsLife

    DoubleIsLoveDoubleIsLife

    Joined:
    Nov 29, 2014
    Posts:
    29
    Yep, it's beta 6 of 2017.2. I've had really bizarre issues with Unity beta builds before so I wouldn't be surprised if it somehow was the reason for the errors. Other things in the UnityEngine namespace aren't working either like Color or Vector3 and stuff.
     
  35. N00MKRAD

    N00MKRAD

    Joined:
    Dec 31, 2013
    Posts:
    202
    What are the benefits compared to an eval() based script loader? (Got that in my game rn)

    Can it run functions like Update()?
     
  36. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    I will give it a try and see what I find. I just tested the LoadScriptExample and it did not compile for me either but I got the errors in the image relating to a missing semi colon. After I added a semi colon to line 20 inside the source code quote string it compiled fine for me.
     

    Attached Files:

  37. DoubleIsLoveDoubleIsLife

    DoubleIsLoveDoubleIsLife

    Joined:
    Nov 29, 2014
    Posts:
    29
    Yes I added the semicolon but got the debug log errors. I was going to send you a project file but gmail blocked it for some reason, I'll try reinstalling Unity.
     
  38. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    You get all the benefits of fully compiled C# code because the code is actually compiled before it is loaded. It uses a modified version of the mcs compiler which does not rely on the entire mono distribution being installed. Dynamic C# then adds an extra layer which makes it easy to load and run MonoBehaviour scripts will all the event methods including Update. There is also support for scriptable objects and non-Unity types.
     
  39. N00MKRAD

    N00MKRAD

    Joined:
    Dec 31, 2013
    Posts:
    202
    Thanks, I'll think about picking it up, I currently have an eval() based script loader and while it works with assembly references and other basic stuff, it can't run Update() or loops which makes Hotkeys and other things impossible.

    With your asset it should be possible to make a hotkey to toggle fog, for example, right?
     
  40. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Ok, I have downloaded the version you are using and it looks like there have been some significant changes to the assemblies for the engine. It looks like most of the scripts that were located within the UnityEngine assembly have been split out into modules and as a result the 'Debug' class is no longer located inside 'UnityEngine.dll'. The assembly you will want to use is called 'UnityEngine.CoreModule.dll'. Once I added that reference to the settings the 'LoadScriptExample' worked as expected. There are quite a few other modules also which you may need to add later down the line. Take a look in the following folder to find the new module assembles "<unity_install_path>/Editor/Data/Managed/UnityEditor/". Note that the old 'UnityEngine.dll' still exists in the parent folder but it looks like the editor is not using it and they are planning to switch to this modular approach.
     
  41. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    That depends how you would want it to work. If you wanted to press a button and then run a C# script to toggle the flashlight then this would be possible or did you have something else in mind?
     
  42. DoubleIsLoveDoubleIsLife

    DoubleIsLoveDoubleIsLife

    Joined:
    Nov 29, 2014
    Posts:
    29
    Mystery solved! Thanks.
     
  43. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    No problem! let me know if you have any other issues :)
     
  44. N00MKRAD

    N00MKRAD

    Joined:
    Dec 31, 2013
    Posts:
    202
    What I had in mind was a solution in a single script.

    Like this:

    void Update () {
    if(Input.GetKeyDown(KeyCode.F)){
    //toggle stuff​
    }​
    }

    ...would that work as an external script?

    Also, is it possible to execute code from an InputField? I did that with eval() since it simply works with a string, is that also the case with your asset?
     
  45. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Yes absolutely, that code would work fine. The entire C# language is supported and provided you add a reference to UnityEngine (Added by default) then you can use the entire Unity API from your C# code.

    To run code from an input field is very easy. Dynamic C# includes a method called 'CompileAndLoadScriptSource' which you can provide a string containing C# source code. This will give you a 'ScriptType' object as the result which you can use to call static methods, create instances, and much more. There is also a tank demo included where you write C# source code to control the movement of a tank around a level. This demo uses an input field to get the source code that the user writes so if you do decide to buy the asset then definitely take a look at that.

    I should also mention that all code needs to be inside a class or struct method as per C# specification.
     
    Last edited: Sep 13, 2017
    N00MKRAD likes this.
  46. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Dynamic C# 1.0.8 has been submitted to the asset store for review. This update introduces a new feature called the Script Evaluator which contains an 'Eval' method which can execute C# code outside of the context of a method body.

    Here is a basic code example:

    Code (CSharp):
    1. using UnityEngine;
    2. using DynamicCSharp;
    3.  
    4. class ExampleClass : MonoBehaviour
    5. {
    6.     void Start()
    7.     {
    8.         var evaluator = new ScriptEvaluator(ScriptDomain.CreateDomain("ExampleDomain", true));
    9.  
    10.         Debug.Log(evaluator.Eval("return 20 / 4 * 6"));
    11.     }
    12. }

    Unlike other Eval methods, Dynamic C# will first compile the eval code before it can be executed meaning that all C# method body syntax is allowed including loops, lambdas, branching etc. It does meant that there is a slight performance hit the first time the eval code is run but Dynamic C# will then cache the code so that subsequent evaluations of the same source will execute at lightning speed. For example: The following code will run quite slow the first time but will then execute at full speed after that:

    Code (CSharp):
    1. using UnityEngine;
    2. using DYnamicCSharp;
    3.  
    4. class ExampleClass : MonoBehaviour
    5. {
    6.      // Some code omitted for conciseness
    7.     void Update()
    8.     {
    9.         Debug.Log(evaluator.Eval("int total = 0; for(int i = 0; i < 5; i++) total += i * 3; return total;"));
    10.     }
    11. }

    Evaluating code like this is pretty impressive, however it is not much use if you cannot share data between the calling app and the evaluating code. Don't worry though, Dynamic C# makes this very easy. Take the following example: We bind a variable to the evaluator and then the eval code can access it via its name. What could be easier.

    Code (CSharp):
    1. using UnityEngine;
    2. using DynamicCSharp;
    3.  
    4. class ExampleClass : MonoBehaviour
    5. {
    6.     // Some code omitted for conciseness
    7.     void Start()
    8.     {
    9.         evaluator.BindVar("sharedInt", 123);
    10.  
    11.         Debug.Log(evaluator.Eval("return sharedInt;"));
    12.     }
    13. }

    We can even access bound variables via a reference so that if the value is changed during evaluation, we can access that new value later on:

    Code (CSharp):
    1. using UnityEngine;
    2. using DynamicCSharp;
    3.  
    4. class ExampleClass : MonoBehaviour
    5. {
    6.     // Some code omitted for conciseness
    7.     void Start()
    8.     {
    9.         Variable<int> intRef = evaluator.BindVar<int>("sharedInt", 123);
    10.  
    11.         evaluator.Eval("sharedInt = sharedInt * 2;");
    12.  
    13.         // intRef = 246
    14.         Debug.Log(intRef);
    15.     }
    16. }

    The same principle applies for delegates meaning that you can invoke a method in the calling application from the evaluated code:

    Code (CSharp):
    1. using UnityEngine;
    2. using DynamicCSharp;
    3.  
    4. class ExampleClass : MonoBehaviour
    5. {
    6.     // Some code omitted for conciseness
    7.     void Start()
    8.     {
    9.         evaluator.BindDelegate("sayHello", () =>
    10.         {
    11.             Debug.Log("Hello World");
    12.         });
    13.  
    14.         evaluator.Eval("sayHello();");
    15.     }
    16. }

    Of course the full Unity API is accessible if required. Just make sure that 'UnityEngine.dll' is referenced in the Dynamic C# settings and you can then write code like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using DynamicCSharp;
    3.  
    4. class ExampleClass : MonoBehaviour
    5. {
    6.     // Some code omitted for conciseness
    7.     void Start()
    8.     {
    9.         evaluator.AddUsing("UnityEngine");
    10.  
    11.         evaluator.BindVar("primitive", PrimitiveType.Cube);
    12.  
    13.         GameObject result = evaluator.Eval<GameObject>("return GameObject.CreatePrimitive(primitive);");
    14.     }
    15. }

    let us know what you think or if you would like to see any other features in Dynamic C#.
     
    Last edited: Sep 19, 2017
    N00MKRAD and one_one like this.
  47. ldlework

    ldlework

    Joined:
    Jan 22, 2014
    Posts:
    31
    Hello, nice asset. However I'm having some troubling issues. I have the following script:

    Code (CSharp):
    1. using UnityEngine;
    2. using DynamicCSharp;
    3.  
    4. public class ScriptRunner : MonoBehaviour
    5. {
    6.     protected string Fullpath(string filename) {
    7.         var root = Application.persistentDataPath;
    8.         return System.IO.Path.Combine(root, filename);
    9.     }
    10.  
    11.     protected ScriptProxy LoadScript(string filename)
    12.     {
    13.         var text = System.IO.File.ReadAllText(Fullpath(filename));
    14.         var domain = ScriptDomain.CreateDomain(null, true);
    15.         ScriptType type = domain.CompileAndLoadScriptSource(text);
    16.         return type.CreateInstance(gameObject);
    17.     }
    18.  
    19.     public void Run()
    20.     {
    21.         LoadScript("script.cs");
    22.         LoadScript("script2.cs");
    23.     }
    24. }
    "script.cs" looks like this:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. class Script : MonoBehaviour
    4. {
    5.     public void Awake()
    6.     {
    7.         Debug.Log("Hello World????");
    8.     }
    9. }
    And "script2.cs" looks like this:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. class Script2 : MonoBehaviour
    4. {
    5.     public void Awake()
    6.     {
    7.         Debug.Log("Hello World!!!!");
    8.     }
    9. }
    So essentially, we're loading a script from a file and passing its text to the source compiler. Great. Then we load a second file with different contents and pass it to the source compiler. Unfortunately, the same exact type is compiled twice! The first one!

    When pressing play on Unity editor I get the following debug statements when I invoke Run():
    Do you see the problem?
     
  48. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Hi,
    We managed to recreate the problem using the code you provided and have now fixed the issue and your code outputs the expected log messages. The problem was caused by the compiler creating multiple assemblies with the same name.

    We are submitting the fix to the asset store which can take a few days but if you don't want to wait then simply email us your invoice number and we can send you the update straight away.

    Thanks for reporting the problem and providing clear steps to replicate it.
     
  49. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    503
    Dynamic C# 1.0.9 has been submitted to the asset store for review. As above, this version addresses an issue where the compiler would generate assemblies with the same names inside different script domains causing the code from the first assembly to be used rather the correct assembly.
     
  50. Gustavo-Santos

    Gustavo-Santos

    Joined:
    May 4, 2014
    Posts:
    3
    Works in Android?