MicroStorage API is an open-source Spore modding library that lets any mod that uses it to store custom data inside saved adventures, which can be either serialized structures/classes, short to medium strings, or even small files. These adventures then can be published to Spore.com and played by both modded and vanilla Spore: Galactic Adventures players. MicroStorage is the way to add additional content for mod users.
MicroStorage API is based (and dependent) on Spore ModAPI, initially developed by emd4600.
- In Visual Studio 2022, create a C++ project using the Spore ModAPI template (follow the official ModAPI tutorial, just ignore the "VS 2019" part of it).
- In the Solution Explorer, right-click your mod project (the one with the name you gave it, NOT "Spore ModAPI") and select "Manage NuGet Packages...".
- Type "MicroStorage API" in the searchbar and select "nuget.org" as the package source.
- Select the latest version and press "Install".
- Include
MicroStorageAPI.hyour .cpp file:
#include <MicroStorageAPI.h>MicroStorage API uses the mComments field in cScenarioString classes, which is unused in the final game, as it was meant for translators of Maxis adventures to provide additional context. Yet, this field is getting saved and loaded anyway.
To separate records (payloads) saved by different mods, MicroStorage uses the namespace:resource key system, in which namespace is the unique mod ID (private globally), and resource is the record ID (private internally).
To operate with MicroStorage API, you must create a mod-specific instance of the MicroStorageClient class, which then should be stored in a global variable (the best practice is to call the class constructor inside your Initialize() function):
YourModGlobals.h(name it yourself):
#pragma once
class MicroStorageClient;
extern MicroStorageClient* g_msc;
// ... Other global variables, if you have anydllmain.cpp:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include "YourModGlobals.h" // The name you gave to the globals header
// ... Other includes
#include <MicroStorageAPI.h>
MicroStorageClient* g_msc = nullptr;
void Initialize()
{
// =================
// \/ ||
g_msc = new MicroStorageClient("UniqueIDofYourMod"); // ||
// This ID should be taken from the "unique" ===========
// attribute in your ModInfo.xml, as it's already meant
// to be non-repeating between mods.
// ... Other stuff to initialize
}
void Dispose()
{
if (g_msc)
{
delete g_msc;
g_msc = nullptr;
}
// ... Other stuff to dispose
}
// ...After initializing your Client, you can use it in the code by including the globals header (in this example it's named YourModGlobals.h).
MicroStorage operates with EAIO streams (IO::IStream*) in both reading and writing operations.
The provided example code extracts the EAIO stream of the file by its Resource Key (cursors~!cursor-talkto.cur) and writes it inside a new MicroStorage Record:
//////////////////////////
// Add new (TestRecord) //
//////////////////////////
// 1. Gets cScenarioData and cScenarioResource from the currently opened Adventure Creator.
cScenarioDataPtr scenarioData = ScenarioMode.GetData();
cScenarioResourcePtr scenarioResource = scenarioData->mpResource;
// 2. Specifies the Resource Key to look up.
ResourceKey key = ResourceKey(id("cursor-talkto"), id("cur"), GroupIDs::Cursors);
// 3. Looks up the provided key, returns a pointer to the database where the resource is located.
Resource::Database* db = ResourceManager.FindRecord(key, nullptr);
if (db)
{
// 4. Opens the file record.
Resource::IRecord* record = nullptr;
db->OpenRecord(key, &record);
// 5. Extracts the EAIO stream of the opened record.
IO::IStream* stream = record->GetStream();
// 6. Starts a new history (undo) entry.
scenarioData->StartHistoryEntry();
// 7. Writes the contents of the stream inside the new MicroStorage Record.
g_msc->Write(stream, id("TestRecord"), scenarioResource.get());
// 8. Commits changes to the history entry.
scenarioData->CommitHistoryEntry();
}After the new MicroStorage Record was created, it can be read back. This example reads the contents of the previously created MicroStorage Record into MemoryStream:
///////////////////////
// Read (TestRecord) //
///////////////////////
// 1. Creates a new SharedPointer to initialize MemoryStream with.
IO::SharedPointer* sharedPtr = new IO::SharedPointer(0, nullptr);
// 2. Creates and initializes a new MemoryStream.
MemoryStreamPtr memoryStream = new IO::MemoryStream(sharedPtr, 0);
memoryStream->SetData(sharedPtr, 0);
// 3. Allows the MemoryStream to resize.
// (Required if you don't know the size of the record.)
memoryStream->SetOption(IO::MemoryStream::kOptionResizeEnabled, 1);
// 4. Reads the contents of the MicroStorage Record
g_msc->Read(scenarioResource.get(), id("TestRecord"), memoryStream.get());It's important to keep the size of any encoded data as small as possible, as Spore.com itself has a strict limit of 600 000 bytes (~585KB) per one published creation; for adventures this also includes 4 large images that usually take up to 4/5 of this size.
To keep adventures made with your mod publishable, you should avoid using MicroStorage Records to store large amounts of data.
Here's a short list of examples on what to NOT use MicroStorage for (if you don't want to break the publishing functionality):
- AVI/VP6 videos (.avi, .vp6).
- EALayer3/XAS audio samples, except for very short ones (.snr, .sns).
- RenderWare/Spore-baked 3D models (.rw4 a.k.a. .rdx9, .gmdl).
- Large uncompressed or lossless-compressed images (.png, .*bitImage, .raster in some cases).
- Actual serialized game saves.
- Enormous amounts of smaller files/data structures (a thousand of small 1KB files is still 1MB).
Although cScenarioResource is stored inside PNGs a compressed DBPF container, most of these won't get much smaller after saving.