FFXIVClientStructs provides C# wrappers for C++ STD containers found in the game client. These containers manage heap-allocated collections and expose them through familiar C# interfaces.
StdVector<T, TMemorySpace>
A dynamic array container similar to List<T>, wrapping std::vector.
Memory Layout
[StructLayout(LayoutKind.Sequential)]
public unsafe struct StdVector<T, TMemorySpace>
where T : unmanaged
where TMemorySpace : IStaticMemorySpace
{
public T* First; // Pointer to first element
public T* Last; // Pointer to one past last element
public T* End; // Pointer to end of allocation
}
Size: 24 bytes (3 pointers)
Key Properties
- Count: Number of elements (
Last - First)
- Capacity: Allocated capacity (
End - First)
- LongCount: 64-bit element count
Access Patterns
// Direct element access
ref var element = ref vector[index];
// Span-based iteration (efficient)
Span<MyStruct> span = vector.AsSpan();
foreach (ref var item in span)
{
// Modify item directly
}
// Pointer iteration
for (var ptr = vector.First; ptr < vector.Last; ptr++)
{
// Access *ptr
}
// LINQ-style enumeration
foreach (ref var item in vector)
{
// Enumerates by reference
}
Common Operations
// Adding elements
vector.AddCopy(in item); // Copy item
vector.AddSpanCopy(span); // Copy span
vector.InsertCopy(index, in item); // Insert at index
// Removing elements
vector.RemoveAt(index);
vector.RemoveRange(index, count);
vector.Clear();
// Searching
long idx = vector.LongIndexOf(item);
bool found = vector.Contains(item);
// Capacity management
vector.EnsureCapacity(newSize);
vector.TrimExcess();
vector.Resize(newSize);
Working with Pointer Collections
For vectors of pointers, use Pointer<T> wrapper:
// Vector of pointers to structs
StdVector<Pointer<MyStruct>, IStaticMemorySpace.Default> pointerVector;
// Access pointed-to data
for (int i = 0; i < pointerVector.Count; i++)
{
MyStruct* ptr = pointerVector[i].Value;
if (ptr != null)
{
// Use ptr->Field
}
}
StdMap<TKey, TValue, TMemorySpace>
An ordered associative container wrapping std::map, implemented as a red-black tree.
Memory Layout
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public unsafe struct StdMap<TKey, TValue, TMemorySpace>
where TKey : unmanaged
where TValue : unmanaged
where TMemorySpace : IStaticMemorySpace
{
public RedBlackTree<StdPair<TKey, TValue>, TKey, PairKeyExtractor<TKey, TValue>> Tree;
}
Size: 16 bytes (red-black tree structure)
Access Patterns
// Dictionary-style access (creates entry if missing)
ref var value = ref map[key];
// Safe lookup
if (map.TryGetValue(key, out TValue value, copyCtor: false))
{
// Use value
}
// Get pointer to value (no copy)
if (map.TryGetValuePointer(key, out TValue* valuePtr))
{
// Modify *valuePtr directly
}
// Iterate key-value pairs
foreach (ref var pair in map)
{
TKey key = pair.Item1;
TValue value = pair.Item2;
}
// Keys and values collections
foreach (ref var key in map.Keys) { }
foreach (ref var value in map.Values) { }
Adding and Removing
// Try add (returns false if key exists)
if (map.TryAddValueKCopyVCopy(in key, in value))
{
// Added successfully
}
// Remove by key
if (map.Remove(in key))
{
// Removed successfully
}
// Clear all entries
map.Clear();
StdSet<T, TMemorySpace>
An ordered set container wrapping std::set, implemented as a red-black tree.
Memory Layout
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public unsafe struct StdSet<T, TMemorySpace>
where T : unmanaged
where TMemorySpace : IStaticMemorySpace
{
public RedBlackTree<T, T, DefaultKeyExtractor<T>> Tree;
}
Size: 16 bytes
Operations
// Add elements (returns false if already exists)
if (set.AddCopy(in value))
{
// Added successfully
}
// Check membership
if (set.Contains(in value))
{
// Value exists
}
// Remove
set.Remove(in value);
// Set operations
set.UnionWith(otherSet);
set.IntersectWith(otherSet);
set.ExceptWith(otherSet);
set.SymmetricExceptWith(otherSet);
// Iterate in sorted order
foreach (ref var item in set)
{
// Items are sorted
}
// Reverse iteration
foreach (ref var item in set.Reverse())
{
// Descending order
}
StdList<T, TMemorySpace>
A doubly-linked list wrapping std::list.
Memory Layout
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public unsafe struct StdList<T, TMemorySpace>
where T : unmanaged
where TMemorySpace : IStaticMemorySpace
{
public IStdList<T>.Node* Head; // Sentinel node
public ulong Size; // Number of elements
}
Size: 16 bytes
Node Structure
Each node contains:
T Value - The stored value
Node* Next - Pointer to next node
Node* Previous - Pointer to previous node
Access Patterns
// Add elements
var node = list.AddLastCopy(in value);
var firstNode = list.AddFirstCopy(in value);
// Add after/before specific node
var newNode = list.AddAfterCopy(existingNode, in value);
// Manual node iteration
for (var node = list.First.Value; node != list.Last.Value; node = node->Next)
{
ref T value = ref node->Value;
}
// Find nodes
var foundNode = list.Find(in value);
if (foundNode.Value != null)
{
list.Remove(foundNode);
}
// Remove from ends
list.RemoveFirst();
list.RemoveLast();
StdDeque<T>
A double-ended queue wrapping std::deque, implemented as a map of fixed-size blocks.
Memory Layout
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
public unsafe struct StdDeque<T>
where T : unmanaged
{
public ContainerProxy<T>* ContainerBase;
public T** Map; // Array of pointers to blocks
public ulong MapSize; // Size of map
public ulong MyOff; // Offset of first element
public ulong MySize; // Number of elements
}
Size: 40 bytes
Block Size
Block size varies by element size:
- 1 byte elements: 16 per block
- 2 byte elements: 8 per block
- 4 byte elements: 4 per block
- 8 byte elements: 2 per block
- Larger: 1 per block
Access
// Random access (index calculation)
ref var element = ref deque[index];
// Iteration
foreach (ref var item in deque)
{
// Access items
}
// Convert to array
T[] array = deque.ToArray();
StdDeque is less commonly used in game structures. Most mutable collections use StdVector.
StdPair<T1, T2>
A value tuple wrapper for std::pair.
Memory Layout
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct StdPair<T1, T2>
where T1 : unmanaged
where T2 : unmanaged
{
public T1 Item1;
public T2 Item2;
}
Size: Varies (sum of T1 and T2 sizes, 8-byte aligned)
Usage
// Create
var pair = new StdPair<int, float>(42, 3.14f);
// Access
int key = pair.Item1;
float value = pair.Item2;
// Deconstruct
var (key, value) = pair;
// Convert to/from KeyValuePair
KeyValuePair<int, float> kvp = pair;
StdPair<int, float> pair2 = kvp;
// Used in StdMap
StdMap<int, MyStruct, IStaticMemorySpace.Default> map;
foreach (ref var pair in map)
{
int key = pair.Item1;
MyStruct value = pair.Item2;
}
Memory Management
All STD containers use the specified TMemorySpace for allocations:
// Default game allocator
StdVector<int, IStaticMemorySpace.Default> gameVector;
// Specific allocator types
StdVector<MyStruct, IStaticMemorySpace.ApricotUserSpace> customVector;
Disposal
Containers must be disposed to free native memory:
vector.Dispose(); // Frees all elements and capacity
map.Dispose(); // Frees all tree nodes
list.Dispose(); // Frees all list nodes
Never dispose containers that are part of game structures. Only dispose containers you create.
Common Patterns
Safe Enumeration
// Make defensive copy if modifying during enumeration
var items = vector.ToArray();
foreach (var item in items)
{
// Safe to modify vector here
}
Null Checks for Pointers
StdVector<Pointer<MyStruct>, IStaticMemorySpace.Default> pointers;
for (int i = 0; i < pointers.Count; i++)
{
var ptr = pointers[i].Value;
if (ptr == null) continue;
// Use ptr safely
}
Capacity Pre-allocation
// Avoid reallocations
vector.EnsureCapacity(expectedCount);
for (int i = 0; i < expectedCount; i++)
{
vector.AddCopy(items[i]);
}
See Also