Hi Ferdinand,
I have worked on fitting your example into my project. As you know, there are two major differences that I needed to account for; first, I am doing this in a Scene Hook, and second, I am not keeping Cinema 4D primitives in my BranchInfo, I am keeping URLs and their contents. So I had to adapt.
First I implemented GetBranchInfo in my scene hook. It is more or less a copy of yours.
maxon::Result<Bool> GetBranchInfo(const GeListNode* node, const maxon::ValueReceiver<const BranchInfo&>& info, GETBRANCHINFO flags) const override
{
iferr_scope;
yield_scope;
NodeData::GetBranchInfo (node, info, flags) yield_return;
if (!(flags & GETBRANCHINFO::ONLYWITHCHILDREN) || _assetHead->GetFirst())
{
info (
BranchInfo{
MAXON_REMOVE_CONST (this)->_assetHead,
"clever.unique.name.changed.to.protect.the.innocent"_s,
ID_MORPHINETABLE_URLCACHE_ASSETNODE,
BRANCHINFOFLAGS::NONE
}) yield_return;
}
return true;
}
This will come into play a little later.
In my scenehook I also implemented the Read, Write and CopyTo (I understand how these go together, but will CopyTo ever really be used in a scene hook? I placed a breakpoint in the function and it didn't get called yet).
First of all; Since I need something to store my data in, I decided on creating a NodeData subclass like this - the simplest implementation I could imagine getting away with:
class UrlCacheAssetData : public NodeData
{
INSTANCEOF (UrlCacheAssetData, NodeData)
private:
maxon::String _url;
maxon::String _data;
public:
static NodeData* Alloc()
{
return NewObjClear (UrlCacheAssetData);
}
static Bool RegisterAssetDataPlugin()
{
return RegisterNodePlugin (ID_MORPHINETABLE_URLCACHE_ASSETNODE, "UrlCacheAssetDataPlugin"_s,
PLUGINFLAG_SMALLNODE, UrlCacheAssetData::Alloc, nullptr, 0, nullptr);
}
void SetData(String url, String data)
{
_url = url;
_data = data;
}
String GetUrl() const
{
return _url;
}
String GetData() const
{
return _data;
}
};
So when the user enters a new URL into my plugin, I have added code to add a new asset as such:
auto node = AllocSmallListNode (ID_MORPHINETABLE_URLCACHE_ASSETNODE);
auto const assetData = node->GetNodeData<UrlCacheAssetData>();
if (assetData != nullptr)
{
assetData->SetData (*task->url, loadedData);
_assetHead->InsertLast (node);
_currentCacheSize++;
}
_assetHead is declared the same way you did in your example. _currentCacheSize is a running count of the number of asset nodes we have.
Ok, time to save a document (with a plugin that has added at least one item in the cache). Here is my Write implementation for the scene hook:
Bool Write(const GeListNode* node, HyperFile* hf) const override
{
if (!_assetHead || !hf)
return false;
if (!hf->WriteInt32 (_currentCacheSize))
return false;
auto current = _assetHead->GetFirst();
while (current)
{
auto const assetData = current->GetNodeData<UrlCacheAssetData>();
if (assetData != nullptr)
{
if (!hf->WriteString (assetData->GetUrl()))
return false;
if (!hf->WriteString (assetData->GetData()))
return false;
}
current = current->GetNext();
}
return SceneHookData::Write (node, hf);
}
As you see, my thought is that I first write an integer saying how many url/content pairs I have, then I loop through the asset nodes and write them to the file. I couldn't think of another way to do this, as I do not have the luxury of dealing with objects that already have the WriteObject() functions implemented. I am of course expecting that my way is not the optimal way, or even correct. 
I step through it using the debugger and all seems to work the way I expect. Ok, I close the file and then try to reopen it using C4D.
Here is my Read implementation:
Bool Read(GeListNode* node, HyperFile* hf, Int32 level) override
{
SUPER::Read (node, hf, level);
iferr_scope_handler
{
return false;
};
if (!hf || !_assetHead)
return false;
_assetHead->FlushAll();
if (!hf->ReadInt32 (&_currentCacheSize))
return false;
for (auto i = 0; i < _currentCacheSize; i++)
{
auto newNode = AllocSmallListNode (ID_MORPHINETABLE_URLCACHE_ASSETNODE);
auto const assetData = newNode->GetNodeData<UrlCacheAssetData>();
if (assetData != nullptr)
{
String url;
if (!hf->ReadString (&url))
return false;
String data;
if (!hf->ReadString (&data))
return false;
assetData->SetData (url, data);
_assetHead->InsertLast (newNode);
}
}
return true;
}
I step through it using the debugger and again it works as expected; I read the integer specifying the number of cache items, then loop through that amount of times to read the items. The items are read successfully, containing the expected data! Amazing!
However, at some point after my Read method is exited, I get a crash. This crash happens while Cinema is executing my GetBranchInfo() code above, specifically the info(...) yield return
part. At this point it gets a little difficult for me to look into. I understand that a value is pointing to the wrong place, with a very suspicious value (the "opposite" of a null pointer, so to speak), but I can't tell where this value is coming from.
Exception details:
Exception thrown at 0x00007FF8E8427C10 (c4d_base.xdl64) in Cinema 4D.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF.
This is refering to a location in delegate.h in the SDK where the following code can be seen:
//----------------------------------------------------------------------------------------
/// Forwards the call (invokes the stub function for a callable).
//----------------------------------------------------------------------------------------
MAXON_ATTRIBUTE_FORCE_INLINE RESULT operator ()(ARGS... args) const
{
StubPtrType stub = StubPtrType(_stubPtr);
#ifdef PRIVATE_MAXON_MTABLE_PTMF
return (reinterpret_cast<Delegate*>(_objectPtr)->*stub)(std::forward<ARGS>(args)...);
#else
return stub(_objectPtr, std::forward<ARGS>(args)...);
#endif
}
Line 761-772 in my 2024 SDK. I don't know where exactly the 0xFFF... is coming from but it is not the stub/_stubPtr and the exception happens in the line with the reinterpret_cast
.
The call stack does not tell me a lot apart from this happening while Cinema is running my GetBranchInfo:

I realize of course that debugging this based on a forum post is hopeless - unless the 0xFFFF... value could be caused by only one very well known thing, I expect you would need my complete code for this? It changed quite a bit since last time.
I am quite sure that there is something that is not properly initialized or something along those lines, since this happens only when I try to open a document saved with my branch info in it. Oh, and for completeness, my asset head is declared as a private instance variable in my scene hook like this:
AutoAlloc<GeListHead> _assetHead;
And I also borrowed from your init code and put it in the same class.
Bool Init(GeListNode* node, Bool isCloneInit) override
{
if (!_assetHead)
return false;
_assetHead->SetParent (node);
return SUPER::Init (node, isCloneInit);
}
If you want me to send you the code, just let me know. If you want me to change or test anything else, the same. And if I am completely off track with my asset implementation, doing something that couldn't possibly work - please don't be afraid to tell me. I'm a big boy, I can take it. 
Thanks again!