Skip to content

Serialization

Ryan edited this page Jul 10, 2019 · 12 revisions

Summary

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.

Class Interface

  • 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 buffer buf with the number of bytes length to the co-save under the signature type with the version version.
  • OpenRecord: This opens a record in the co-save with the given signature type and the version version. It returns a boolean indicating success.
  • WriteRecordData: This writes the buffer buf with the number of bytes length 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 in type, the version in version, and the number of bytes in length. It returns a boolean indicating success.
  • ReadRecordData: This reads the specified number of bytes length into the given buffer buf from the co-save. It returns the number of bytes actually read.
  • ResolveHandle: This takes a virtual machine handle handle as it was when the save was made and writes the handle as it is when the save is loaded into handleOut. It returns a boolean indicating success.
  • ResolveFormId: This takes a formID formId as it was when the save was made and writes the formID as it is when the save is loaded into formIdOut. It returns a boolean indicating success.

Important Notes

  • 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 the length paremeter. For example, given SInt32 num, one would call SKSESerializationInterface::WriteRecordData(&num, sizeof(num)) in order to serialize the value stored in num.
  • Authors should take care that they are serializing the data they actually wish to serialize. For example, given SInt32* num one might call SKSESerializationInterface::WriteRecordData(&num, sizeof(num)), however this would serialize the address of num, 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 to sizeof(SInt32*) which is not necessarily equivalent to sizeof(SInt32). Thus, the proper call to serialize num would look like SKSESerializationInterface::WriteRecordData(num, sizeof(SInt32)).
  • Authors should take care to serialize fixed-width data types. For example, instead of using int authors should use SInt32, which is a 32-bit, signed integer equivalent to int.

Usage

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) {
                    SInt32 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 Extensions

  • 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.

Clone this wiki locally