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.