All times are UTC + 1 hour


It is currently July 23rd, 2019, 07:53



Post new topic Reply to topic  [ 90 posts ]  Go to page 1, 2, 3, 4, 5, 6  Next
Author Message
 Post subject: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 15:03 
Vanguard
User avatar
Offline

Joined: Tue Sep 04, 2012
Posts: 144
In-game name: Drakulix

Scrolls Modding Tutorial



Hallo to all those developers out there, waiting to add their little code base to our favorite game Scrolls.

This little page shall give you hints to get stated to develop awesome mods, making the game we all love even better.
If you have further questions don't hesitate to make new topic in our Mod Discussion Forum.

1. What do you need
    Some general knowledge of C# or Object-Orientated Languages in general.
    Some kind of IDE (not really, but highly recommend). Windows Users may get Visual Studio Express for free, Mac users may download MonoDevelop or the more native app Xamarin Studio based on MonoDevelop.
    Having the latest stable Mono Runtime installed is highly recommend.


2. What can I do with this API? What do I have to expect?
You can do everything, but do not expect to work with a code base that was developed with mods in mind.
What does that mean, is this a Mod API or not?
Yes it is, but it provides some basic functionality to change everything in the game, rather then building up a complete new backend to build mods.

Why is that?
Because Scrolls itself is in kinda early stages of development. Unlike Minecraft, where you cannot expect large changes on the underlying engine, Scrolls does not offer a finished engine, because of a lot missing features waiting to be implemented.
At a stage where game updates only extend the content, it is easy to develop a real API, but Scrolls will see some larger code overhauls in different aspects, we believe.

Still why do not write an API for stuff that is already there?
Because those overhauls would require large changes to such a non-existing API-backend, which would make the Summoner ModLoader unusable for some weeks each time a larger update gets released, because of limited time me and kbasten have.

So get to the point, what does it offer right now?
Injecting own code into every function scrolls has internally.

Whoa, I can change the whole game code???
Nearly everything, yes.

Isn't that highly insecure/unstable???
Nope, the Summoner ModLoader is build to provide maximum safety. If a mod crashes or tries to perform any impossible operations (maybe injecting code to a non-existing function) it just gets unloaded and disabled.
If the ModLoader itself should ever break after a game update, it will even disable itself until a new update is provided.

That provides the possibility of making a simple, solid API, that does not limit developers in any way and just gives the responsibility to write up-to-date code to the mod developers without trusting them enough to make them break the game in the worst case.

(If you try to, you can probably still crash the game somehow, remember that this is version 0.9 and might contain some smaller bugs. This is no excuse for you to write insecure code, though! Safety first!)

3. Lets get started!

To skip the ugly part of making a little base project, download this mod template:
www.scrollsguide.com/uploads/mods/template.mod.zip

Do-it-yourself-version:
Spoiler: [show]



Open the Template.mod.sln file with your IDE and take a look at the Mod.cs file.

It contains a bunch of methods you need to adjust to your needs to get into interaction with the Summoner framework.

At first set a name and a version by overriding the default values at GetName and GetVersion.

What about the rest?
That's where the fun part starts:

4. The Meat
You may ask how can you know which functions you need to extend, if you don't know how the game is internally build?

As a result you need to take a look at the Scrolls Source Code somehow. All game logic lies in the Assembly-CSharp.dll (really all logic).
So here we have multiple ways:

1. Decompile the game assembly. It is a .net Assembly, so ILSpy or similar will do the job.
(Don't try to actual recompile it afterwards, Unity will let you fail not very gracefully)

2. Inspect the assembly from a good IDE. MonoDevelop already enables you to view the contents of an assembly, even private ones, by simple double-clicking the reference. Visual Studio should be able to do similar things at least with some plugins.

So at first lets show you how easy it is to get somebody's account. Why? To make you aware of the harm untrusted non-open-source mods can do! (BTW, this is something every minecraft mod could also have easily done.)

So let's find the correct function to do that. The Login class of the Assembly-CSharp class sounds interesting, doesn't it? Let's have a look there:
And what do we find? A method called login(). Looks good, the username and password seem to be stored in this.username and this.password:
Code:
    private void login()
    {
        if ((!this.signInSent && ((this.username != null) && (this.password != null))) && ((this.username != string.Empty) && (this.password != string.Empty)))
        {
            this._rotateTime = Time.time;
            this.saveSettings();
            this.comm.storeUserCredentials(this.username, this.password);
            this.comm.sendLogin();
            this.errorMess = string.Empty;
            this.signInSent = true;
            base.StartCoroutine("SignInTimeout");
        }
    }


Goal is set. How to proceed? Easy. Make the GetHooks method return the Login MethodDefinition, so the ModLoader knows, we want to hook into that function.

How do we do that?
Construct a MethodDefinition object?
NO, stupid dev, never do that! You always use the original definitions to make sure a right function is hooked. How to get them? From the arguments GetHooks have, here is an example:

Code:
MethodDefinition[] definitions = new MethodDefinition[] {
            scrollsTypes["Login"].Methods.GetMethod("login")[0]
};
return definitions;


Notice the '[0]' at the end? Be careful, if your function is overloaded. Check the Arguments to find out, which one is the right one.

Also did you notice the gameVersion parameter? You might check that to ensure your mod does not mess up on later game versions. If you are asking yourself, what happens if you mod does not do that: It will likely crash. As a result Summoner will deactive it. Your mod might still receive automatic updates through Summoner once you fixed that bug, but without user interaction, it will not get activated anymore. So better prepare yourself to return an empty array, if your hooked functions are not found.

SO how do we make the actual extension??
Easy. With BeforeInvoke and AfterInvoke.

BeforeInvoke gets called before our function is executed and AfterInvoke, well after it. I might be a good idea, to get familiar with the InvocationInfo object, as it contains some pretty helpful information. If you hook multiple functions a switch statement on info.TargetMethod might be a good idea. ;)

BeforeInvoke also has a returnValue argument as well as a bool return type. How shall you handle that? Well most times, set the returnValue to null and return false at the end of your code. But we know, that there might be a reason you want to entirely replace a function. This can be done by setting the returnValue (null for void methods) and return true. Best thing is, that you may do this on a per-call base. Means you do not need to replace it every time, but decided that at runtime per call. But be warned! Other mods will not receive a callback of that function anymore, if you do so. Except, if they have a higher priority. And that priority (most times referred to as 'Mod Order') is set by the user.

(This subject is likely to change in upcoming versions to reduce the amount of possible conflicts for method-replacing mods)

What does that mean for you?
1. You cannot be sure to get a hook on certain functions at all. If another mod with higher priority decides to replace that function, you are out of luck. Never rely on that! (safety first!)
2. You cannot be sure to get a matching AfterInvoke call, if any other mod called later decides to replace the function.
3. You should always replace functions only, if absolutely needed. Clean code is not the best in that case. No matter how messy your solution looks. If you do not need to replace a function, you show coding-skill.

AfterInvoke also contains the returnValue of the hooked method (if any), and your mod may change that, if you so desire.

Back on topic: The username and password fields should be set already at the beginning of our function call, so we just need to read that out in BeforeInvoke and do not need to care for AfterInvoke. Lets just return; on AfterInvoke for that reason.

But they are private! How shall we read those fields!?
Luckily C# offers something called Reflection. You may or may not like it, but for developing mods you will need it. Mojang seems to use the private statement anywhere possible (which is good coding practice, of course!).

Reflection allows you to read and write(!) private fields as well as call private methods. But they might change over game versions, so check, if they actually exist and are even of the type you would expect before doing insecure operations!

Just google how to do so. It is a little difficult, but StackExchange has lots of examples.

For now, we do it fast and insecure for that small example:
Code:
string username = (string)typeof(Login).GetField ("username", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(info.Target);
string password = (string)typeof(Login).GetField ("password", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(info.Target);


Done! You got the username and password, but how can you tell whether or not the password is actually correct? What if the user typed it wrong?

Lets see how our Login class gets its callback:
Code:
public override void handleMessage(Message msg)
    {
        ...
     if (msg is OkMessage)
        {
            OkMessage message = (OkMessage) msg;
            if (message.op != "SignIn")
            {

Aha! But where does that handleMessage call come from? It is not called in the file anywhere, so it must be an external callback. Let's see, what Interfaces the Login class implements:
Code:
AbstractCommListener, IMethodReplacementHost, IModifiableType, IOkCallback


First one looks promising. And where does that callback get set? Let's take a look at the constructor. What? It has no constructor? Okay, whats the Unity-way of initializing? A Start() method. But that looks weird, you may ask. Because it is generated from Unity, probably because it was masked as Coroutine in Unity.
If you take the time to walk through that code, you may find this:
Code:
...
this.<>f__this.comm = App.Communicator;
this.<>f__this.comm.addListener(this.<>f__this);
...


And that is the correct method. The Login class does subscribe to the Communicator class to get callbacks on all incoming network messages. So how can we use that?

We could hook the handleMessage function of the Login class. But we can do better. Why don't we subscribe our mod to the Communicator?
We already reference the Assembly-CSharp file, so that should be easy enough.

Where should we do that? Well when the game is loaded, right?
Our luck, that Summoner guarantees, that the game is initialized when the mod's constructors are called.
Lets modify our mod constructor and add this to it's body:
Code:
App.Communicator.addListener(this);

That's it, you get callbacks on every network message now. (horribly easy, right?)

So now to implement the AbstractCommListener Interface and save the username and password somehow, when the SignIn-OK Message comes and you are done. Maybe clean up and add App.Communicator.removeListener(this) at some point, but that's it.

5. Anything missing?
You may have noticed that the BaseMod class does get initialized with a ModAPI object?
What? I though you did not provide an API?
No we don't, but we try to provide some stuff that makes a little bit more complex operations easier.
You can add and load custom scenes in-game (once you worked yourself a bit into Unity, you will notice what that is and what you can do with it).
ScrollsExtension adds some basic popups to the already powerful Popups class of Scrolls.
And OwnFolder() gets you the writable folder for your mod to store data, while ModAPI.fileOpenDialog() lets you read files per user input from everywhere on any platform.

Finally some other stuff you should take notice off:
- Behavior of Summoner is unknown if you hook functions calling themselves recursively. It might be that you don't get a callback, or only one at the first external function call.
- Summoner and Scrolls is Multi-Platform. If you do not have multiple systems to test (which is likely, I know), please try to follow these rules for file handling to avoid breaking it on Mac OS (or Windows):
    Use System.IO.Path.DirectorySeparatorChar instead of "\\" or "/" in your paths.
    Use Platform.getOS() to find the OS of the user.
    Try to use relative paths from the Environment class
- Scrolls is Mono-based. No matter if you program is using .net 4.5 or you use windows or not, Scrolls will always use (a slightly out-dated and incomplete) Mono-Runtime. So do not expect all .net Libraries you find on the Internet to work nor expect new .net 4.0 features to work. If you are using Visual Studio, get the Mono Tools for Visual Studio to check the compatibility or browse some Unity dev forums for more infos. Still they are no guarantee that it works in-game. So test.
- More to come

6. How to debug?
Yeah, that's a small problem. Debugging is not as easy when running the mod in Unity, as you might hoped. There is no way, I know of, to attach a debugger.
Your only luck is, that Console.WriteLine(Line) commands are actually redirected to a log file through unity.
Look up the location (Desktop, Player) for your OS here: http://docs.unity3d.com/Documentation/Manual/LogFiles.html

7. Testing?
Easy. You do not need to add your mod to a repository do some early testing. Simply go to your Scrolls ModLoader install folder
(Windows: C:/Users/YOUR USERNAME/AppData/Local/Mojang/Scrolls/game/scrolls_data/Managed/ModLoader/mods
Mac OS: YOURScrollsApp.app/Contents/MacOS/game/MacScrolls.app/Contents/Data/Managed/ModLoader/mods)
and create a new folder for your mod. (Name does not really matter for local installed mods)
Copy your mod.dll in there and make sure it's name is ending on .mod.dll (case-sensitive!). Final step: delete mods.ini from the Modloader folder. This will make sure the game reloads the list of mods on startup. It should now show up in-game!

For real releases we suggest adding your mods to a repository. That provides a way to update your mod easily and also builds up some trust if the repository is one of the larger known ones. A submit page for mods in the repository software will be up soon! Check out the repo software if you want to host one yourself: https://github.com/kbasten/ScrollsModRepo

8. Some more examples please?
All our mods are open-source, why not take a look at them:
GameReplay: https://github.com/Drakulix/ScrollsModLoader/tree/master/GameReplay.Mod
DeckSync: https://github.com/kbasten/DeckSync
PlayerInfo: https://github.com/kbasten/PlayerInfo
ChatCommands: https://github.com/kbasten/ChatCommands
And for the more skilled people, the whole ModLoader itself is also open-source:
https://github.com/Drakulix/ScrollsModLoader

Happy Modding!


Last edited by Drakulix on June 24th, 2013, 09:43, edited 3 times in total.
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 18:59 
Newcomer
User avatar
Offline

Joined: Sun Jun 02, 2013
Posts: 4
In-game name: scottystreet
Really well done! This is a great framework and a great tutorial, and I hope we get some really nice mods out of it. You deserve money!

_________________
mah sig
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 20:33 
Vanguard
User avatar
Offline

Joined: Sat Jun 08, 2013
Posts: 142
Location: *taps your shoulder*
In-game name: Matty
Me trying to understand this:

Image
Great guide though, thanks! :)

_________________
“σя∂єя ιѕ α ℓσνєℓу тнιηg; ση ∂ιѕαяяαу ιт ℓαуѕ ιтѕ ωιηg, тєα¢нιηg ѕιмρℓι¢ιту тσ ѕιηg.”

-Feel free to ask me about something in game if you have a question or need help or tips-
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 21:05 
Vanguard
User avatar
Offline

Joined: Tue Sep 04, 2012
Posts: 144
In-game name: Drakulix
Matty wrote:
Me trying to understand this:

Image
Great guide though, thanks! :)

I miss a like button for dat image!
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 22:09 
Newcomer
Offline

Joined: Sun Jun 23, 2013
Posts: 6
In-game name: ImpulseCreed
Im dabbling around with your tutorial at the moment and also fineshed looking at the mods that are already done.

Could you elaborate on how to test your mod that you have written?
Do you have to load it into your local copy of scrolls manually, because the official repository is only for finished and reviewed mods, at least to my understanding.

thanks,
ImpulseCreed
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 22:12 
Vanguard
User avatar
Offline

Joined: Tue Sep 04, 2012
Posts: 144
In-game name: Drakulix
ImpulseCreed wrote:
Im dabbling around with your tutorial at the moment and also fineshed looking at the mods that are already done.

Could you elaborate on how to test your mod that you have written?
Do you have to load it into your local copy of scrolls manually, because the official repository is only for finished and reviewed mods, at least to my understanding.

thanks,
ImpulseCreed


Uh yeah, missed that part in the tutorial. xD
Gonna add that right now.
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 22:36 
Newcomer
Offline

Joined: Sun Jun 23, 2013
Posts: 6
In-game name: ImpulseCreed
Drakulix wrote:
ImpulseCreed wrote:
Im dabbling around with your tutorial at the moment and also fineshed looking at the mods that are already done.

Could you elaborate on how to test your mod that you have written?
Do you have to load it into your local copy of scrolls manually, because the official repository is only for finished and reviewed mods, at least to my understanding.

thanks,
ImpulseCreed


Uh yeah, missed that part in the tutorial. xD
Gonna add that right now.


Thanks!
Btw, half your links are not working because they are collapsed because they are too long and you probably copied them that way
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:15 
Skirmisher
User avatar
Offline

Joined: Sat Jun 22, 2013
Posts: 56
In-game name: aTidwell
I had to also create a config.json in order for it to show up in the list, not sure if thats the correct way to do it, but it seemed to show up at least?
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:19 
Administrator
User avatar
Offline

Joined: Thu Mar 03, 2011
Posts: 1096
Location: Boxmeer
In-game name: kbasten
Tidwell wrote:
I had to also create a config.json in order for it to show up in the list, not sure if thats the correct way to do it, but it seemed to show up at least?

When you place a mod in the mods folder, make sure to have the folder name and the mod name the same. so /mods/ModName/ModName.mod.dll. Then delete mods.ini from the managed/modloader folder. That will reload all mods on startup. The config.json is generated automatically.

Edit: So with every update of the mod you make, copy the new dll to the correct folder and delete mods.ini, nothing more. What I do is put something like "mv /path/to/build/modname.mod.dll /path/to/scrolls/modloader/mod/modname.mod.dll" and "rm /path/to/mods.ini" in the post-build commands so I just have to start scrolls and it should have updated :)

_________________
,.,g.,,.\@|+++++++
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:23 
Vanguard
User avatar
Offline

Joined: Tue Sep 04, 2012
Posts: 144
In-game name: Drakulix
Tidwell wrote:
I had to also create a config.json in order for it to show up in the list, not sure if thats the correct way to do it, but it seemed to show up at least?

No you should not do that (see kbastens answer).
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:41 
Skirmisher
User avatar
Offline

Joined: Sat Jun 22, 2013
Posts: 56
In-game name: aTidwell
Hmm, I must be doing something stupid very stupid, but this doesn't seem to work for me... Not really a C# guy (more webstack with python, js, php) so parts of this are new to me, might just be some rookie mistake...

Video of what I'm doing

Really appreciate any help, hope I'm just being a dummy.
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:47 
Vanguard
User avatar
Offline

Joined: Tue Sep 04, 2012
Posts: 144
In-game name: Drakulix
You are throwing exceptions in critical functions. Summoner automatically unloads your mod.
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:47 
Administrator
User avatar
Offline

Joined: Thu Mar 03, 2011
Posts: 1096
Location: Boxmeer
In-game name: kbasten
Alright, the notimplementedexceptions should be removed. :)
To start: Please don't use spaces in mod names, while that's possible it's just better not to. in GetName(), that is :)

Then, in GetHooks():
Code:
return new MethodDefinition[]{};

Then, in beforeinvoke:
Code:
returnValue = null;
return false;

and in afterinvoke:
Code:
return;

That should at least show it in the list in Scrolls, the post build steps you're doing perfectly. (Deleting mods.ini and copying the file over)

Let me know whether that worked!

_________________
,.,g.,,.\@|+++++++
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:51 
Skirmisher
User avatar
Offline

Joined: Sat Jun 22, 2013
Posts: 56
In-game name: aTidwell
Ahh worked like a charm. I need to do some C# reading, though I think I'm going to probably pick apart the deck importer to get a better idea of how this stuff works. Thanks guys, super helpful.
Top
  Profile Send private message  
 
 Post subject: Re: Mod Development - An Introduction to the Summoner API
PostPosted: June 23rd, 2013, 23:53 
Administrator
User avatar
Offline

Joined: Thu Mar 03, 2011
Posts: 1096
Location: Boxmeer
In-game name: kbasten
Tidwell wrote:
Ahh worked like a charm. I need to do some C# reading, though I think I'm going to probably pick apart the deck importer to get a better idea of how this stuff works. Thanks guys, super helpful.

It might have been unclear from the template mod - it has been changed to a version that compiles and shows up in the modlist when the post-build steps are completed. No more notimplementedexceptions :).

Yes, you should be able to learn a lot from the opensource mods already available, take a look at the ChatCommands or PlayerInfo mod for the most basic of examples :)

_________________
,.,g.,,.\@|+++++++
Top
  Profile Send private message  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 90 posts ]  Go to page 1, 2, 3, 4, 5, 6  Next

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Jump to:  
cron