-
Notifications
You must be signed in to change notification settings - Fork 156
Serialization
SKSESerializationInterface
can be accessed from SKSEInterface
by calling SKSEInterface::QueryInterface(kInterface_Serialization)
. The serialization interface allows plugin authors to serialize data to the SKSE co-save. This can be useful if the author wishes to persist data between runs of the executable.
-
version
: This is the version of the exported interface. Plugin authors should assert on this field if they require a certain version. -
SetUniqueID
: This sets a unique signature for the plugin, which SKSE will use to call it plugin when serializing to/from the co-save. Give it a four letter signature that's a shorthand for the plugin name (i.e.'PLGN'
). -
SetRevertCallback
: This assigns the function that will be called whenever the game swaps between save profiles. -
SetSaveCallback
: This assigns the function that will be called whenever the game saves. -
SetLoadCallback
: This assigns the function that will be called whenever the game loads. -
SetFormDeleteCallback
: This assigns the function that will be called whenever a form is deleted. -
WriteRecord
: This writes the bufferbuf
with the number of byteslength
to the co-save under the signaturetype
with the versionversion
. -
OpenRecord
: This opens a record in the co-save with the given signaturetype
and the versionversion
. It returns a boolean indicating success. -
WriteRecordData
: This writes the bufferbuf
with the number of byteslength
to the co-save. It returns a boolean indicating success. -
GetNextRecordInfo
: This reads the next record's info from the co-save, storing the signature intype
, the version inversion
, and the number of bytes inlength
. It returns a boolean indicating success. -
ReadRecordData
: This reads the specified number of byteslength
into the given bufferbuf
from the co-save. It returns the number of bytes actually read. -
ResolveHandle
: This takes a virtual machine handlehandle
as it was when the save was made and writes the handle as it is when the save is loaded intohandleOut
. It returns a boolean indicating success. -
ResolveFormId
: This takes a formIDformId
as it was when the save was made and writes the formID as it is when the save is loaded intoformIdOut
. It returns a boolean indicating success.
- Authors should pass a pointer to the beginning of the data that they wish to serialize as the
buf
parameter, and pass the size of that data, in bytes, as thelength
paremeter. For example, givenSInt32 num
, one would callSKSESerializationInterface::WriteRecordData(&num, sizeof(num))
in order to serialize the value stored innum
. - Authors should take care that they are serializing the data they actually wish to serialize. For example, given
SInt32* num
one might callSKSESerializationInterface::WriteRecordData(&num, sizeof(num))
, however this would serialize the address ofnum
, and not the value itself.num
is a pointer, and as such, it stores a the address of an integer value. If one wishes to serialize that value, they can simply pass the value in the pointer itself as our buffer. One must also take care that they are taking the size of the type pointed to, and not the size of the pointer.sizeof(num)
is equivalent tosizeof(SInt32*)
which is not necessarily equivalent tosizeof(SInt32)
. Thus, the proper call to serializenum
would look likeSKSESerializationInterface::WriteRecordData(num, sizeof(SInt32))
. - Authors should take care to serialize fixed-width data types. For example, instead of using
int
authors should useSInt32
, which is a 32-bit, signed integer equivalent toint
.
Authors should define one function for each callback they wish to register, matching the declared typedef
for EventCallback
. In these callbacks, authors can use the passed SKSESerializationInterface*
to perform the serialization tasks that are required for their plugin.
- The Save Callback
#include "skse64/PluginAPI.h" // SKSESerializationInterface
#include <vector> // vector
void SaveCallback(SKSESerializationInterface* a_intfc)
{
SInt32 num = 42;
std::vector<SInt32> arr;
for (std::size_t i = 0; i < 10; ++i) {
arr.push_back(i);
}
if (!a_intfc->WriteRecord('NUM_', 1, &num, sizeof(num))) {
_ERROR("Failed to serialize num!");
}
if (!a_intfc->OpenRecord('ARR_', 1)) {
_ERROR("Failed to open record for arr!");
} else {
std::size_t size = arr.size();
if (!a_intfc->WriteRecordData(&size, sizeof(size))) {
_ERROR("Failed to write size of arr!");
} else {
for (auto& elem : arr) {
if (!a_intfc->WriteRecordData(&elem, sizeof(elem))) {
_ERROR("Failed to write data for elem!");
break;
}
}
}
}
}
In this example, we're attempting to save an integer num
and the contents of a vector arr
. In the case of num
, we use WriteRecord
to open a record and write its value to the co-save. In the case of arr
, we call OpenRecord
to open a record for the array, and WriteRecordData
to serialize the number of elements in the array. Finally, we iterate over the contents of the array and serialize them one at a time.
- The Load Callback
#include "skse64/PluginAPI.h" // SKSESerializationInterface
#include <vector> // vector
void LoadCallback(SKSESerializationInterface* a_intfc)
{
SInt32 num;
std::vector<SInt32> arr;
UInt32 type;
UInt32 version;
UInt32 length;
while (a_intfc->GetNextRecordInfo(&type, &version, &length)) {
switch (type) {
case 'NUM_':
if (!a_intfc->ReadRecordData(&num, sizeof(num))) {
_ERROR("Failed to load num!");
}
break;
case 'ARR_':
{
std::size_t size = a_intfc->ReadRecordData(&size, sizeof(size));
for (UInt32 i = 0; i < size; ++i) {
int elem;
if (!a_intfc->ReadRecordData(&elem, sizeof(elem))) {
_ERROR("Failed to load elem!");
break;
} else {
arr.push_back(elem);
}
}
}
break;
default:
_ERROR("Unrecognized signature type!");
break;
}
}
}
In this example, we're attempting to load an integer num
and the contents of a vector arr
. We call GetNextRecordInfo
in a while
loop and switch-case
on the extracted record types to deserialize from the file. In the case of num
, we call ReadRecordData
directly with num
to deserialize its value. In the case of arr
, we first deserialize the size of the array from the file, and then insert its elements into the vector, one at a time.
Here is a complete implementation of a plugin using SKSESerializationInterface
.
- CommonLib wraps the SKSE interface by using reference types instead of pointer types, where convenient, and passes your plugin handle for you automatically.
Here is a complete implementation of a plugin using CommonLib's SKSE::SerializationInterface.