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.)

Math

min/max functions

When dealing with fmin/fmax instructions, pay close attention to the exact instruction and their order of operands. If the order of operands is wrong, try swapping the arguments of the call to the respective function call.

When calculating the minimum of two floating point operands, either the instruction fmin or fminnm can be used by the compiler. The former is usually generated by sead::Mathf::maxClamp, while fminnm is generated by sead::Mathf::min. Both of these will show up as fminf in the pseudocode.

fmin   => sead::Mathf::maxClamp
fminnm => sead::Mathf::min

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.