Skip to content

Writing Your Plugin

Kyryl (Etherr) edited this page Jul 12, 2018 · 11 revisions

What can SKSE Do? Capabilities and Limitations

SKSE is a reverse engineering of the Skyrim binary. Bethesda doesn't make the source code of Skyrim available to us (obviously), so to be able to tinker with the innards of the game we have to deconstruct the binary that is given to us. Papyrus scripts call into those same binary functions. Every function in Papyrus corresponds to a C++ function in Skyrim's binary. The same is also true of calls that are made in Skyrim's in-game console. SKSE is essentially a C++ library of decoded Skyrim functions. SKSE makes these functions available to modders so that they have greater power. However, only a limited subset of Skyrim's binary functions have been decoded by the SKSE team, so only those functions that have been reverse engineered will be available. You can see which functions have been successfully decoded in skse\GameReferences.cpp. For example, in the in-game console you can kill an NPC with the command kill. That function also exists in Papyrus:

Function Kill(Actor akKiller = None) native

Both that console call and that Papyrus function will call into the same function in Skyrim's binary. In this case, SKSE has not bothered to decode the binary function for kill because it's already available in Papyrus, and you're going to have to call your SKSE functions in a Papyrus script anyway. Most of SKSE's efforts are focused on decoding functions that are not already available in Papyrus.
The reason for using SKSE is that it offers far greater performance and flexibility than Papyrus. It can expose game functionality that isn't already available in Papyrus. In SKSE, you have the full power of C++ behind you. In Papyrus, you're limited to the extremely weak capabilities of whatever Bethesda has already given you in Papyrus.

Getting Familiar with SKSE

Now that you've settled into the development environment and you have a plugin that both compiles and runs in Skyrim, it's time to get familiar with what SKSE makes available to you and program away in C++.

  • If there already exists a function listed here that does something similar to what you're trying to do or works on the same type of objects, it's worth finding the corresponding Papyrus*.cpp file in the project and looking at the source to see how it works.
  • You might find yourself wanting to use Bethesda's native Papyrus functions in your SKSE plugin. For example, you might want to call AddItem on an ObjectReference. However, only a very limited set of the vanilla native Papyrus functions are available from within an SKSE plugin. The ones that are available you'll be able to find defined in skse\GameReferences.h. Some functions, like AddItem, are very complicated and hence difficult to decode so that we can call them in SKSE. You'll have to stick with calling these functions in Papyrus, or find a lower level way of doing the same thing in your SKSE code. All of the lines that look like this are bits of the Skyrim binary that haven't been decoded:
virtual void	Unk_5C(void);

Common SKSE syntax

  • Many classes have a Create function defined on them. This function is used to create a new instance of that class. Example:
// The EntryData class in GameExtraData.h has a Create() function:
static EntryData * Create(TESForm * item, UInt32 count);

// To use it in my code:
#include "skse\GameExtraData.h"
ExtraContainerChanges::EntryData *newData = ExtraContainerChanges::EntryData::Create(i->first, i->second);
  • Many classes have a Visit function defined on them. This function is used for iterating through every element of that class.
  • Some functions are defined using DEFINE_MEMBER_FN. These are then called using CALL_MEMBER_FN. For example, to get the level of an Actor, it's defined in Actor.h:
DEFINE_MEMBER_FN(GetLevel, UInt16, 0x006A7320);

So, call it as:

CALL_MEMBER_FN(myActorRef, GetLevel)();

Messages

SKSE has various levels of logging. For a regular message, use _MESSAGE. For a message that should only show up for debugging purposes, use _DMESSAGE and use this in SKSEPlugin_Query of your main.cpp:

#ifdef _DEBUG
		gLog.SetLogLevel(IDebugLog::kLevel_DebugMessage);
#else
		gLog.SetLogLevel(IDebugLog::kLevel_Message);
#endif

Fast Development

To make development faster in the future, you might want to set your plugin_example's output directory directly to the Skyrim\Data\skse\plugins folder so that on every build, the files are right in place. Do this by right clicking on the plugin_example project in the Solution Explorer -> Properties -> General. Select the Output Directory -> Browse -> Select your Skyrim\Data\skse\plugins directory.