Decompiling sead: a small example
For those who are unfamiliar with recent Nintendo games, sead
is Nintendo’s homemade C++ standard library. It is used in every recent first-party game such as Breath of the Wild, New Horizons or Labo.
Since those games rely on sead for data structures, strings, threads, file I/O and more, decompiling a game like BotW (even a very partial decomp) involves decompiling sead first.
By decompiling, I do not mean simply throwing the executable into a decompiler, but painstakingly reconstructing source code that is as accurate as possible to the original code1. Getting an accurate implementation of sead with matching code2 is all the more essential since many of its functions are inlined.
One sead component that is used at the heart of BotW’s actor system to speed up lookups by name is sead::TreeMapImpl<K, V>
3. Let’s see what it looks like…
TreeMapImpl and TreeMapNode #
Since TreeMapImpl is a class template, looking at any variant of the class would work. I chose TreeMapImpl<TreeMapKeyImpl<SafeString>>
as its insert
function looked cleanest.
void* sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char>>>::insert(__int64 a1, void *a2, void *a3)
{
// ...
v3 = a3;
v4 = a2;
if ( a2 )
{
cmp = sead::TreeMapKeyImpl<sead::SafeStringBase<char>>::compare(
(sead::SafeString *)a3 + 2,
(sead::SafeString *)a2 + 2);
if ( cmp & 0x80000000 )
{
*((_QWORD *)v4 + 1) = sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char>>>::insert(
a1,
*((void **)v4 + 1),
v3);
j = (void *)*((_QWORD *)v4 + 2);
if ( !j )
goto label1;
}
else if ( cmp )
{
*((_QWORD *)v4 + 2) = sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char>>>::insert(
a1,
*((void **)v4 + 2),
v3);
j = (void *)*((_QWORD *)v4 + 2);
if ( !j )
goto label1;
}
else
{
if ( v4 != v3 )
{
*((_QWORD *)v3 + 2) = *((_QWORD *)v4 + 2);
*((_QWORD *)v3 + 1) = *((_QWORD *)v4 + 1);
*((_QWORD *)v3 + 3) = *((_QWORD *)v4 + 3);
(*(void (__fastcall **)(void *))(*(_QWORD *)v4 + 16LL))(v4);
v4 = v3;
}
j = (void *)*((_QWORD *)v4 + 2);
if ( !j )
goto label1;
}
if ( !(*((_BYTE *)j + 24) & 1) )
{
v8 = *((_QWORD *)v4 + 1);
if ( !v8 || *(_BYTE *)(v8 + 24) & 1 )
{
*((_QWORD *)v4 + 2) = *((_QWORD *)j + 1);
*((_QWORD *)j + 1) = v4;
*((_QWORD *)j + 3) = *((_QWORD *)v4 + 3);
*((_QWORD *)v4 + 3) = 0LL;
v3 = (void *)*((_QWORD *)j + 1);
if ( !v3 )
return j;
label2:
if ( *((_BYTE *)v3 + 24) & 1 || (m = *((_QWORD *)v3 + 1)) == 0 )
{
k = v3;
if ( *((_BYTE *)v3 + 24) & 1 )
return j;
}
else
{
if ( *(_BYTE *)(m + 24) & 1 )
{
k = v3;
}
else
{
*((_QWORD *)j + 1) = *((_QWORD *)v3 + 2);
*((_QWORD *)v3 + 2) = j;
*((_QWORD *)v3 + 3) = *((_QWORD *)j + 3);
*((_QWORD *)j + 3) = 0LL;
k = (_BYTE *)*((_QWORD *)v3 + 1);
j = v3;
if ( !k )
return v3;
}
if ( k[24] & 1 )
return j;
}
v11 = *((_QWORD *)j + 2);
if ( !v11 || *(_BYTE *)(v11 + 24) & 1 )
return j;
*((_QWORD *)j + 3) ^= 1uLL;
*((_QWORD *)k + 3) ^= 1uLL;
*(_QWORD *)(*((_QWORD *)j + 2) + 24LL) ^= 1uLL;
return j;
}
}
label1:
j = v4;
v3 = (void *)*((_QWORD *)v4 + 1);
if ( !v3 )
return j;
goto label2;
}
*((_QWORD *)a3 + 2) = 0LL;
*((_QWORD *)a3 + 3) = 0LL;
*((_QWORD *)a3 + 1) = 0LL;
return v3;
}
Because of all those ugly casts, the pseudocode isn’t very readable yet, but it is already clear that a2
and a3
are both pointers to nodes. The binary search at the beginning and the boolean at offset 0x18 in nodes suggest that TreeMapImpl in fact implements a red-black tree.
With this in mind, structures can now be defined in IDA to make the output easier to read:
template <typename Key>
class TreeMapImpl
{
public:
using Node = TreeMapNode<Key>;
Node* insert(Node* root, Node* node);
// ...
};
template <typename Key>
class TreeMapNode
{
public:
virtual ~TreeMapNode() = default;
virtual void erase_() = 0;
// ...
protected:
friend class TreeMapImpl<Key>;
enum class Color : u64
{
Red = 0,
Black = 1,
};
TreeMapNode* mLeft;
TreeMapNode* mRight;
Color mColor;
Key mKey;
};
Well, I cannot exactly define them like that since IDA and Hex-Rays do not really support C++. Note that when C++ classes are C-ified, the pointer to the virtual table (or vtable) has to be added manually. After defining sead::TreeMapNode_SafeString
and renaming variables, re-running the decompiler yields the following output:
sead::TreeMapNode_SafeString *__fastcall sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char>>>::insert(sead::TreeMapImpl_SafeString *this, sead::TreeMapNode_SafeString *root, sead::TreeMapNode_SafeString *node)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
n = node;
r = root;
if ( root )
{
cmp = sead::TreeMapKeyImpl<sead::SafeStringBase<char>>::compare(&node->mKey, &root->mKey);
if ( cmp & 0x80000000 )
{
r->mLeft = sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char>>>::insert(this, r->mLeft, n);
j = r->mRight;
if ( !j )
goto step2;
}
else if ( cmp )
{
r->mRight = sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char>>>::insert(this, r->mRight, n);
j = r->mRight;
if ( !j )
goto step2;
}
else
{
if ( r != n )
{
n->mRight = r->mRight;
n->mLeft = r->mLeft;
n->mColor = r->mColor;
r->vtable->erase_(r);
r = n;
}
j = r->mRight;
if ( !j )
goto step2;
}
if ( !(j->mColor & 1) )
{
v8 = r->mLeft;
if ( !v8 || v8->mColor & 1 )
{
r->mRight = j->mLeft;
j->mLeft = r;
j->mColor = r->mColor;
r->mColor = 0LL;
n = j->mLeft;
if ( !n )
return j;
step3:
if ( n->mColor & 1 || (nLeft = n->mLeft) == 0LL )
{
k = n;
if ( n->mColor & 1 )
return j;
}
else
{
if ( nLeft->mColor & 1 )
{
k = n;
}
else
{
j->mLeft = n->mRight;
n->mRight = j;
n->mColor = j->mColor;
j->mColor = 0LL;
k = n->mLeft;
j = n;
if ( !k )
return n;
}
if ( k->mColor & 1 )
return j;
}
v11 = j->mRight;
if ( !v11 || v11->mColor & 1 )
return j;
j->mColor ^= 1uLL;
k->mColor ^= 1uLL;
j->mRight->mColor ^= 1uLL;
return j;
}
}
step2:
j = r;
n = r->mLeft;
if ( !n )
return j;
goto step3;
}
node->mRight = 0LL;
node->mColor = 0LL;
node->mLeft = 0LL;
return n;
}
Much better! Now I can actually implement TreeMapImpl<K>::insert
and try to make it match…
A first attempt #
While Hex-Rays appears to focus on readability more than on accuracy when compared to decompilers like mips_to_c, the generated pseudocode is usually still a good starting point.
This might seem counterintuitive, but manually applying a couple of optimization prettification passes often goes a long way towards getting a matching version. This is what I obtained after applying transformations like returning early and deduplicating common code paths:
template <typename Key>
TreeMapNode<Key>* TreeMapImpl<Key>::insert(Node* root, Node* node)
{
if (!root)
{
node->mLeft = node->mRight = nullptr;
node->mColor = Node::Color::Red;
return node;
}
const s32 cmp = node->key().compare(root->key());
if (cmp < 0)
{
root->mLeft = insert(root->mLeft, node);
}
else if (cmp > 0)
{
root->mRight = insert(root->mRight, node);
}
else if (root != node)
{
node->mRight = root->mRight;
node->mLeft = root->mLeft;
node->mColor = root->mColor;
root->erase_();
root = node;
}
Node* j = root->mRight;
if (j && j->isRed() && (!root->mLeft || !root->mLeft->isRed()))
{
root->mRight = j->mLeft;
j->mLeft = root;
j->mColor = root->mColor;
root->mColor = Node::Color::Red;
}
else
{
j = root;
}
node = j->mLeft;
Node* k;
if (!node || !node->isRed() || !node->mLeft || !node->mLeft->isRed())
{
k = node;
}
else
{
j->mLeft = node->mRight;
node->mRight = j;
node->mColor = j->mColor;
j->mColor = Node::Color::Red;
k = node->mLeft;
j = node;
}
if (!k || !k->isRed())
return j;
if (!j->mRight || !j->mRight->isRed())
return j;
j->flipColor();
k->flipColor();
j->mRight->flipColor();
return j;
}
…where flipColor and isRed are defined as follows:
void flipColor() { mColor = Color(u64(mColor) ^ 1u); }
bool isRed() const { return (u8(mColor) & 1u) == bool(Color::Red); }
This version isn’t ideal considering that there are still ugly j
and k
variables that probably don’t exist in the original code, but all the gotos are gone and it almost matches the original function. Promising. This can be seen by using simonlindholm’s asm-differ, which is an amazing tool for comparing assembly:
/home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:200 | j->mColor = Node::Color::Red; a5ddb0: str xzr, [x8, #0x18] 2a718: str xzr, [x8, #0x18] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:201 | k = node->mLeft; a5ddb4: ldr x9, [x19, #8] 2a71c: ldr x9, [x19, #8] a5ddb8: mov x8, x19 < a5ddbc: cbnz x9, a5ddd8 ~> < a5ddc0: b a5de1c ~> < a5ddc4: ~> mov x9, x19 | 2a720: cbz x9, 2a778 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x190> ~> a5ddc8: ldrb w10, [x9, #0x18] 2a724: ldrb w10, [x9, #0x18] a5ddcc: tbz w10, #0, a5dde0 ~> 2a728: tbz w10, #0, 2a73c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x154> ~> a5ddd0: b a5de18 ~> i 2a72c: b 2a778 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x190> ~> a5ddd4: ~> mov x9, x19 2a730: ~> mov x9, x19 a5ddd8: ~> ldrb w10, [x9, #0x18] | 2a734: mov x19, x8 a5dddc: tbnz w10, #0, a5de18 ~> i 2a738: tbnz w10, #0, 2a778 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x190> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:208 | } | | if (!k || !k->isRed()) | return j; | | if (!j->mRight || !j->mRight->isRed()) a5dde0: ~> ldr x10, [x8, #0x10] r 2a73c: ~> ldr x8, [x19, #0x10] a5dde4: cbz x10, a5de18 ~> r 2a740: cbz x8, 2a778 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x190> ~> bool sead::BitUtil::bitCastPtr<bool>(void const*, long) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/prim/seadBitUtil.h:35 a5dde8: ldrb w10, [x10, #0x18] r 2a744: ldrb w10, [x8, #0x18] a5ddec: tbnz w10, #0, a5de18 ~> i 2a748: tbnz w10, #0, 2a778 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x190> ~> sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::flipColor() /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:62 | void flipColor() { mColor = Color(size_t(mColor) ^ 1u); } a5ddf0: ldr x10, [x8, #0x18] r 2a74c: ldr x10, [x19, #0x18] a5ddf4: eor x10, x10, #0x1 2a750: eor x10, x10, #0x1 a5ddf8: str x10, [x8, #0x18] r 2a754: str x10, [x19, #0x18] a5ddfc: ldr x10, [x9, #0x18] 2a758: ldr x10, [x9, #0x18] a5de00: eor x10, x10, #0x1 2a75c: eor x10, x10, #0x1 a5de04: str x10, [x9, #0x18] 2a760: str x10, [x9, #0x18] a5de08: ldr x9, [x8, #0x10] < a5de0c: ldr x10, [x9, #0x18] r 2a764: ldr x9, [x8, #0x18] a5de10: eor x10, x10, #0x1 r 2a768: eor x9, x9, #0x1 a5de14: str x10, [x9, #0x18] r 2a76c: str x9, [x8, #0x18] > 2a770: b 2a778 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x190> ~> sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) a5de18: ~> mov x19, x8 2a774: ~> mov x19, x8 /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:215 (discriminator 1) | | j->flipColor(); | k->flipColor(); | j->mRight->flipColor(); | return j; | } a5de1c: ~> mov x0, x19 2a778: ~> mov x0, x19 a5de20: ldp x29, x30, [sp, #0x20] 2a77c: ldp x29, x30, [sp, #0x20] a5de24: ldp x20, x19, [sp, #0x10] 2a780: ldp x20, x19, [sp, #0x10] a5de28: ldr x21, [sp], #0x30 2a784: ldr x21, [sp], #0x30 a5de2c: ret 2a788: ret
A quick look at the end of the diff reveals two issues:
- Clang decided to invert the jump condition (at
a5ddb8
on the left): instead of jumping ifnode->mLeft
is not null, the second version branches if it is; - The original code unnecessarily reloads
j->mRight
from memory (ata5de08
), which results in different register allocation in the surrounding code.
If TreeMap were not a header-only utility, I might have stopped here since it is pretty much clear that the code is semantically equivalent. In this case though, it is worth spending some more time to fix those issues.
Inverting the condition and swapping the statements for the if
statement in question has absolutely zero effect: Clang generates the exact same code. How can this be fixed?
asm("")
to the rescue #
In my experience, adding asm("")
in random places can force Clang to generate different code. I don’t really know how that works; it seems to act as a compile-time memory barrier and sometimes also appears to inhibit some optimizations…
After several attempts, I found a combination of asm("")
that works around both issues:
asm("");
if (!k || !k->isRed())
return j;
if (!j->mRight || !j->mRight->isRed())
return j;
j->flipColor();
k->flipColor();
asm("");
j->mRight->flipColor();
return j;
But now Clang realises that some of the if (!k)
checks are redundant and merges the code paths (issue #3):
| k = node->mLeft; a5ddb4: ldr x9, [x19, #8] 2a718: ldr x9, [x19, #8] a5ddb8: mov x8, x19 2a71c: mov x8, x19 /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:205 | asm(""); a5ddbc: cbnz x9, a5ddd8 ~> i 2a720: cbnz x9, 2a72c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x148> ~> a5ddc0: b a5de1c ~> i 2a724: b 2a770 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x18c> ~> a5ddc4: ~> mov x9, x19 2a728: ~> mov x9, x19 bool sead::BitUtil::bitCastPtr<bool>(void const*, long) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/prim/seadBitUtil.h:35 a5ddc8: ldrb w10, [x9, #0x18] 2a72c: ~> ldrb w10, [x9, #0x18] a5ddcc: tbz w10, #0, a5dde0 ~> < a5ddd0: b a5de18 ~> < a5ddd4: ~> mov x9, x19 < a5ddd8: ~> ldrb w10, [x9, #0x18] < a5dddc: tbnz w10, #0, a5de18 ~> 2a730: tbnz w10, #0, 2a76c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x188> ~>
Unfortunately, further simplifying the control flow or doing the exact opposite – reduplicating some of the checks and return
s – does nothing to solve that new problem; Clang emits almost identical code every time.
Function outlining #
Another thing that usually helps when doing sead/BotW decomp is imagining how a typical C++ developer would have written the code and extracting bits of code into smaller functions to make the code cleaner. In other words: refactoring.
Given that this is red-black tree stuff, it seems natural to introduce the following helper functions:
// Null nodes are leaves, and all leaves are black. This also deduplicates some checks.
static bool isRed(const TreeMapNode* node) { return node && node->isRed(); }
TreeMapNode* rotateLeft();
TreeMapNode* rotateRight();
After having implemented those helpers, it became obvious to me that they were used in insert()
. All those !child || !child->isRed()
are in fact a simple !Node::isRed(child)
and the weird assignments are due to rotations changing the left and right node pointers. As you’d expect, outlining all three functions significantly improves code clarity:
template <typename Key>
TreeMapNode<Key>* TreeMapImpl<Key>::insert(Node* root, Node* node)
{
if (!root)
{
node->mLeft = node->mRight = nullptr;
node->mColor = Node::Color::Red;
return node;
}
const s32 cmp = node->key().compare(root->key());
if (cmp < 0)
{
root->mLeft = insert(root->mLeft, node);
}
else if (cmp > 0)
{
root->mRight = insert(root->mRight, node);
}
else if (root != node)
{
node->mRight = root->mRight;
node->mLeft = root->mLeft;
node->mColor = root->mColor;
root->erase_();
root = node;
}
if (Node::isRed(root->mRight) && !Node::isRed(root->mLeft))
root = root->rotateLeft();
if (Node::isRed(root->mLeft) && Node::isRed(root->mLeft->mLeft))
root = root->rotateRight();
if (Node::isRed(root->mLeft) && Node::isRed(root->mRight))
{
root->flipColor();
root->mLeft->flipColor();
root->mRight->flipColor();
}
return root;
}
Does it match?
<lines of matching instructions removed for brevity...> a5dde0: ~> ldr x10, [x8, #0x10] 2a750: ~> ldr x10, [x8, #0x10] a5dde4: cbz x10, a5de18 ~> i 2a754: cbz x10, 2a784 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x194> ~> a5dde8: ldrb w10, [x10, #0x18] r 2a758: ldrb w11, [x10, #0x18] a5ddec: tbnz w10, #0, a5de18 ~> r 2a75c: tbnz w11, #0, 2a784 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x194> ~> sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::flipColor() /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:61 | void flipColor() { mColor = Color(size_t(mColor) ^ 1u); } a5ddf0: ldr x10, [x8, #0x18] r 2a760: ldr x11, [x8, #0x18] a5ddf4: eor x10, x10, #0x1 r 2a764: eor x11, x11, #0x1 a5ddf8: str x10, [x8, #0x18] r 2a768: str x11, [x8, #0x18] a5ddfc: ldr x10, [x9, #0x18] r 2a76c: ldr x11, [x9, #0x18] a5de00: eor x10, x10, #0x1 r 2a770: eor x11, x11, #0x1 a5de04: str x10, [x9, #0x18] r 2a774: str x11, [x9, #0x18] a5de08: ldr x9, [x8, #0x10] r 2a778: ldr x9, [x10, #0x18] a5de0c: ldr x10, [x9, #0x18] < a5de10: eor x10, x10, #0x1 r 2a77c: eor x9, x9, #0x1 a5de14: str x10, [x9, #0x18] r 2a780: str x9, [x10, #0x18] sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) a5de18: ~> mov x19, x8 2a784: ~> mov x19, x8
Unfortunately, it doesn’t… While issues #1 and #3 are definitely solved now – without the use of hacks! – issue #2 (the missing memory load) has reappeared. Re-adding asm("")
right before root->mRight->flipColor();
would force Clang to reload mRight
from memory, but that obviously isn’t a satisfactory solution.
Furthermore, adding asm("")
in TreeMapNode<T>::flipColor
itself does not solve the problem as that causes another member variable to be reloaded.
A less hacky fix? #
What if, instead of casting twice manually in TreeMapNode<T>::flipColor
:
void flipColor() { mColor = Color(u64(mColor) ^ 1u); }
…Nintendo instead used some kind of bit casting utility, which would let them reinterpret the bits of one type as another type? Something like C++20’s std::bit_cast
but in the opposite direction, for example:
template <typename To, typename From>
inline void bitCastWrite(const From& value, To* ptr)
{
static_assert(sizeof(To) == sizeof(From), "To and From must have the same size");
std::memcpy(ptr, &value, sizeof(To));
}
Surprisingly enough, replacing flipColor
with
void flipColor() { BitUtil::bitCastWrite(u64(mColor) ^ 1u, &mColor); }
does materialize the extra load. I suspect that even though the call to std::memcpy
is lowered into a simple store instruction, merely calling memcpy forces Clang to assume that global memory might have been modified and to reload mRight
from memory.
To be honest, I am not convinced this is the correct fix, but this is the only thing I have found so far that has made a difference and it is at least much better than an asm("")
. If anyone has suggestions, please let me know!
View the final diff
sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:171 | mRoot = insert(mRoot, node); | } | | template <typename Key> | TreeMapNode<Key>* TreeMapImpl<Key>::insert(Node* root, Node* node) | { a5dc80: ~> str x21, [sp, #-0x30]! 2a5f4: ~> str x21, [sp, #-0x30]! a5dc84: stp x20, x19, [sp, #0x10] 2a5f8: stp x20, x19, [sp, #0x10] a5dc88: stp x29, x30, [sp, #0x20] 2a5fc: stp x29, x30, [sp, #0x20] a5dc8c: add x29, sp, #0x20 2a600: add x29, sp, #0x20 a5dc90: mov x19, x2 2a604: mov x19, x2 a5dc94: mov x20, x1 2a608: mov x20, x1 a5dc98: mov x21, x0 2a60c: mov x21, x0 a5dc9c: cbz x20, a5dcd4 ~> 2a610: cbz x20, 2a648 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x54> ~> sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::key() const /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:48 | const Key& key() const { return mKey; } a5dca0: add x0, x19, #0x20 2a614: add x0, x19, #0x20 a5dca4: add x1, x20, #0x20 2a618: add x1, x20, #0x20 sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:179 (discriminator 2) | node->mLeft = node->mRight = nullptr; | node->mColor = Node::Color::Red; | return node; | } | | const s32 cmp = node->key().compare(root->key()); a5dca8: bl a5d4a8 ~> i 2a61c: bl 16f14 <_ZNK4sead14TreeMapKeyImplINS_14SafeStringBaseIcEEE7compareERKS3_@plt> ~> a5dcac: tbnz w0, #31, a5dce0 ~> 2a620: tbnz w0, #31, 2a654 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x60> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:185 | | if (cmp < 0) | { | root->mLeft = insert(root->mLeft, node); | } | else if (cmp > 0) a5dcb0: cbz w0, a5dd00 ~> 2a624: cbz w0, 2a674 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x80> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:187 | { | root->mRight = insert(root->mRight, node); a5dcb4: ldr x1, [x20, #0x10] 2a628: ldr x1, [x20, #0x10] a5dcb8: mov x0, x21 2a62c: mov x0, x21 a5dcbc: mov x2, x19 2a630: mov x2, x19 a5dcc0: bl a5dc80 ~> 2a634: bl 2a5f4 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_> ~> a5dcc4: str x0, [x20, #0x10] 2a638: str x0, [x20, #0x10] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:198 | node->mColor = root->mColor; | root->erase_(); | root = node; | } | | if (Node::isRed(root->mRight) && !Node::isRed(root->mLeft)) a5dcc8: ldr x8, [x20, #0x10] 2a63c: ldr x8, [x20, #0x10] a5dccc: cbnz x8, a5dd3c ~> 2a640: cbnz x8, 2a6b0 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xbc> ~> a5dcd0: b a5dd54 ~> 2a644: b 2a6c8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xd4> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:174 | node->mLeft = node->mRight = nullptr; a5dcd4: ~> stp xzr, xzr, [x19, #0x10] 2a648: ~> stp xzr, xzr, [x19, #0x10] a5dcd8: str xzr, [x19, #8] 2a64c: str xzr, [x19, #8] a5dcdc: b a5de1c ~> 2a650: b 2a790 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x19c> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:183 | root->mLeft = insert(root->mLeft, node); a5dce0: ~> ldr x1, [x20, #8] 2a654: ~> ldr x1, [x20, #8] a5dce4: mov x0, x21 2a658: mov x0, x21 a5dce8: mov x2, x19 2a65c: mov x2, x19 a5dcec: bl a5dc80 ~> 2a660: bl 2a5f4 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_> ~> a5dcf0: str x0, [x20, #8] 2a664: str x0, [x20, #8] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:198 | if (Node::isRed(root->mRight) && !Node::isRed(root->mLeft)) a5dcf4: ldr x8, [x20, #0x10] 2a668: ldr x8, [x20, #0x10] a5dcf8: cbnz x8, a5dd3c ~> 2a66c: cbnz x8, 2a6b0 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xbc> ~> a5dcfc: b a5dd54 ~> 2a670: b 2a6c8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xd4> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:189 | else if (root != node) a5dd00: ~> cmp x20, x19 2a674: ~> cmp x20, x19 a5dd04: b.eq a5dd34 ~> 2a678: b.eq 2a6a8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xb4> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:191 | node->mRight = root->mRight; a5dd08: ldr x8, [x20, #0x10] 2a67c: ldr x8, [x20, #0x10] a5dd0c: str x8, [x19, #0x10] 2a680: str x8, [x19, #0x10] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:192 | node->mLeft = root->mLeft; a5dd10: ldr x8, [x20, #8] 2a684: ldr x8, [x20, #8] a5dd14: str x8, [x19, #8] 2a688: str x8, [x19, #8] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:193 | node->mColor = root->mColor; a5dd18: ldr x8, [x20, #0x18] 2a68c: ldr x8, [x20, #0x18] a5dd1c: str x8, [x19, #0x18] 2a690: str x8, [x19, #0x18] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:194 | root->erase_(); a5dd20: ldr x8, [x20] 2a694: ldr x8, [x20] a5dd24: ldr x8, [x8, #0x10] 2a698: ldr x8, [x8, #0x10] a5dd28: mov x0, x20 2a69c: mov x0, x20 a5dd2c: blr x8 2a6a0: blr x8 a5dd30: mov x20, x19 2a6a4: mov x20, x19 /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:198 | if (Node::isRed(root->mRight) && !Node::isRed(root->mLeft)) a5dd34: ~> ldr x8, [x20, #0x10] 2a6a8: ~> ldr x8, [x20, #0x10] a5dd38: cbz x8, a5dd54 ~> 2a6ac: cbz x8, 2a6c8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xd4> ~> a5dd3c: ~> ldrb w9, [x8, #0x18] 2a6b0: ~> ldrb w9, [x8, #0x18] a5dd40: tbnz w9, #0, a5dd54 ~> 2a6b4: tbnz w9, #0, 2a6c8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xd4> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:198 (discriminator 1) a5dd44: ldr x9, [x20, #8] 2a6b8: ldr x9, [x20, #8] a5dd48: cbz x9, a5dd64 ~> 2a6bc: cbz x9, 2a6d8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xe4> ~> a5dd4c: ldrb w9, [x9, #0x18] 2a6c0: ldrb w9, [x9, #0x18] a5dd50: tbnz w9, #0, a5dd64 ~> 2a6c4: tbnz w9, #0, 2a6d8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0xe4> ~> a5dd54: ~> mov x8, x20 2a6c8: ~> mov x8, x20 a5dd58: ldr x19, [x8, #8] 2a6cc: ldr x19, [x8, #8] a5dd5c: cbnz x19, a5dd84 ~> 2a6d0: cbnz x19, 2a6f8 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x104> ~> a5dd60: b a5de18 ~> 2a6d4: b 2a78c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x198> ~> sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::rotateLeft() /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:145 | mRight = j->mLeft; a5dd64: ~> ldr x9, [x8, #8] 2a6d8: ~> ldr x9, [x8, #8] a5dd68: str x9, [x20, #0x10] 2a6dc: str x9, [x20, #0x10] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:146 | j->mLeft = this; a5dd6c: str x20, [x8, #8] 2a6e0: str x20, [x8, #8] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:147 | j->mColor = mColor; a5dd70: ldr x9, [x20, #0x18] 2a6e4: ldr x9, [x20, #0x18] a5dd74: str x9, [x8, #0x18] 2a6e8: str x9, [x8, #0x18] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:148 | mColor = Color::Red; a5dd78: str xzr, [x20, #0x18] 2a6ec: str xzr, [x20, #0x18] sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:201 | root = root->rotateLeft(); | | if (Node::isRed(root->mLeft) && Node::isRed(root->mLeft->mLeft)) a5dd7c: ldr x19, [x8, #8] 2a6f0: ldr x19, [x8, #8] a5dd80: cbz x19, a5de18 ~> 2a6f4: cbz x19, 2a78c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x198> ~> a5dd84: ~> ldrb w9, [x19, #0x18] 2a6f8: ~> ldrb w9, [x19, #0x18] a5dd88: tbnz w9, #0, a5ddc4 ~> 2a6fc: tbnz w9, #0, 2a738 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x144> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:201 (discriminator 1) a5dd8c: ldr x9, [x19, #8] 2a700: ldr x9, [x19, #8] a5dd90: cbz x9, a5ddc4 ~> 2a704: cbz x9, 2a738 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x144> ~> a5dd94: ldrb w9, [x9, #0x18] 2a708: ldrb w9, [x9, #0x18] a5dd98: tbnz w9, #0, a5ddd4 ~> 2a70c: tbnz w9, #0, 2a748 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x154> ~> sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::rotateRight() /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:156 | mLeft = j->mRight; a5dd9c: ldr x9, [x19, #0x10] 2a710: ldr x9, [x19, #0x10] a5dda0: str x9, [x8, #8] 2a714: str x9, [x8, #8] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:157 | j->mRight = this; a5dda4: str x8, [x19, #0x10] 2a718: str x8, [x19, #0x10] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:158 | j->mColor = mColor; a5dda8: ldr x9, [x8, #0x18] 2a71c: ldr x9, [x8, #0x18] a5ddac: str x9, [x19, #0x18] 2a720: str x9, [x19, #0x18] /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:159 | mColor = Color::Red; a5ddb0: str xzr, [x8, #0x18] 2a724: str xzr, [x8, #0x18] a5ddb4: ldr x9, [x19, #8] 2a728: ldr x9, [x19, #8] sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) a5ddb8: mov x8, x19 2a72c: mov x8, x19 a5ddbc: cbnz x9, a5ddd8 ~> 2a730: cbnz x9, 2a74c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x158> ~> a5ddc0: b a5de1c ~> 2a734: b 2a790 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x19c> ~> a5ddc4: ~> mov x9, x19 2a738: ~> mov x9, x19 a5ddc8: ldrb w10, [x9, #0x18] 2a73c: ldrb w10, [x9, #0x18] a5ddcc: tbz w10, #0, a5dde0 ~> 2a740: tbz w10, #0, 2a754 <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x160> ~> a5ddd0: b a5de18 ~> 2a744: b 2a78c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x198> ~> a5ddd4: ~> mov x9, x19 2a748: ~> mov x9, x19 /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:204 | root = root->rotateRight(); | | if (Node::isRed(root->mLeft) && Node::isRed(root->mRight)) a5ddd8: ~> ldrb w10, [x9, #0x18] 2a74c: ~> ldrb w10, [x9, #0x18] a5dddc: tbnz w10, #0, a5de18 ~> 2a750: tbnz w10, #0, 2a78c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x198> ~> /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:204 (discriminator 1) a5dde0: ~> ldr x10, [x8, #0x10] 2a754: ~> ldr x10, [x8, #0x10] a5dde4: cbz x10, a5de18 ~> 2a758: cbz x10, 2a78c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x198> ~> a5dde8: ldrb w10, [x10, #0x18] 2a75c: ldrb w10, [x10, #0x18] a5ddec: tbnz w10, #0, a5de18 ~> 2a760: tbnz w10, #0, 2a78c <_ZN4sead11TreeMapImplINS_14TreeMapKeyImplINS_14SafeStringBaseIcEEEEE6insertEPNS_11TreeMapNodeIS4_EES8_+0x198> ~> sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::flipColor() /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:62 | void flipColor() { BitUtil::bitCastWrite(u64(mColor) ^ 1u, &mColor); } a5ddf0: ldr x10, [x8, #0x18] 2a764: ldr x10, [x8, #0x18] a5ddf4: eor x10, x10, #0x1 2a768: eor x10, x10, #0x1 void sead::BitUtil::bitCastWrite<sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::Color, unsigned long>(unsigned long const&, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::Color*) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/prim/seadBitUtil.h:45 | // Nintendo appears to perform type punning, but we care about UB. | template <typename To, typename From> | inline void bitCastWrite(const From& value, To* ptr) | { | static_assert(sizeof(To) == sizeof(From), "To and From must have the same size"); | std::memcpy(ptr, &value, sizeof(To)); a5ddf8: str x10, [x8, #0x18] 2a76c: str x10, [x8, #0x18] sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::flipColor() /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:62 a5ddfc: ldr x10, [x9, #0x18] 2a770: ldr x10, [x9, #0x18] a5de00: eor x10, x10, #0x1 2a774: eor x10, x10, #0x1 void sead::BitUtil::bitCastWrite<sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::Color, unsigned long>(unsigned long const&, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::Color*) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/prim/seadBitUtil.h:45 a5de04: str x10, [x9, #0x18] 2a778: str x10, [x9, #0x18] sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:208 | { | root->flipColor(); | root->mLeft->flipColor(); | root->mRight->flipColor(); a5de08: ldr x9, [x8, #0x10] 2a77c: ldr x9, [x8, #0x10] sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::flipColor() /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:62 | void flipColor() { BitUtil::bitCastWrite(u64(mColor) ^ 1u, &mColor); } a5de0c: ldr x10, [x9, #0x18] 2a780: ldr x10, [x9, #0x18] a5de10: eor x10, x10, #0x1 2a784: eor x10, x10, #0x1 void sead::BitUtil::bitCastWrite<sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::Color, unsigned long>(unsigned long const&, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::Color*) /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/prim/seadBitUtil.h:45 a5de14: str x10, [x9, #0x18] 2a788: str x10, [x9, #0x18] sead::TreeMapImpl<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >::insert(sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*, sead::TreeMapNode<sead::TreeMapKeyImpl<sead::SafeStringBase<char> > >*) a5de18: ~> mov x19, x8 2a78c: ~> mov x19, x8 /home/leo/projects/uking/build/nx64-develop/../../lib/sead/include/container/seadTreeMap.h:212 (discriminator 1) | } | | return root; | } a5de1c: ~> mov x0, x19 2a790: ~> mov x0, x19 a5de20: ldp x29, x30, [sp, #0x20] 2a794: ldp x29, x30, [sp, #0x20] a5de24: ldp x20, x19, [sp, #0x10] 2a798: ldp x20, x19, [sp, #0x10] a5de28: ldr x21, [sp], #0x30 2a79c: ldr x21, [sp], #0x30 a5de2c: ret 2a7a0: ret
-
See github.com/zeldaret/oot for an example. ↩︎
-
Code that perfectly matches the original assembly. Non-matching code is usually semantically equivalent to the original code but differs in register allocation, instruction ordering, etc. ↩︎
-
All known available versions of BotW are stripped, but it is possible to recover some symbols by diffing BotW and an unstripped game that uses sead (e.g. Super Mario Odyssey, Splatoon 2 or even Nintendo Labo for a debug build). That isn’t perfect, but it’s better than nothing. ↩︎