Skip to main content

sead

sead is Nintendo's C++ standard library. It provides basic data structures such as strings and tree maps and many other essential components (e.g. threads, critical sections, file IO, etc.)

Strings

sead::SafeString constructor

x.vptr = &sead::SafeString::vt;
x.cstr = "some string here";

⬇️

sead::SafeString x = "some string here";

Note that the SafeString constructor is also implicitly called whenever a string literal is converted into an sead::SafeString (because it was passed as an argument to a function that expects an sead::SafeString for example).

sead::FixedSafeString<N> constructor

A sead::FixedSafeString<N> is a fixed-length SafeString. It derives from sead::BufferedSafeString (which derives from sead::SafeString) and contains a char[N] buffer right after the length.

Note: the field assignments may be in a different order.

x._.cstr = (char*)&xxx; // some buffer right after `x`
x._.vptr = &`vtable for'sead::BufferedSafeStringBase<char>;
x.length = N; // where N is a number
sead::BufferedSafeStringBase<char>::assureTerminationImpl_(&x);
*x._.cstr = sead::SafeStringBase<char>::cNullChar;
x._.vptr = ...;

⬇️

sead::FixedSafeString<N> x;

sead::SafeString::cstr

sead::SafeString::cstr returns a const char*, like std::string::c_str. You can expect it to be called whenever a SafeString needs to be passed to a function that takes a C-style string (const char*).

string.vptr->assureTermination(...);
const char* ptr = string.cstr;
// do stuff with string.cstr
// note that the variable may not exist in the pseudocode

⬇️

const char* ptr = string.cstr();

sead::SafeString::calcLength

x.vptr->assureTermination(&x);
v12 = x.cstr;
str_length = 0LL;
v14 = (signed __int64)(v12 + 1);
while ( v12[str_length] != sead::SafeStringBase<char>::cNullChar )
{
if ( *(unsigned __int8 *)(v14 + str_length) == (unsigned __int8)sead::SafeStringBase<char>::cNullChar )
{
LODWORD(str_length) = str_length + 1;
break;
}
if ( *(unsigned __int8 *)(v14 + str_length + 1) == (unsigned __int8)sead::SafeStringBase<char>::cNullChar )
{
LODWORD(str_length) = str_length + 2;
break;
}
v15 = str_length + 2;
str_length += 3LL;
if ( v15 >= 0x80000 )
{
LODWORD(str_length) = 0;
break;
}
}

⬇️

s32 str_length = x.calcLength();

Note that this function is commonly called from other sead::SafeString inline functions.

sead::BufferedSafeString::copy

dest_cstr = dest_safestring.cstr;
source_safestring.vptr->assureTermination(&source_safestring);
source_cstr = source_safestring.cstr;
if ( dest_cstr != source_cstr )
{
source_safestring.vptr->assureTermination(&source_safestring);
v6 = 0LL;
v7 = (signed __int64)(source_safestring.cstr + 1);
while ( source_safestring.cstr[v6] != sead::SafeStringBase<char>::cNullChar )
{
if ( *(unsigned __int8 *)(v7 + v6) == (unsigned __int8)sead::SafeStringBase<char>::cNullChar )
{
LODWORD(v6) = v6 + 1;
break;
}
if ( *(unsigned __int8 *)(v7 + v6 + 1) == (unsigned __int8)sead::SafeStringBase<char>::cNullChar )
{
LODWORD(v6) = v6 + 2;
break;
}
v8 = v6 + 2;
v6 += 3LL;
if ( v8 >= 0x80000 )
{
LODWORD(v6) = 0;
break;
}
}
if ( (signed int)v6 >= dest_safestring.length )
LODWORD(v6) = dest_safestring.length - 1;
v9 = (signed int)v6;
memcpy_0(dest_str, source_str, (signed int)v6);
dest_str[v9] = sead::SafeStringBase<char>::cNullChar;
}

⬇️

dest_safestring = source_safestring;

Note: the while loop comes from an inlined version of sead::SafeString::calcLength.

sead::SafeString::startsWith

if ( sead::SafeStringBase<char>::cNullChar != 'E' )
{
v11 = string->cstr;
v12 = "nemy";
v13 = 'E';
while ( (unsigned __int8)*v11 == v13 )
{
v14 = (unsigned __int8)*v12++;
v13 = v14;
++v11;
if ( v14 == (unsigned __int8)sead::SafeStringBase<char>::cNullChar )
goto LABEL;
}
foo();
}
LABEL:
bar();

⬇️

if (string.startsWith("Enemy"))
bar();
else
foo();

This weird optimization can lead to malformed strings in Hex-Rays's output for strings that contain Japanese or multibyte characters more generally.


sead::SafeString::isEqual / operator==

enemy->vptr->assureTermination(enemy);
enemy->vptr->assureTermination(enemy);
v15 = enemy->cstr;
if ( v15 == "Enemy_Assassin_Junior" )
{
LABEL_37:
foo();
}
else
{
v16 = 0LL;
do
{
v17 = (unsigned __int8)v15[v16];
if ( v17 != (unsigned __int8)aEnemyAssassinJ[v16] )
break;
if ( v17 == (unsigned __int8)sead::SafeStringBase<char>::cNullChar )
goto LABEL_37;
++v16;
}
while ( v16 < 0x80001 );
bar();
}

⬇️

// enemy is a sead::SafeString or a derived class
if (enemy == "Enemy_Assassin_Junior")
foo();
else
bar();

sead::SafeString::isEmpty

if ( *string.cstr == sead::SafeStringBase<char>::cNullChar )

⬇️

if (string.isEmpty())

ScopedLock

sead::CriticalSection::lock(foo);
bar();
sead::CriticalSection::unlock(foo);

⬇️

{
auto lock = sead::makeScopedLock(foo);
bar();
}

Buffer

An sead::Buffer is a non-owning view over a contiguous array of values: it is essentially a wrapper around a raw pointer and a size count.

sead::Buffer::allocBufferAssert

In this example, 0x108 is the size of each item.

v9 = 0x108LL * (signed int)num_tables;
v10 = is_mul_ok((signed int)num_tables, 0x108uLL) == 0;
v11 = __CFADD__(v9, 8LL);
v12 = v9 + 8;
if ( v11 )
v13 = 1;
else
v13 = 0;
if ( (unsigned int)v10 | v13 )
size = 1LL;
else
size = v12;
v15 = (char *)operator new[](size, &heap->_, 8u, &std::nothrow);
if ( v15 )
{
*(_QWORD *)v15 = (signed int)num_tables;
v16 = (BdropTable *)((char*)v15 + 8);

// loop over each item and call a constructor
// note: the constructor may be inlined

// at the end:
buffer->size = num_tables;
buffer->data = v16;
}

⬇️

buffer->allocBufferAssert(num_tables, heap);

Notice how the object count is stored in the first 8 bytes of the allocation: this is because in this specific example, the allocated objects are not trivially destructible.

Another code pattern with a multiplication that looks different:

v13 = operator new[](0x28LL * (unsigned int)count + 8, &heap->_, 8u, &std::nothrow);
if ( v13 )
{
*v13 = count;
v14 = (signed __int64)(v13 + 1);
// loop over each item and call a constructor
// note: the constructor may be inlined
// at the end:
*buffer = count;
*((_QWORD *)buffer + 1) = v14;
}

⬇️

buffer->allocBufferAssert(count, heap);

If each item is a trivially constructible type (e.g. the buffer stores ints or pointers) then there will be no loop that calls a constructor and the compiler will not store the size of the array in the first 8 bytes of the allocation.

sead::Buffer::operator[]

With automatic bounds checks.

if ( buffer->size <= i )
item = buffer->data;
else
item = buffer->data[i];

⬇️

item = buffer[i];

RTTI (Runtime Type Info)

sead::DynamicCast

some_ptr = ...;
x = __ldar(...); // usually a guard variable, but the variable is not always named
another_ptr = some_ptr;
if ( (x & 1) == 0 && _cxa_guard_acquire_0(...)) // the same guard variable
{
... = &...;
_cxa_guard_release_0(...); // the same guard variable
}
if ( another_ptr && another_ptr->checkDerivedRuntimeTypeInfo(another_ptr, ...) )
{
// code that uses another_ptr
}

⬇️

if (auto* another_ptr = sead::DynamicCast<T>(some_ptr))
...

T is a derived type that should be related to the type of the original pointer.