Saving and loading
EDInventorySave serialises a container to bytes and reads it back, with versioning and migration so
a save written by an older build still loads.
Saving
TArray<uint8> Blob;
EDInventorySave::SaveContainer(*Container, Blob);
That captures the entries, their stack counts, per-stack state, dynamic tags and slot addresses. Promoted item instances are saved too.
Loading
FEDDemoSubsystemAllocator Allocator(UEDInventorySubsystem::Get(this));
EDInventorySave::LoadContainer(*Container, Blob, Allocator);
Loading needs a handle allocator because handles are server-allocated identity. Passing the world subsystem's allocator means loaded items get handles that cannot collide with anything allocated since. Restoring raw handles instead is how you end up with two items that are the same item.
Loading replaces the container's contents and broadcasts a Reset change, so observers re-read rather
than trying to diff.
A whole inventory, bags included
For anything that owns an inventory — a player, an NPC, a corpse — save the inventory, not its containers:
EDInventorySave::SaveInventory(*Inventory, Save->InventoryBytes);
// ... later
EDInventorySave::LoadInventory(*Inventory, Save->InventoryBytes, Allocator);
This is not a convenience wrapper. A container an item brings with it — the grid inside a worn backpack — does not exist at the moment you load. It appears only once the backpack itself has been restored. Code that collects containers up front, saves each one, and later walks the same list to restore them writes the bag's contents to disk and then has nowhere to put them back. The player loses exactly what they took the trouble to pack, and nothing reports an error.
Keying by container tag has a second failure on top: two backpacks of the same kind are two containers with the same tag, so one of them silently overwrites the other.
SaveInventory records each container's owning item by position within the same save rather than
by handle — handles are deliberately re-minted on load, so they cannot survive one — and orders the
records so an owner is always written before the container it brings. A bag inside a bag works for
the same reason, with no special case.
On load, containers the inventory owns outright are matched by type tag; the ones items bring are found through the restored owning item, which by then has re-opened them.
One item on its own
SaveItem / LoadItem for something that is not in a container at all — lying on the ground, in
flight between systems, attached to something that is not an inventory:
EDInventorySave::SaveItem(WorldItem->ToItemEntry(Handle), Record.Bytes);
EDInventorySave::LoadItem(Record.Bytes, Allocator, RestoredEntry);
It writes the whole entry, so a dropped backpack comes back packed and a worn sword comes back worn. Saving a definition and a count instead is how a world reload hands everybody new equipment.
One container on its own
SaveContainer / LoadContainer remain the right call for a container that belongs to nothing else
— a chest, a stash, a vendor's stock, a loot container that has to persist between sessions:
EDInventorySave::SaveContainer(*Chest->GetContainer(), Blob.Bytes);
EDInventorySave::LoadContainer(*Chest->GetContainer(), Blob.Bytes, Allocator);
Pass a TArray<FEDItemHandle>* as the last argument if you need to reconnect anything that referred
to those items by handle — it reports the new handle of every restored entry, in save order.
Loading into a container that already has a type
LoadContainer restores the type stored in the blob. An older save may have none, and blanking a
container's type would silently detach every filter and lookup keyed on it — so an empty saved tag
leaves the configured one alone.
If you see containers losing their identity after a load, that is the case to check first.
Versioning
Blobs carry a version, and migration runs on load. Adding a field to an entry means bumping the
version and handling the old shape; the header for EDInventorySave documents the current version and
what changed.
Replicated formats — the transaction error enum, NetSerialize layouts, the FastArray entry shape —
get the same scrutiny for the same reason: changing one breaks saves and network compatibility with
running clients.
JSON export
There is a debug JSON exporter as well. It is for reading a container in a bug report, not for persistence — it is not versioned and not intended to be loaded back.
Where next
- Replication
- Items and fragments — what per-stack state is