#include "d/d_tag_processor.h"

#include "common.h"
#include "d/a/d_a_base.h"
#include "d/d_font_manager.h"
#include "d/d_lyt_hio.h"
#include "d/d_message.h"
#include "d/d_textwindow_unk.h"
#include "d/lyt/d_lyt_util_items.h"
#include "d/lyt/d_textbox.h"
#include "d/lyt/msg_window/d_lyt_msg_window.h"
#include "d/snd/d_snd_player_mgr.h"
#include "f/f_base.h"
#include "f/f_manager.h"
#include "f/f_profile_name.h"
#include "m/m_color.h"
#include "nw4r/lyt/lyt_types.h"
#include "nw4r/ut/ut_CharWriter.h"
#include "nw4r/ut/ut_Color.h"
#include "nw4r/ut/ut_Font.h"
#include "nw4r/ut/ut_Rect.h"
#include "nw4r/ut/ut_TagProcessorBase.h"
#include "nw4r/ut/ut_TextWriterBase.h"
#include "sized_string.h"
#include "toBeSorted/file_manager.h"

#include <libc.h>


#define READ_U8(src, off) (*((const u8 *)((const u8 *)src + off)))
#define READ_S8(src, off) (*((const s8 *)((const u8 *)src + off)))
#define READ_U16(src, off) (*((const u16 *)((const u8 *)src + off)))
#define READ_S16(src, off) (*((const s16 *)((const u8 *)src + off)))
#define READ_U32(src, off) (*((const u32 *)((const u8 *)src + off)))

// picture_00.brfnt
enum PictureFontIdx {
    FONT_BTN_A = 0,
    FONT_BTN_B = 1,
    FONT_BTN_MINUS = 2,
    FONT_BTN_PLUS = 3,
    FONT_BTN_1 = 4,
    FONT_BTN_2 = 5,
    FONT_BTN_C = 6,
    FONT_BTN_Z = 7,
    FONT_BTN_NUN_STK = 8,
    FONT_BTN_NUN_STK_UP = 9,
    FONT_BTN_NUN_STK_DOWN = 10,
    FONT_BTN_NUN_STK_LEFT = 11,
    FONT_BTN_NUN_STK_RIGHT = 12,
    FONT_BTN_NUN_STK_WIGGLE_VERTICAL = 13,
    FONT_BTN_NUN_STK_WIGGLE_HORIZONTAL = 14,
    FONT_BTN_CROSS_FULL = 15,
    FONT_BTN_CROSS_UP = 16,
    FONT_BTN_CROSS_DOWN = 17,
    FONT_BTN_CORSS_LEFT = 18,
    FONT_BTN_CROSS_RIGHT = 19,
    FONT_ARROW_UP = 20,
    FONT_ARROW_DOWN = 21,
    FONT_ARROW_LEFT = 22,
    FONT_ARROW_RIGHT = 23,
    FONT_CURSOR = 24,
    FONT_X = 25,
    FONT_HOLE = 26,
};

nw4r::ut::Color FontColors1[] = {
    nw4r::ut::Color(0xff, 0x4b, 0x32, 0xff), // #ff4b32ff
    nw4r::ut::Color(0xff, 0x4b, 0x32, 0xff), // #ff4b32ff
    nw4r::ut::Color(0xfa, 0xaf, 0x05, 0xff), // #faaf05ff
    nw4r::ut::Color(0xdc, 0xf5, 0xfa, 0xff), // #dcf5faff
    nw4r::ut::Color(0x91, 0xff, 0x6e, 0xff), // #91ff6eff
    nw4r::ut::Color(0xff, 0xbe, 0x1e, 0xff), // #ffbe1eff
    nw4r::ut::Color(0xeb, 0xc8, 0xff, 0xff), // #ebc8ffff
    nw4r::ut::Color(0x14, 0xd7, 0x46, 0xff), // #14d746ff
    nw4r::ut::Color(0x19, 0xa5, 0xeb, 0xff), // #19a5ebff
    nw4r::ut::Color(0xf0, 0x3c, 0x1e, 0xff), // #f03c1eff
    nw4r::ut::Color(0xc8, 0xc8, 0xc8, 0xff), // #c8c8c8ff
    nw4r::ut::Color(0xfa, 0xaf, 0x05, 0xff), // #faaf05ff
    nw4r::ut::Color(0x32, 0x23, 0x46, 0xff), // #322346ff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(0xf6, 0xf6, 0xde, 0xff), // #f6f6deff
    nw4r::ut::Color(0xe6, 0xe6, 0xa0, 0xff), // #e6e6a0ff
    nw4r::ut::Color(0xe8, 0xad, 0x55, 0xff), // #e8ad55ff
    nw4r::ut::Color(0xff, 0xfa, 0xe6, 0xff), // #fffae6ff
    nw4r::ut::Color(0xf5, 0xf2, 0xeb, 0xff), // #f5f2ebff
    nw4r::ut::Color(0x64, 0x5f, 0x69, 0xff), // #645f69ff
    nw4r::ut::Color(0x5e, 0x51, 0x46, 0xff), // #5e5146ff
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xfa, 0xf6, 0xe7, 0xff), // #faf6e7ff
    nw4r::ut::Color(0xff, 0xff, 0xff, 0xff), // #ffffffff
    nw4r::ut::Color(0x4c, 0x32, 0x0f, 0xff), // #4c320fff
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xf6, 0xf2, 0xea, 0xff), // #f6f2eaff
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(0xff, 0xff, 0xff, 0xff), // #ffffffff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xed, 0xe2, 0xd0, 0xff), // #ede2d0ff
    nw4r::ut::Color(0x20, 0x20, 0x20, 0xff), // #202020ff
    nw4r::ut::Color(0x74, 0x6d, 0x5c, 0xff), // #746d5cff
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(),
};

nw4r::ut::Color FontColors2[] = {
    nw4r::ut::Color(0xff, 0x4b, 0x32, 0xff), // #ff4b32ff
    nw4r::ut::Color(0xcd, 0x4b, 0x32, 0xff), // #cd4b32ff
    nw4r::ut::Color(0xff, 0xe6, 0x8c, 0xff), // #ffe68cff
    nw4r::ut::Color(0x7d, 0xc3, 0xff, 0xff), // #7dc3ffff
    nw4r::ut::Color(0x64, 0xc8, 0x41, 0xff), // #64c841ff
    nw4r::ut::Color(0xff, 0xbe, 0x1e, 0xff), // #ffbe1eff
    nw4r::ut::Color(0x91, 0x5f, 0xff, 0xff), // #915fffff
    nw4r::ut::Color(0xaf, 0xe6, 0x2d, 0xff), // #afe62dff
    nw4r::ut::Color(0xb4, 0xe6, 0xff, 0xff), // #b4e6ffff
    nw4r::ut::Color(0xff, 0x91, 0x78, 0xff), // #ff9178ff
    nw4r::ut::Color(0xe6, 0xe6, 0xc8, 0xff), // #e6e6c8ff
    nw4r::ut::Color(0xff, 0xe6, 0x8c, 0xff), // #ffe68cff
    nw4r::ut::Color(0x50, 0x4b, 0x5f, 0xff), // #504b5fff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(0xe7, 0xd7, 0xed, 0xff), // #e7d7edff
    nw4r::ut::Color(0xa5, 0x96, 0x6e, 0xff), // #a5966eff
    nw4r::ut::Color(0xb5, 0x52, 0x2e, 0xff), // #b5522eff
    nw4r::ut::Color(0xff, 0xfa, 0xe6, 0xff), // #fffae6ff
    nw4r::ut::Color(0xe1, 0xc8, 0x9b, 0xff), // #e1c89bff
    nw4r::ut::Color(0x55, 0x50, 0x5a, 0xff), // #55505aff
    nw4r::ut::Color(0x35, 0x2d, 0x27, 0xff), // #352d27ff
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xe3, 0xcc, 0x8f, 0xff), // #e3cc8fff
    nw4r::ut::Color(0xff, 0xff, 0xff, 0xff), // #ffffffff
    nw4r::ut::Color(0x3a, 0x25, 0x09, 0xff), // #3a2509ff
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xe1, 0xc7, 0x9b, 0xff), // #e1c79bff
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(0xff, 0xff, 0xff, 0xff), // #ffffffff
    nw4r::ut::Color(),
    nw4r::ut::Color(0xca, 0xaf, 0x81, 0xff), // #caaf81ff
    nw4r::ut::Color(0x20, 0x20, 0x20, 0xff), // #202020ff
    nw4r::ut::Color(0x4b, 0x47, 0x3f, 0xff), // #4b473fff
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(),
    nw4r::ut::Color(),
};

static const wchar_t replacementSeq0xDF[] = {0x53, 0x53};

struct ReplacementSequence {
    /* 0x00 */ wchar_t origChar;
    /* 0x02 */ u8 replacementSequenceLen;
    /* 0x04 */ const wchar_t *replacementSequence;
};

static const ReplacementSequence sReplacementSequences[] = {
    {0xDF, 2, replacementSeq0xDF},
    {   0, 0,            nullptr}
};

static const wchar_t sUpperCaseChars[] = {
    L'A',   L'B',   L'C',   L'D',   L'E',   L'F',   L'G',   L'H',   L'I',   L'J',   L'K',   L'L',   L'M',   L'N',
    L'O',   L'P',   L'Q',   L'R',   L'S',   L'T',   L'U',   L'V',   L'W',   L'X',   L'Y',   L'Z',   0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    0x0000, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC,
    0x00CD, 0x00CE, 0x00CF, 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0000, 0x00D8, 0x00D9, 0x00DA,
    0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x0178, 0x0000, 0x0100, 0x0000, 0x0102, 0x0000, 0x0104, 0x0000, 0x0106, 0x0000,
    0x0108, 0x0000, 0x010A, 0x0000, 0x010C, 0x0000, 0x010E, 0x0000, 0x0110, 0x0000, 0x0112, 0x0000, 0x0114, 0x0000,
    0x0116, 0x0000, 0x0118, 0x0000, 0x011A, 0x0000, 0x011C, 0x0000, 0x011E, 0x0000, 0x0120, 0x0000, 0x0122, 0x0000,
    0x0124, 0x0000, 0x0126, 0x0000, 0x0128, 0x0000, 0x012A, 0x0000, 0x012C, 0x0000, 0x012E, 0x0000, 0x0049, 0x0000,
    0x0132, 0x0000, 0x0134, 0x0000, 0x0136, 0x0000, 0x0000, 0x0139, 0x0000, 0x013B, 0x0000, 0x013D, 0x0000, 0x013F,
    0x0000, 0x0141, 0x0000, 0x0143, 0x0000, 0x0145, 0x0000, 0x0147, 0x0000, 0x0000, 0x014A, 0x0000, 0x014C, 0x0000,
    0x014E, 0x0000, 0x0150, 0x0000, 0x0152, 0x0000, 0x0154, 0x0000, 0x0156, 0x0000, 0x0158, 0x0000, 0x015A, 0x0000,
    0x015C, 0x0000, 0x015E, 0x0000, 0x0160, 0x0000, 0x0162, 0x0000, 0x0164, 0x0000, 0x0166, 0x0000, 0x0168, 0x0000,
    0x016A, 0x0000, 0x016C, 0x0000, 0x016E, 0x0000, 0x0170, 0x0000, 0x0172, 0x0000, 0x0174, 0x0000, 0x0176, 0x0000,
    0x0000, 0x0179, 0x0000, 0x017B, 0x0000, 0x017D, 0x0053, 0x0000,
};

s32 toUpperCase(wchar_t orig, wchar_t *out, u32 maxLen) {
    if (maxLen < 3) {
        return -1;
    }

    if (orig >= L'a' && orig <= 383) {
        const ReplacementSequence *seq = sReplacementSequences;
        s32 len = seq->replacementSequenceLen;
        while (true) {
            if (seq->replacementSequenceLen == 0) {
                break;
            }
            if (seq->origChar == orig) {
                s32 len = seq->replacementSequenceLen;
                const wchar_t *r = seq->replacementSequence;
                s32 i = seq->replacementSequenceLen;
                do {
                    *out = *r;
                    out++;
                    r++;
                } while (--i);
                *out = L'\0';
                return len;
            }
            seq++;
        }
        u32 ret = 1;
        wchar_t replacement = sUpperCaseChars[orig - L'a'];
        if (replacement == L'\0') {
            replacement = orig;
            ret = 0;
        }
        out[0] = replacement;
        out[1] = L'\0';
        return ret;
    }
    out[0] = orig;
    out[1] = L'\0';
    return 0;
}

const u16 sMsgWindowFlags[2050] = {
    0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800,
};

dTagProcessor_c::dTagProcessor_c() {
    field_0x82C = -1;
    mCancelBtnIdx = -1;
    mCurrentOptionIdx = 0;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 256; j++) {
            mOptionBufs[i][j] = 0;
        }
        mOptionLengths[i] = 0;
        mOptionSound[i] = 0;
    }

    field_0x904 = 0.0f;
    field_0x908 = 0.0f;
    resetSomething();
    field_0x830 = -1;
    mPauseFramesLeft = 0;
    field_0x838 = 0;
    field_0x83C = 0;
    field_0x840 = 0;
    field_0x844 = 0;
    field_0x848 = 0;
    field_0x84C = 0;
    field_0x850 = 0;
    field_0x87C = 0;
    field_0x854 = 0;
    field_0x858 = 0;
    field_0x85C = 0;
    field_0x860 = 0;
    field_0x880 = 0;
    field_0x864 = 0;
    field_0x868 = 0;
    field_0x884 = 0;
    field_0x86C = 0;
    field_0x870 = 0;
    field_0x894 = 0;
    field_0x874 = 0;
    field_0x878 = 0xffffffff;
    field_0x898 = 0;
    field_0x888 = 0;
    field_0x88C = 0;
    field_0x824 = 0;
    field_0x890 = 0;
    field_0x8FC = -1;
    field_0x900 = -1;
    resetLineData();
    field_0x81C = 0;
    field_0x820 = 0;
    setNumericArg0(0);
    mButtonPressAnimTimer = 0;
    mButtonWiggleAnimTimer = 0;
    mFirstNumericArg = 0;

    mNumericArgsCopy[0] = 0;
    mNumericArgsCopy[1] = 0;
    mNumericArgsCopy[2] = 0;
    mNumericArgsCopy[3] = 0;
    mNumericArgsCopy[4] = 0;
    mNumericArgsCopy[5] = 0;
    mNumericArgsCopy[6] = 0;
    mNumericArgsCopy[7] = 0;
    mNumericArgsCopy[8] = 0;
    mNumericArgsCopy[9] = 0;
    mNumericArgsCopy[10] = 0;

    field_0xEE5 = 0;
    field_0xEE6 = 0;
    field_0xEE7 = 0;
    field_0xEE8 = 0;
    field_0xEE9 = 0;
    field_0xEEA = 0;
    field_0xEEB = 0;
    field_0xEEC = 0;
    field_0xEED = 0;
    field_0xEEE = 0;
    field_0xEEF = 0;
    field_0xEE0 = 0;
    mIsShadowText = false;
    field_0xEE2 = 0;
    field_0xEE3 = 0;
    field_0xEE4 = 0;
    mpTextBox = nullptr;
    mMsgWindowSubtype = dLytMsgWindow_c::MSG_WINDOW_36;
    field_0x90D = 4;
    field_0xEF0 = 0;
    field_0xEF1 = 0;
}

dTagProcessor_c::~dTagProcessor_c() {}

struct StackThing {
    wchar_t us[4];
};

void dTagProcessor_c::format(
    dTextBox_c *textBox, const wchar_t *src, wchar_t *dest, u32 destLen, u32 *pOutLen, void *unk, ...
) {
    va_list l;
    va_start(l, str);
    formatV(textBox, src, dest, destLen, pOutLen, unk, l);
    va_end(l);
}

void dTagProcessor_c::formatV(
    dTextBox_c *textBox, const wchar_t *src, wchar_t *dest, u32 destLen, u32 *pOutLen, void *unk, va_list list
) {
    s32 optionIdx = 0;
    bool isProcessingOption = false;

    s32 state1 = -1;
    s32 cancelBtnIdx = -1;

    f32 currScale, backupScale;
    backupScale = currScale = fn_800B8040(0, mMsgWindowSubtype);

    if (textBox != nullptr) {
        currScale *= textBox->getMyScale();
        resetLineData();
        textBox->set0x1F8(0);
    }

    s32 optionLength = 0;

    // 0xE TAG_CMD_0x0F0F0F0F 0x00 0x02
    StackThing x = {0x000E, 0x0F0F, 0x0F0F, 0x0002};
    wchar_t *writePtr = dest;
    if (textBox != nullptr) {
        writePtr += 5;
        dest[0] = x.us[0];
        dest[1] = x.us[1];
        dest[2] = x.us[2];
        dest[3] = x.us[3];
        dest[4] = mLineData.mNumLines;
    }

    // 0xE TAG_CMD_0x0F0F0F0E 0x00 0x01
    StackThing yTmp;
    StackThing y = {0x000E, 0x0F0F, 0x0F0E, 0x0001};

    do {
        wchar_t c = *src;
        if (c == nullptr) {
            if (textBox != nullptr) {
                mLineData.mNumLines++;
            }
            *writePtr = '\0';
            goto end;
        }

        if (c == 0xE) {
            u8 cmdLen = 0;
            s32 cmd = 0;
            wchar_t *endPtr = nullptr;
            getTextCommand(c, src + 1, &cmdLen, &cmd, &endPtr);
            bool processingOption = false;
            switch (cmd) {
                case TAG_CMD_0x0F0F0F0F:
                    if (isProcessingOption && mCurrentOptionIdx != 0) {
                        const wchar_t *t = src;
                        u32 len = (cmdLen / 2) + 1;
                        for (int i = 0; i < len; i++) {
                            getOptionBuf()[optionLength] = *(t++);
                            onWriteOptionBuf();
                            optionLength++;
                        }
                    } else {
                        const wchar_t *t = src;
                        u32 len = (cmdLen / 2) + 1;
                        for (int i = 0; i < len; i++) {
                            *(writePtr++) = *(t++);
                        }
                    }
                    break;
                case TAG_CMD_OPTION_0: {
                    u8 a = READ_U8(endPtr, 0);
                    u8 b = READ_U8(endPtr, 1);
                    switch (a) {
                        case 0: cancelBtnIdx = 0; break;
                        case 1: state1 = 0; break;
                    }
                    mOptionSound[0] = b;
                    yTmp = y;
                    for (int i = 0; i < 4; i++) {
                        for (int j = 0; j < 256; j++) {
                            if (j < 4) {
                                mOptionBufs[i][j] = yTmp.us[j];
                            } else {
                                mOptionBufs[i][j] = 0;
                            }
                        }
                        mOptionLengths[i] = 4;
                    }
                    optionIdx = 1;
                    processingOption = true;
                } break;
                case TAG_CMD_OPTION_1: {
                    u8 a = READ_U8(endPtr, 0);
                    u8 b = READ_U8(endPtr, 1);
                    switch (a) {
                        case 0: cancelBtnIdx = 1; break;
                        case 1: state1 = 1; break;
                    }
                    mOptionSound[1] = b;
                    optionIdx = 2;
                    processingOption = true;
                } break;
                case TAG_CMD_OPTION_2: {
                    u8 a = READ_U8(endPtr, 0);
                    u8 b = READ_U8(endPtr, 1);
                    switch (a) {
                        case 0: cancelBtnIdx = 2; break;
                        case 1: state1 = 2; break;
                    }
                    mOptionSound[2] = b;
                    optionIdx = 3;
                    processingOption = true;
                } break;
                case TAG_CMD_OPTION_3: {
                    u8 a = READ_U8(endPtr, 0);
                    u8 b = READ_U8(endPtr, 1);
                    switch (a) {
                        case 0: cancelBtnIdx = 3; break;
                        case 1: state1 = 3; break;
                    }
                    mOptionSound[3] = b;
                    optionIdx = 4;
                    processingOption = true;
                } break;
                case 0x10008:
                    if (textBox != nullptr) {
                        currScale = fn_800B8040(READ_U8(endPtr, 0), mMsgWindowSubtype);
                        currScale *= textBox->getMyScale();
                    }
                    writePtr = writeTextNormal(src, writePtr, &optionLength, cmdLen, isProcessingOption);
                    break;
                case 0x30000: {
                    if (textBox != nullptr) {
                        backupScale = currScale;
                        currScale *= dLyt_HIO_c::getField0x768();
                        currScale *= textBox->getMyScale();
                    }
                    writePtr = writeTextNormal(src, writePtr, &optionLength, cmdLen, isProcessingOption);
                } break;
                case 0x10010: fn_800B5520(endPtr); break;
                case 0x20004:
                    if (textBox != nullptr) {
                        saveIconWidth(textBox, endPtr, currScale);
                    }
                    writePtr = writeTextNormal(src, writePtr, &optionLength, cmdLen, isProcessingOption);
                    break;
                case TAG_CMD_WRITE_HERONAME:
                    writePtr = writeHeroname(writePtr, &optionLength, isProcessingOption);
                    break;
                case TAG_CMD_WRITE_ITEM:
                    writePtr = writeItem(writePtr, endPtr, &optionLength, isProcessingOption);
                    break;
                case TAG_CMD_WRITE_STRING_ARG:
                    writePtr = writeStringArg(writePtr, endPtr, &optionLength, isProcessingOption);
                    break;
                case TAG_CMD_WRITE_NUMERIC_ARG:
                    writePtr = writeNumericArg(writePtr, endPtr, &optionLength, isProcessingOption);
                    break;

                case TAG_CMD_WRITE_WORD:
                    writePtr = writeSingularOrPluralWord(writePtr, endPtr, &optionLength, isProcessingOption);
                    break;
                case 0x30001:
                    field_0xEF1 = 1;
                    writePtr = writeTextNormal(src, writePtr, &optionLength, cmdLen, isProcessingOption);
                    break;

                default: writePtr = writeTextNormal(src, writePtr, &optionLength, cmdLen, isProcessingOption); break;
            }

            if (processingOption) {
                field_0x82C = state1;
                isProcessingOption = 1;
                mCancelBtnIdx = cancelBtnIdx;
                mCurrentOptionIdx = optionIdx;
                optionLength = mOptionLengths[optionIdx - 1];
            }
            src += (cmdLen / 2) + 1;
        } else if (c == 0xF) {
            s32 tmp = 0;
            process0xFCommand(c, src + 1, &tmp);
            if (tmp == 0x30000) {
                currScale = backupScale;
            }
            writePtr[0] = src[0];
            writePtr[1] = src[1];
            writePtr[2] = src[2];
            writePtr += 3;
            src += 3;
        } else if (isProcessingOption != 0 && mCurrentOptionIdx != 0) {
            // Note: Return ignored here
            writeSingleCharacter(c, &getOptionBuf()[optionLength], &optionLength);
            src++;
            onWriteOptionBuf();
        } else {
            if (textBox != nullptr) {
                if (c == L'\n') {
                    *writePtr = c;
                    src++;
                    writePtr++;
                    mLineData.mNumLines++;
                    s32 i10 = getMaxNumLines(mMsgWindowSubtype);
                    if (mLineData.mNumLines % i10 == 0) {
                        currScale = fn_800B8040(0, mMsgWindowSubtype);
                        currScale *= textBox->getMyScale();
                    }
                    if (textBox != nullptr) {
                        wchar_t *buf = (wchar_t *)&x;
                        writePtr[0] = buf[0];
                        writePtr[1] = buf[1];
                        writePtr[2] = buf[2];
                        writePtr[3] = buf[3];
                        writePtr[4] = mLineData.mNumLines;
                        writePtr += 5;
                    }
                } else {
                    const nw4r::ut::Font *fnt = textBox->GetFont();
                    mLineData.mLineWidths[mLineData.mNumLines] +=
                        currScale * fnt->GetCharWidth(*src) + textBox->GetCharSpace();
                    writePtr = writeSingleCharacter(*src, writePtr, nullptr);
                    src++;
                }

            } else {
                writePtr = writeSingleCharacter(c, writePtr, nullptr);
                src++;
            }
        }

    } while (destLen > writePtr - dest);

    dest[destLen - 1] = 0;
end:
    if (pOutLen != nullptr) {
        *pOutLen = writePtr - dest;
    }
}

void dTagProcessor_c::computeCharacterPlacement(
    dTextBox_c *textBox, const wchar_t *src_, wchar_t *dest, s32 unkArg, u16 *pOutLen, dLytMsgWindowCharData *charData
) {
    const wchar_t *src = src_;
    wchar_t *writePtr = dest;
    bool b1 = false;
    s32 lineNum = 0;
    bool b2 = false;
    s32 charDataIdx;

    f32 currScale = fn_800B8040(0, mMsgWindowSubtype);
    f32 backupScale = currScale;

    charDataIdx = 0;

    f32 posX = 0.0f;
    f32 posY = 0.0f;

    // Float order
    1.0f;

    if (textBox != nullptr) {
        currScale *= textBox->getMyScale();
    }

beginning:
    bool r22 = !b1;
    while (!b1) {
        wchar_t c = *src;
        if (c == '\0') {
            *writePtr = '\0';
            break;
        } else if (c == 0xE) {
            u8 cmdLen = 0;
            s32 cmd = 0;
            wchar_t *endPtr = nullptr;
            getTextCommand(c, src + 1, &cmdLen, &cmd, &endPtr);
            switch (cmd) {
                case TAG_CMD_0x0F0F0F0F: {
                    if (lineNum / getMaxNumLines(mMsgWindowSubtype) == unkArg) {
                        nw4r::lyt::Size fontSize = getTextBox()->GetFontSize();
                        posX = getMarginForCenteredLine(lineNum);
                        if ((mMsgWindowSubtype < dLytMsgWindow_c::MSG_WINDOW_WOOD ||
                             mMsgWindowSubtype >= dLytMsgWindow_c::MSG_WINDOW_LINK + 1) &&
                            mMsgWindowSubtype != dLytMsgWindow_c::MSG_WINDOW_DEMO) {
                            posX = 0.0f;
                        }

                        f32 tmp3;
                        f32 tmp = fn_800B8040(0, mMsgWindowSubtype);

                        if (textBox != nullptr) {
                            tmp *= textBox->getMyScale();
                        }

                        if (lineNum % getMaxNumLines(mMsgWindowSubtype) == 0) {
                            currScale = fn_800B8040(0, mMsgWindowSubtype);
                            if (textBox != nullptr) {
                                currScale *= textBox->getMyScale();
                            }
                            if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_DEMO) {
                                tmp3 = -2.0f;
                            } else {
                                tmp3 = 3.0f;
                            }
                            posY = (fn_800B85C0(lineNum) - (fontSize.height * (currScale / tmp) / 2.0f)) - tmp3;
                        } else {
                            posY = (posY - (fontSize.height * (currScale / tmp) + getTextBox()->GetLineSpace()));
                        }
                        if (!b2) {
                            b2 = true;
                        }
                    } else if (b2) {
                        b2 = false;
                        b1 = true;
                    }
                    lineNum++;
                } break;
                case TAG_CMD_OPTION_0:
                case TAG_CMD_OPTION_1:
                case TAG_CMD_OPTION_2:
                case TAG_CMD_OPTION_3: b1 = true; break;
                case 0x10008:          {
                    if (b2) {
                        f32 newScale = fn_800B8040(READ_U8(endPtr, 0), mMsgWindowSubtype);
                        f32 baseScale = fn_800B8040(0, mMsgWindowSubtype);
                        if (textBox != nullptr) {
                            newScale *= textBox->getMyScale();
                            baseScale *= textBox->getMyScale();
                        }
                        if (currScale != newScale) {
                            nw4r::lyt::Size fontSize = mpTextBox->GetFontSize();
                            posY -= (fontSize.height * ((currScale - newScale) / baseScale) / 2.0f) *
                                    dLyt_HIO_c::getField0x788();
                            currScale = newScale;
                        }
                    }
                } break;
                case 0x30000: {
                    if (b2) {
                        backupScale = currScale;
                        f32 tmp = backupScale * dLyt_HIO_c::getField0x768();
                        if (textBox != nullptr) {
                            tmp *= textBox->getMyScale();
                        }
                        if (currScale != tmp) {
                            nw4r::lyt::Size _size = mpTextBox->GetFontSize();
                            currScale = tmp;
                        }
                    }
                } break;
                case 0x20004:
                    if (b2) {
                        *writePtr = -1;
                        const nw4r::ut::Font *fnt = dFontMng_c::getFont(dFontMng_c::PICTURE);
                        s8 c = getAnimatedIconReplacement(READ_U8(endPtr, 0));
                        f32 tmp = dLyt_HIO_c::getField0x764();
                        f32 wid = tmp / fnt->GetWidth() * currScale;
                        f32 charSpace = textBox->GetCharSpace();
                        f32 charWidth = wid * fnt->GetCharWidth(c);
                        posX += charWidth + charSpace;
                        writePtr++;
                    }
                    break;
                case 0x10012:
                    if (b2) {
                        somethingWithScrapperAndMusic(endPtr);
                    }
                    break;
            }
            src += cmdLen / 2 + 1;
            // For some reason the compiler believes that this branch affects something about
            // charData/charDataIdx and branches to a re-calculation of the strength-reduced
            // pointer, which means the compiler keepts charDataIdx actually around while still
            // incrementing the strength-reduced charData pointer by 0x14. Very weird!
            // This charDataIdx++, charDataIdx-- causes this behavior, not sure what the real match would be.
            charDataIdx++;
            charDataIdx--;
            goto beginning;
        } else if (c == 0xF) {
            s32 cmd = 0;
            process0xFCommand(c, &src[1], &cmd);
            if (cmd == 0x30000) {
                currScale = backupScale;
            }
            src += 3;
        } else {
            if (b2) {
                *writePtr = c;
                const nw4r::ut::Font *fnt = textBox->GetFont();
                f32 tmp = currScale * fnt->GetCharWidth(READ_U16(src, 0)) * 0.5f;
                posX += tmp;
                if (charData != nullptr) {
                    wchar_t s = *writePtr;
                    if (s != 10 && s != 0x20 && s != 0x3000) {
                        charData[charDataIdx].posX = posX;
                        charData[charDataIdx].posY = posY;
                        charData[charDataIdx].field_0x08 = currScale;
                        charData[charDataIdx].character = *writePtr;
                        charDataIdx++;
                    }
                }
                writePtr++;
                posX += (tmp + textBox->GetCharSpace());
            }
            src++;
        }
    }

end:
    if (pOutLen != nullptr) {
        *pOutLen = writePtr - dest;
    }
}

nw4r::ut::Operation dTagProcessor_c::Process(u16 ch, nw4r::ut::PrintContext<wchar_t> *ctx) {
    return ProcessTags(nullptr, ch, ctx);
}

nw4r::ut::Operation dTagProcessor_c::CalcRect(nw4r::ut::Rect *rect, u16 ch, nw4r::ut::PrintContext<wchar_t> *ctx) {
    return ProcessTags(rect, ch, ctx);
}

nw4r::ut::Operation dTagProcessor_c::ProcessTags(nw4r::ut::Rect *rect, u16 ch, nw4r::ut::PrintContext<wchar_t> *ctx) {
    if (ch != 0x0E) {
        if (ch != 0x0F) {
            if (rect != nullptr) {
                return nw4r::ut::TagProcessorBase<wchar_t>::CalcRect(rect, ch, ctx);
            } else {
                return nw4r::ut::TagProcessorBase<wchar_t>::Process(ch, ctx);
            }
        } else {
            s32 cmd = 0;
            process0xFCommand(ch, ctx->str, &cmd);
            if (cmd == 0x30000) {
                changeScale(rect, ctx, true);
            }
            ctx->str += 2;
            return nw4r::ut::OPERATION_DEFAULT;
        }
    }

    u8 cmdLen = 0;
    s32 cmd = 0;
    wchar_t *endPtr = nullptr;
    getTextCommand(ch, ctx->str, &cmdLen, &cmd, &endPtr);
    switch (cmd) {
        case TAG_CMD_OPTION_0:
            ctx->str += cmdLen / 2;
            ctx->str += mOptionLengths[0];
            return nw4r::ut::OPERATION_DEFAULT;
        case TAG_CMD_OPTION_1:
            ctx->str += cmdLen / 2;
            ctx->str += mOptionLengths[1];
            return nw4r::ut::OPERATION_DEFAULT;
        case TAG_CMD_OPTION_2:
            ctx->str += cmdLen / 2;
            ctx->str += mOptionLengths[2];
            return nw4r::ut::OPERATION_DEFAULT;
        case TAG_CMD_OPTION_3:
            ctx->str += cmdLen / 2;
            ctx->str += mOptionLengths[3];
            return nw4r::ut::OPERATION_DEFAULT;
        case TAG_CMD_0x0F0F0F0F: fn_800B4FF0(rect, ctx, cmdLen, endPtr); break;
        case TAG_CMD_0x0F0F0F0E: field_0xEE2 = 1; break;
        case TAG_CMD_COLOR:
            if (!mIsShadowText) {
                setColor(rect, ctx, cmdLen, endPtr);
            }
            break;
        case TAG_CMD_SCALE: setScale(rect, ctx, cmdLen, endPtr); break;
        case 0x10004:
            // Pause
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEED == 0 && mNumericArgsCopy[8] == field_0x838) {
                    setFramesLeftOnPause(rect, ctx, cmdLen, endPtr);
                    field_0xEED = 1;
                    field_0x838++;
                }
                mNumericArgsCopy[8]++;
            }
            break;
        case 0x10005:
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEEE == 0 && mNumericArgsCopy[9] == field_0x83C) {
                    fn_800B5500(cmdLen, endPtr);
                    field_0xEEE = 1;
                    field_0x83C++;
                }
                mNumericArgsCopy[9]++;
            }
            break;
        case 0x10007:
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEE5 == 0 && mNumericArgsCopy[0] == field_0x87C) {
                    fn_800B60E0(cmdLen, endPtr);
                    field_0xEE5 = 1;
                    field_0x87C++;
                }
                mNumericArgsCopy[0]++;
            }
            break;
        case 0x10008: fn_800B61D0(rect, ctx, cmdLen, endPtr); break;
        case 0x30000: changeScale(rect, ctx, false); break;
        case 0x10009:
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEE6 == 0 && mNumericArgsCopy[1] == field_0x880) {
                    fn_800B6110(cmdLen, endPtr);
                    field_0xEE6 = 1;
                    field_0x880++;
                }
                mNumericArgsCopy[1]++;
            }
            break;
        case 0x1000A:
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEE7 == 0 && mNumericArgsCopy[2] == field_0x884) {
                    fn_800B6140(cmdLen, endPtr);
                    field_0xEE7 = 1;
                    field_0x884++;
                }
                mNumericArgsCopy[2]++;
            }
            break;
        case 0x1000B:
            // Sound
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEE8 == 0 && mNumericArgsCopy[3] == field_0x888) {
                    playSound(cmdLen, endPtr);
                    field_0xEE8 = 1;
                    field_0x888++;
                }
                mNumericArgsCopy[3]++;
            }
            break;
        case 0x1000C:
            // "entrypoint"
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEEA == 0 && mNumericArgsCopy[5] == field_0x890) {
                    fn_800B6170(cmdLen, endPtr);
                    field_0xEEA = 1;
                    field_0x890++;
                }
                mNumericArgsCopy[5]++;
            }
            break;
        case 0x1000D:
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEEB == 0 && mNumericArgsCopy[6] == field_0x894) {
                    fn_800B6190(cmdLen, endPtr);
                    field_0xEEB = 1;
                    field_0x894++;
                }
                mNumericArgsCopy[6]++;
            }
            break;
        case 0x1000E:
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEEC == 0 && mNumericArgsCopy[7] == field_0x898) {
                    fn_800B61B0(cmdLen, endPtr);
                    field_0xEEC = 1;
                    field_0x898++;
                }
                mNumericArgsCopy[7]++;
            }
            break;
        case 0x20004:
            if (field_0xEE3 != 0) {
                if (rect == nullptr) {
                    drawIcon(rect, ctx, cmdLen, endPtr);
                } else {
                    calcRectIcon(rect, ctx, endPtr);
                }
            }
            break;
        case 0x10011:
            if (rect == nullptr && !mIsShadowText) {
                if (field_0xEEF == 0 && mNumericArgsCopy[10] == field_0x840) {
                    fn_800B5540(endPtr);
                    field_0xEEF = 1;
                    field_0x840++;
                }
                mNumericArgsCopy[10]++;
            }
            break;
    }
    ctx->str += cmdLen / 2;
    return nw4r::ut::OPERATION_DEFAULT;
}

void dTagProcessor_c::fn_800B4FF0(nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, u8 cmdLen, wchar_t *ptr) {
    if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_22 || mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_DEMO) {
        return;
    }

    if (field_0xEE0 != 0) {
        int arg = ptr[0];
        nw4r::lyt::Size textBoxSize = getTextBox()->GetSize();
        nw4r::lyt::Size fontSize = getTextBox()->GetFontSize();
        if (arg % getMaxNumLines(mMsgWindowSubtype) == 0 && mMsgWindowSubtype != dLytMsgWindow_c::MSG_WINDOW_31 &&
            mMsgWindowSubtype != dLytMsgWindow_c::MSG_WINDOW_8) {
            int v = 0;
            int u = 0;
            // similar code in fn_800B85C0
            for (int i = arg; i < arg + getMaxNumLines(mMsgWindowSubtype) && i < 0x32; i++) {
                f32 f6 = getLineWidth(i);
                if (f6 > 0.0f) {
                    v++;
                    if (u != 0) {
                        v += u;
                        u = 0;
                    }
                } else {
                    u++;
                }
            }

            if (u < getMaxNumLines(mMsgWindowSubtype)) {
                // Begin unused code - the results here are unused and disappear
                // in the assembly, so we don't know which fields are accessed in
                // the tmp vars, but control flow from the inlines remains
                f32 w1 = fontSize.width * 0.5f;
                f32 h1 = fontSize.height * 0.5f;
                if (w1 < dLyt_HIO_c::getField0x758() || h1 < dLyt_HIO_c::getField0x758()) {
                    if (w1 < h1) {
                        f32 tmp = dLyt_HIO_c::getField0x758();
                    } else {
                        f32 tmp2 = dLyt_HIO_c::getField0x758();
                    }
                }
                f32 tmp3 = dLyt_HIO_c::getField0x758();
                // End unused code

                f32 f7 = (fontSize.height + getTextBox()->GetLineSpace()) / 2.0f;
                f32 f8 = f7 * (getMaxNumLines(mMsgWindowSubtype) - v);
                field_0x814 = ctx->writer->GetCursorY();
                field_0x818 = ctx->y;
                field_0xEE4 = 1;
                if (f8 > 0.0f) {
                    ctx->writer->MoveCursorY(f8);
                    field_0x810 = f8;
                }
            }
        }

        f32 lineWidth = getLineWidth(arg);
        f32 margin = (textBoxSize.width - lineWidth) / 2.0f;
        if ((mMsgWindowSubtype < dLytMsgWindow_c::MSG_WINDOW_WOOD ||
             mMsgWindowSubtype > dLytMsgWindow_c::MSG_WINDOW_LINK) &&
            mMsgWindowSubtype != dLytMsgWindow_c::MSG_WINDOW_DEMO) {
            margin = 0.0f;
        }
        if (margin > 0.0f) {
            ctx->writer->MoveCursorX(margin);
        }
    }
}

void dTagProcessor_c::setColor(nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, u8 cmdLen, wchar_t *buf) {
    u16 cmd = buf[0];
    if (cmd == 0xFFFF) {
        restoreColor(ctx, mMsgWindowSubtype);
        return;
    }
    nw4r::ut::Color c1 = FontColors1[cmd & 0xFFFF];
    nw4r::ut::Color c2 = FontColors2[cmd & 0xFFFF];
    if (cmd == 0) {
        if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_SWORD_FI) {
            // #FF6E54, #FF6E54
            c1.r = 0xFF;
            c1.g = 0x6E;
            c1.b = 0x64;
            c2.r = 0xFF;
            c2.g = 0x6E;
            c2.b = 0x64;
        } else if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_STONE) {
            // #E64B32, #E64B32
            c1.r = 0xE6;
            c1.g = 0x4B;
            c1.b = 0x32;
            c2.r = 0xE6;
            c2.g = 0x4B;
            c2.b = 0x32;
        }
    } else if (cmd == 1) {
        if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_SWORD_FI) {
            // #F5645A, #C8645A
            c1.r = 0xF5;
            c1.g = 0x64;
            c1.b = 0x5A;
            c2.r = 0xC8;
            c2.g = 0x64;
            c2.b = 0x5A;
        } else if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_STONE) {
            // #B45050, #8C4040
            c1.r = 0xB4;
            c1.g = 0x50;
            c1.b = 0x50;
            c2.r = 0x8C;
            c2.g = 0x40;
            c2.b = 0x40;
        }
    } else if (cmd == 3 && mMsgWindowSubtype >= dLytMsgWindow_c::MSG_WINDOW_SWORD_FI &&
               mMsgWindowSubtype < dLytMsgWindow_c::MSG_WINDOW_SWORD_FI + 3) {
        // #50E6FA, #C8E6F5
        c1.r = 0x50;
        c1.g = 0xE6;
        c1.b = 0xFA;
        c2.r = 0xC8;
        c2.g = 0xE6;
        c2.b = 0xF5;
    }

    u8 u5 = 0xFF;
    if (getTextBox() != nullptr) {
        u5 = getTextBox()->GetGlobalAlpha();
    }

    c2.a = u5;
    c1.a = u5;
    ctx->writer->SetGradationMode(nw4r::ut::CharWriter::GRADMODE_V);
    ctx->writer->SetTextColor(c1, c2);
}

void dTagProcessor_c::setScale(nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, u8 cmdLen, wchar_t *buf) {
    u16 scale = READ_U16(buf, 0);
    if (scale == 0) {
        scale = 50;
    }
    f32 x = scale / 100.0f;
    f32 y = scale / 100.0f;
    ctx->writer->SetScale(x, y);
}

void dTagProcessor_c::setFramesLeftOnPause(
    nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, u8 cmdLen, wchar_t *ptr
) {
    mPauseFramesLeft = READ_U16(ptr, 0);
}

void dTagProcessor_c::fn_800B5500(u8 cmdLen, wchar_t *ptr) {
    u16 val = READ_U16(ptr, 0);
    if (field_0x830 == -1) {
        field_0x830 = val;
    }
}

void dTagProcessor_c::fn_800B5520(wchar_t *src) {
    field_0x81C = READ_U32(src, 0);
    field_0x820 = READ_U32(src, 4);
}

void dTagProcessor_c::fn_800B5540(wchar_t *src) {
    dLytMsgWindow_c::getInstance()->onFlag0x820(sMsgWindowFlags[READ_U8(src, 0)]);
}

wchar_t *dTagProcessor_c::writeHeroname(wchar_t *dest, s32 *outArg, bool isProcessingOption) {
    if (FileManager::GetInstance()->getHeroname()[0] != '\0') {
        for (int i = 0; FileManager::GetInstance()->getHeroname()[i] != '\0'; i++) {
            if (isProcessingOption && mCurrentOptionIdx != 0) {
                wchar_t *heroName = FileManager::GetInstance()->getHeroname();
                writeSingleCharacter(heroName[i], &getOptionBuf()[*outArg], outArg);
                onWriteOptionBuf();
            } else {
                wchar_t *heroName = FileManager::GetInstance()->getHeroname();
                dest = writeSingleCharacter(heroName[i], dest, nullptr);
            }
        }
    }
    return dest;
}

wchar_t *dTagProcessor_c::writeItem(wchar_t *dest, wchar_t *src, s32 *outArg, bool isProcessingOption) {
    int itemIndex = READ_U16(src, 0);
    wchar_t c;

    int i = 0;
    SizedString<16> mName;
    mName.sprintf("NAME_ITEM_%03d", itemIndex);
    MsbtInfo *info = dMessage_c::getMsbtInfoForIndex(/* ItemGet */ 3);
    const wchar_t *text = LMS_GetTextByLabel(info, mName);

    while ((c = text[i]) != 0) {
        if (c == 0xE) {
            int len = ((text[i + 3] / 2) & 0x7F) + 4;
            if (isProcessingOption && mCurrentOptionIdx != 0) {
                for (int j = 0; j < len; j++) {
                    writeSingleCharacter(text[i], &getOptionBuf()[*outArg], outArg);
                    onWriteOptionBuf();
                    i++;
                }
            } else {
                for (int j = 0; j < len; j++) {
                    dest = writeSingleCharacter(text[i], dest, nullptr);
                    i++;
                }
            }
        } else {
            if (isProcessingOption && mCurrentOptionIdx != 0) {
                writeSingleCharacter(c, &getOptionBuf()[*outArg], outArg);
                onWriteOptionBuf();
            } else {
                dest = writeSingleCharacter(c, dest, nullptr);
            }
            i++;
        }
    }

    return dest;
}

wchar_t *dTagProcessor_c::writeStringArg(wchar_t *dest, wchar_t *src, s32 *outArg, bool isProcessingOption) {
    s32 argIdx = READ_U32(src, 0);
    wchar_t c;
    int i = 0;
    while ((c = mStringArgs[argIdx][i]) != 0) {
        if (c == 0xE) {
            int len = ((mStringArgs[argIdx][i + 3] / 2) & 0x7F) + 4;
            if (isProcessingOption && mCurrentOptionIdx != 0) {
                for (int j = 0; j < len; j++) {
                    writeSingleCharacter(mStringArgs[argIdx][i], &getOptionBuf()[*outArg], outArg);
                    onWriteOptionBuf();
                    i++;
                }
            } else {
                for (int j = 0; j < len; j++) {
                    dest = writeSingleCharacter(mStringArgs[argIdx][i], dest, nullptr);
                    i++;
                }
            }
        } else {
            if (isProcessingOption && mCurrentOptionIdx != 0) {
                writeSingleCharacter(c, &getOptionBuf()[*outArg], outArg);
                onWriteOptionBuf();
            } else {
                dest = writeSingleCharacter(c, dest, nullptr);
            }
            i++;
        }

        if (i >= 0x40) {
            break;
        }
    }

    return dest;
}

wchar_t *dTagProcessor_c::writeNumericArg(wchar_t *dest, wchar_t *src, s32 *outArg, bool isProcessingOption) {
    int numZeroDigits = READ_U8(src, 4);
    bool writeZeroDigits = false;
    s32 argIdx = READ_U32(src, 0);
    s32 numberArg = mNumericArgs[argIdx];
    mFirstNumericArg = numberArg;

    // This could potentially be an unrolled loop

    s32 digit = numberArg / 10000;
    s32 number = numberArg % 10000;

    if (!writeZeroDigits && numZeroDigits == 5) {
        writeZeroDigits = true;
    }
    if (digit > 0 || writeZeroDigits) {
        if (isProcessingOption && mCurrentOptionIdx != 0) {
            getOptionBuf()[*outArg] = '0' + digit;
            onWriteOptionBuf();
            (*outArg)++;
        } else {
            *(dest++) = '0' + digit;
        }
        writeZeroDigits = true;
    }

    digit = number / 1000;
    number = number % 1000;
    if (!writeZeroDigits && numZeroDigits == 4) {
        writeZeroDigits = true;
    }
    if (digit > 0 || writeZeroDigits) {
        if (isProcessingOption && mCurrentOptionIdx != 0) {
            getOptionBuf()[*outArg] = '0' + digit;
            onWriteOptionBuf();
            (*outArg)++;
        } else {
            *(dest++) = '0' + digit;
        }
        writeZeroDigits = true;
    }

    digit = number / 100;
    number = number % 100;
    if (!writeZeroDigits && numZeroDigits == 3) {
        writeZeroDigits = true;
    }
    if (digit > 0 || writeZeroDigits) {
        if (isProcessingOption && mCurrentOptionIdx != 0) {
            getOptionBuf()[*outArg] = '0' + digit;
            onWriteOptionBuf();
            (*outArg)++;
        } else {
            *(dest++) = '0' + digit;
        }
        writeZeroDigits = true;
    }

    digit = number / 10;
    number = number % 10;
    if (!writeZeroDigits && numZeroDigits == 2) {
        writeZeroDigits = true;
    }
    if (digit > 0 || writeZeroDigits) {
        if (isProcessingOption && mCurrentOptionIdx != 0) {
            getOptionBuf()[*outArg] = '0' + digit;
            onWriteOptionBuf();
            (*outArg)++;
        } else {
            *(dest++) = '0' + digit;
        }
        writeZeroDigits = true;
    }

    digit = number;
    if (isProcessingOption && mCurrentOptionIdx != 0) {
        getOptionBuf()[*outArg] = '0' + digit;
        onWriteOptionBuf();
        (*outArg)++;
    } else {
        *(dest++) = '0' + digit;
    }
    return dest;
}

wchar_t *dTagProcessor_c::writeSingularOrPluralWord(wchar_t *dest, wchar_t *src, s32 *outArg, bool isProcessingOption) {
    int itemIndex = READ_U8(src, 0);
    wchar_t c;

    int i = 0;
    SizedString<32> mName;
    s32 amount = 2;
    if (shouldUseSingular(mFirstNumericArg)) {
        amount = 1;
    }
    mName.sprintf("lang:word:%03d:%02d", itemIndex, amount);
    MsbtInfo *info = dMessage_c::getMsbtInfoForIndex(/* word */ 18);
    const wchar_t *text = LMS_GetTextByLabel(info, mName);

    while ((c = text[i]) != 0) {
        if (c == 0xE) {
            int len = ((text[i + 3] / 2) & 0x7F) + 4;
            if (isProcessingOption && mCurrentOptionIdx != 0) {
                for (int j = 0; j < len; j++) {
                    writeSingleCharacter(text[i], &getOptionBuf()[*outArg], outArg);
                    onWriteOptionBuf();
                    i++;
                }
            } else {
                for (int j = 0; j < len; j++) {
                    dest = writeSingleCharacter(text[i], dest, nullptr);
                    i++;
                }
            }
        } else {
            if (isProcessingOption && mCurrentOptionIdx != 0) {
                writeSingleCharacter(c, &getOptionBuf()[*outArg], outArg);
                onWriteOptionBuf();
            } else {
                dest = writeSingleCharacter(c, dest, nullptr);
            }
            i++;
        }
    }

    return dest;
}

wchar_t *dTagProcessor_c::writeSingleCharacter(wchar_t character, wchar_t *dst, s32 *pOutLen) {
    if (field_0xEF1) {
        field_0xEF1 = 0;
        wchar_t replacementSequence[3];
        toUpperCase(character, replacementSequence, 3);
        wchar_t *ret = dst;
        for (s32 i = 0; i < 3; i++) {
            if (replacementSequence[i] == L'\0') {
                return dst;
            }
            *dst = replacementSequence[i];
            dst++;
            if (pOutLen != nullptr) {
                (*pOutLen)++;
            }
        }
        return dst;
    } else {
        *dst = character;
        if (pOutLen != nullptr) {
            (*pOutLen)++;
        }
        return dst + 1;
    }
}

void dTagProcessor_c::fn_800B60E0(u8 cmdLen, wchar_t *ptr) {
    field_0x844 = READ_U8(ptr, 0);
    field_0x848 = READ_U8(ptr, 1);
    field_0x84C = READ_U8(ptr, 2);
    field_0x850 = READ_U8(ptr, 3);
}
void dTagProcessor_c::fn_800B6110(u8 cmdLen, wchar_t *ptr) {
    field_0x854 = READ_U8(ptr, 0);
    field_0x858 = READ_U8(ptr, 1);
    field_0x85C = READ_U8(ptr, 2);
    field_0x860 = READ_U8(ptr, 3);
}
void dTagProcessor_c::fn_800B6140(u8 cmdLen, wchar_t *ptr) {
    field_0x864 = READ_U16(ptr, 0);
    field_0x868 = READ_U16(ptr, 2);
}
void dTagProcessor_c::playSound(u8 cmdLen, wchar_t *ptr) {
    dFlow_c::playSound(READ_U32(ptr, 0));
}
void dTagProcessor_c::fn_800B6170(u8 cmdLen, wchar_t *ptr) {
    field_0x8FC = READ_U16(ptr, 0);
    field_0x900 = READ_U16(ptr, 2);
}
void dTagProcessor_c::fn_800B6190(u8 cmdLen, wchar_t *ptr) {
    field_0x86C = READ_S8(ptr, 0);
    field_0x870 = READ_S8(ptr, 1);
}
void dTagProcessor_c::fn_800B61B0(u8 cmdLen, wchar_t *ptr) {
    field_0x874 = READ_S8(ptr, 0);
    field_0x878 = READ_S16(ptr, 1);
}

void dTagProcessor_c::fn_800B61D0(nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, u8 cmdLen, wchar_t *ptr) {
    f32 scale = fn_800B8040(READ_S8(ptr, 0), mMsgWindowSubtype);
    if (getTextBox() != nullptr) {
        scale *= getTextBox()->getMyScale();
    }
    ctx->writer->SetScale(scale, scale);
}

void dTagProcessor_c::changeScale(nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, bool direct) {
    f32 scale;
    f32 posY;

    if (direct) {
        scale = field_0x904;
        posY = field_0x908;
    } else {
        field_0x904 = ctx->writer->GetScaleV();
        field_0x908 = ctx->writer->GetCursorY();
        scale = field_0x904 * dLyt_HIO_c::getField0x768();
        posY = field_0x908 - ctx->writer->GetFontAscent() * dLyt_HIO_c::getField0x76C();
    }

    ctx->writer->SetCursorY(posY);
    ctx->writer->SetScale(scale, scale);
}

void dTagProcessor_c::saveIconWidth(dTextBox_c *textBox, wchar_t *cmd, f32 arg) {
    nw4r::ut::Font *f = dFontMng_c::getFont(dFontMng_c::PICTURE);
    s8 c3 = getAnimatedIconReplacement(READ_U8(cmd, 0));
    arg = dLyt_HIO_c::getField0x764() / f->GetWidth() * arg;

    mLineData.mLineWidths[mLineData.mNumLines] += arg * f->GetCharWidth(c3) + textBox->GetCharSpace();
}

void dTagProcessor_c::drawIcon(nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, u8 cmdLen, wchar_t *ptr) {
    s8 c = getAnimatedWiggleIconReplacement(READ_U8(ptr, 0));
    u8 alpha = 0xFF;
    if (getTextBox() != nullptr) {
        alpha = mpTextBox->GetGlobalAlpha();
    }
    s32 x1 = alpha / 255.0f * getAlphaForAnimatedIcon(READ_U8(ptr, 0));
    s32 x2 = alpha / 255.0f * (255 - getAlphaForAnimatedIcon(READ_U8(ptr, 0)));

    nw4r::ut::TextWriterBase<wchar_t> w = *ctx->writer;
    drawPicture(&w, ctx, (u32)c, x1);
    if (READ_U8(ptr, 0) >= FONT_BTN_NUN_STK_UP && READ_U8(ptr, 0) <= FONT_BTN_NUN_STK_WIGGLE_HORIZONTAL) {
        nw4r::ut::TextWriterBase<wchar_t> w2 = *ctx->writer;
        s8 c = symbolToFontIdx(FONT_BTN_NUN_STK);
        drawPicture(&w2, ctx, c, x2);
    }
    ctx->writer->SetCursorX(w.GetCursorX());
    ctx->writer->SetupGX();
    if (mpTextBox != nullptr) {
        mpTextBox->setupGX();
    }
}

void dTagProcessor_c::drawPicture(
    nw4r::ut::CharWriter *w, nw4r::ut::PrintContext<wchar_t> *ctx, u16 character, u8 alpha
) {
    nw4r::ut::Font *font = dFontMng_c::getFont(dFontMng_c::PICTURE);

    f32 f1 = dLyt_HIO_c::getField0x764() * w->GetScaleV() / dLyt_HIO_c::getFn800B1FC0();
    f32 f3 = dLyt_HIO_c::getFn800B1FC0();
    f32 f4 = dLyt_HIO_c::getField0x764();
    f32 f5 = dLyt_HIO_c::getFn800B1FC0();
    f32 scaleY2 = w->GetScaleV();

    s32 fontHeight = w->GetFont()->GetHeight();
    f32 f6 = (scaleY2 * (fontHeight * f3 - f4)) / f5 / 2.0f;

    w->SetFont(*font);
    // #000000, #FFFFFF
    w->ResetColorMapping();

    s32 x = 0;
    w->SetFontSize((s32)(f1 + 1.0f + x));
    if (field_0xEE4) {
        w->SetCursorY(f6 + field_0x818 - (field_0x814 - ctx->writer->GetCursorY()));
    } else {
        w->SetCursorY(ctx->y + f6);
    }
    w->SetGradationMode(nw4r::ut::CharWriter::GRADMODE_V);
    if (character == /* FONT_X */ 0x38) {
        // #FFFFFF, #FFFFFF
        mColor c1(0xFF, 0xFF, 0xFF, alpha);
        mColor c2(0xFF, 0xFF, 0xFF, alpha);
        w->SetTextColor(c1, c2);
        // #FFAB7F, #DD4809
        mColor c3(0xFF, 0xAB, 0x7F, 0);
        mColor c4(0xDD, 0x48, 0x09, 0xFF);
        w->SetColorMapping(c3, c4);
    } else if (character == /* FONT_HOLE */ 0x39) {
        // #FFFFFF, #FFFFFF
        mColor c1(0xFF, 0xFF, 0xFF, alpha);
        mColor c2(0xFF, 0xFF, 0xFF, alpha);
        w->SetTextColor(c1, c2);
        // #24D49F, #FFF8A4
        mColor c3(0x24, 0xD4, 0x9F, 0);
        mColor c4(0xFF, 0xF8, 0xA4, 0xFF);
        w->SetColorMapping(c3, c4);
    } else if (character >= /* FONT_ARROW_UP */ 0x33 && character <= /* FONT_ARROW_RIGHT */ 0x36) {
        if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_WOOD) {
            // #F2DABC, #F2DABC
            mColor c1(0xF2, 0xDA, 0xBC, alpha);
            mColor c2(0xF2, 0xDA, 0xBC, alpha);
            w->SetTextColor(c1, c2);
        } else if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_STONE) {
            // #6E6973, #6E6973
            mColor c1(0x6E, 0x69, 0x73, alpha);
            mColor c2(0x6E, 0x69, 0x73, alpha);
            w->SetTextColor(c1, c2);
        } else {
            // #FFFCEE, #F3EFE1
            mColor c1(0xFF, 0xFC, 0xEE, alpha);
            mColor c2(0xF3, 0xEF, 0xE1, alpha);
            w->SetTextColor(c1, c2);
        }
    } else {
        // #FFFCEE, #F3EFE1
        mColor c1(0xFF, 0xFC, 0xEE, alpha);
        mColor c2(0xF3, 0xEF, 0xE1, alpha);
        w->SetTextColor(c1, c2);
    }
    if (mIsShadowText) {
        // #505050
        mColor c1(0x50, 0x50, 0x50, 0);
        mColor c2(0x50, 0x50, 0x50, 0x50);
        SetupGXCommon();
        SetupGXWithColorMapping(c1, c2);
        SetupVertexFormat();
    } else {
        w->SetupGX();
    }
    w->Print(character);
}

void dTagProcessor_c::calcRectIcon(nw4r::ut::Rect *rect, nw4r::ut::PrintContext<wchar_t> *ctx, wchar_t *ptr) {
    nw4r::ut::Font *f = dFontMng_c::getFont(dFontMng_c::PICTURE);
    s8 c3 = getAnimatedIconReplacement(READ_U8(ptr, 0));
    nw4r::ut::TextWriterBase<wchar_t> copy = *ctx->writer;
    f32 charWidth = f->GetCharWidth(c3);
    configureWriterForIcon(&copy, ctx, c3, 0xFF);
    ctx->writer->MoveCursorX(charWidth * copy.GetScaleH());

    f32 cursorX = ctx->writer->GetCursorX();
    rect->left = rect->left > cursorX ? cursorX : rect->left;
    rect->right = rect->right < cursorX ? cursorX : rect->right;
}

void dTagProcessor_c::configureWriterForIcon(
    nw4r::ut::TextWriterBase<wchar_t> *w, nw4r::ut::PrintContext<wchar_t> *ctx, u16 arg, s32 alpha
) {
    nw4r::ut::Font *font = dFontMng_c::getFont(dFontMng_c::PICTURE);

    f32 f1 = dLyt_HIO_c::getField0x764() * ctx->writer->GetScaleV() / dLyt_HIO_c::getFn800B1FC0();
    w->SetFont(*font);
    w->SetFontSize(f1);
    w->SetGradationMode(nw4r::ut::CharWriter::GRADMODE_V);
    if (arg == /* FONT_X */ 0x38) {
        // #F26E36, #F26E36
        mColor c1(0xF2, 0x6E, 0x36, alpha);
        mColor c2(0xF2, 0x6E, 0x36, alpha);
        w->SetTextColor(c1, c2);
    } else if (arg >= /* FONT_ARROW_UP */ 0x33 && arg < /* FONT_ARROW_DOWN */ 0x37) {
        if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_WOOD) {
            // #F2DABC, #F2DABC
            mColor c1(0xF2, 0xDA, 0xBC, alpha);
            mColor c2(0xF2, 0xDA, 0xBC, alpha);
            w->SetTextColor(c1, c2);
        } else if (mMsgWindowSubtype == dLytMsgWindow_c::MSG_WINDOW_STONE) {
            // #6E6973, #6E6973
            mColor c1(0x6E, 0x69, 0x73, alpha);
            mColor c2(0x6E, 0x69, 0x73, alpha);
            w->SetTextColor(c1, c2);
        } else {
            // #FFFCEE, #F3EFE1
            mColor c1(0xFF, 0xFC, 0xEE, alpha);
            mColor c2(0xF3, 0xEF, 0xE1, alpha);
            w->SetTextColor(c1, c2);
        }
    } else {
        // #FFFCEE, #F3EFE1
        mColor c1(0xFF, 0xFC, 0xEE, alpha);
        mColor c2(0xF3, 0xEF, 0xE1, alpha);
        w->SetTextColor(c1, c2);
    }
}

void dTagProcessor_c::somethingWithScrapperAndMusic(wchar_t *src) {
    field_0x824 = READ_U32(src, 0);
    if (field_0x824 == 100) {
        dAcBase_c *ac = nullptr;
        dAcBase_c *parent = nullptr;
        while ((parent = ac = static_cast<dAcBase_c *>(fManager_c::searchBaseByGroupType(fBase_c::ACTOR, parent))) !=
               nullptr) {
            if ((ac->mProfileName >= fProfile::NPC_SLFB && ac->mProfileName <= fProfile::NPC_SLFL) ||
                (ac->mProfileName >= fProfile::NPC_SLB && ac->mProfileName <= fProfile::FLY_SLB) ||
                ac->mProfileName == fProfile::NPC_SLB2 || ac->mProfileName == fProfile::NPC_DRBC) {
                dSndPlayerMgr_c::GetInstance()->setMsgActor(dLytMsgWindow_c::getInstance()->getMsgIdx(), ac);
                return;
            }
        }
    } else if (field_0x824 == 101) {
        dSndPlayerMgr_c::GetInstance()->setMsgActor(dLytMsgWindow_c::getInstance()->getMsgIdx(), nullptr);
    }
}

void dTagProcessor_c::restoreColor(nw4r::ut::PrintContext<wchar_t> *ctx, u8 windowType) {
    if (field_0xEE2 != 0) {
        windowType = 1;
    }
    int colorIndex;
    switch (windowType) {
        case 2:  colorIndex = 15; break;
        case 3:  colorIndex = 16; break;
        case 4:  colorIndex = 17; break;
        case 6:  colorIndex = 19; break;
        case 7:  colorIndex = 20; break;
        case 8:  colorIndex = 21; break;
        case 9:  colorIndex = 22; break;
        case 11: colorIndex = 24; break;
        case 12: colorIndex = 25; break;
        case 13: colorIndex = 26; break;
        case 17: colorIndex = 30; break;
        case 18: colorIndex = 31; break;
        case 19: colorIndex = 32; break;
        case 30: colorIndex = 43; break;
        case 20: colorIndex = 33; break;
        case 22: colorIndex = 35; break;
        case 23: colorIndex = 36; break;
        case 24: colorIndex = 37; break;
        case 27: colorIndex = 40; break;
        case 29: colorIndex = 42; break;
        case 31: colorIndex = 44; break;
        case 5:  colorIndex = 18; break;
        default: colorIndex = 14; break;
    }

    nw4r::ut::Color c1 = FontColors1[colorIndex & 0xFFFF];
    nw4r::ut::Color c2 = FontColors2[colorIndex & 0xFFFF];

    if (windowType == 7) {
        c2.a = 0xC8;
    } else if (windowType == 12) {
        c2.a = 0xFF;
    } else if (windowType == 13) {
        c2.a = 0xC8;
    }

    u8 u5 = 0xFF;
    if (getTextBox() != nullptr) {
        u5 = getTextBox()->GetGlobalAlpha();
    }

    c1.a = c1.a * u5 / 255.0f;
    c2.a = c2.a * u5 / 255.0f;
    ctx->writer->SetGradationMode(nw4r::ut::CharWriter::GRADMODE_V);
    ctx->writer->SetTextColor(c1, c2);
}

wchar_t *
dTagProcessor_c::writeTextNormal(const wchar_t *src, wchar_t *dest, s32 *pArg, u8 cmdLen, bool isProcessingOption) {
    if (isProcessingOption && mCurrentOptionIdx != 0) {
        for (u32 i = 0; i < (cmdLen / 2 + 1); i++) {
            getOptionBuf()[*pArg] = *(src++);
            onWriteOptionBuf();
            (*pArg)++;
        }
    } else {
        memcpy(dest, src, cmdLen + 2);
        dest += (cmdLen / 2 + 1);
    }

    return dest;
}

u8 dTagProcessor_c::symbolToFontIdx(s32 s) {
    static const s8 index[] = " !\"#$%&\'()*+,)+-/0123456789";
    return index[s];
}

u8 dTagProcessor_c::getAnimatedIconReplacement(u8 arg) {
    if (arg >= FONT_BTN_NUN_STK_UP && arg <= FONT_BTN_NUN_STK_RIGHT) {
        if (mButtonPressAnimTimer >= dLyt_HIO_c::getAnimBtnTotalDuration()) {
            // Show base instead
            return symbolToFontIdx(FONT_BTN_NUN_STK);
        }
    } else if (arg >= FONT_BTN_CROSS_UP && arg <= FONT_BTN_CROSS_RIGHT) {
        if (mButtonPressAnimTimer >= dLyt_HIO_c::getAnimBtnTotalDuration()) {
            // Show base instead
            // Empty Cross Btn
            return '.';
        }
    } else if (arg == FONT_BTN_NUN_STK_WIGGLE_VERTICAL) {
        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration()) {
            return symbolToFontIdx(FONT_BTN_NUN_STK_UP);
        }

        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 2) {
            return symbolToFontIdx(FONT_BTN_NUN_STK);
        }

        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 3) {
            return symbolToFontIdx(FONT_BTN_NUN_STK_DOWN);
        }
        return symbolToFontIdx(FONT_BTN_NUN_STK);
    } else if (arg == FONT_BTN_NUN_STK_WIGGLE_HORIZONTAL) {
        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration()) {
            return symbolToFontIdx(FONT_BTN_NUN_STK_LEFT);
        }

        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 2) {
            return symbolToFontIdx(FONT_BTN_NUN_STK);
        }

        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 3) {
            return symbolToFontIdx(FONT_BTN_NUN_STK_RIGHT);
        }
        return symbolToFontIdx(FONT_BTN_NUN_STK);
    }

    return symbolToFontIdx(arg);
}

u8 dTagProcessor_c::getAnimatedWiggleIconReplacement(u8 arg) {
    if (arg == FONT_BTN_NUN_STK_WIGGLE_VERTICAL) {
        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 2) {
            return symbolToFontIdx(FONT_BTN_NUN_STK_UP);
        } else {
            return symbolToFontIdx(FONT_BTN_NUN_STK_DOWN);
        }
    } else if (arg == FONT_BTN_NUN_STK_WIGGLE_HORIZONTAL) {
        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 2) {
            return symbolToFontIdx(FONT_BTN_NUN_STK_LEFT);
        } else {
            return symbolToFontIdx(FONT_BTN_NUN_STK_RIGHT);
        }
    }
    return symbolToFontIdx(arg);
}

u8 dTagProcessor_c::getAlphaForAnimatedIcon(u8 arg) {
    f32 ret = 255.0f;
    f32 duration = dLyt_HIO_c::getAnimBtnTransitionDuration();
    if (arg >= FONT_BTN_NUN_STK_UP && arg <= FONT_BTN_NUN_STK_RIGHT) {
        if (mButtonPressAnimTimer < dLyt_HIO_c::getAnimBtnTransitionDuration()) {
            // Ramp-up, become visible
            ret = (mButtonPressAnimTimer / duration) * ret;
        } else if (mButtonPressAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration()) {
            // Fully visible
            ret = 255.0f;
        } else if (mButtonPressAnimTimer <
                   dLyt_HIO_c::getAnimBtnTotalDuration() + dLyt_HIO_c::getAnimBtnTransitionDuration()) {
            // Ramp-down, become invisible
            ret = (dLyt_HIO_c::getAnimBtnTotalDuration() + dLyt_HIO_c::getAnimBtnTransitionDuration() -
                   mButtonPressAnimTimer) /
                  duration * ret;
        } else {
            // Invisible
            ret = 0.0f;
        }
    } else if (arg >= FONT_BTN_NUN_STK_WIGGLE_VERTICAL && arg <= FONT_BTN_NUN_STK_WIGGLE_HORIZONTAL) {
        if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTransitionDuration()) {
            // Ramp-up direction variant, become visible
            ret = (mButtonWiggleAnimTimer / duration) * ret;
        } else if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration()) {
            // Direction fully visible
            ret = 255.0f;
        } else if (mButtonWiggleAnimTimer <
                   dLyt_HIO_c::getAnimBtnTotalDuration() + dLyt_HIO_c::getAnimBtnTransitionDuration()) {
            // Ramp-down direction variant, become invisible
            ret = (dLyt_HIO_c::getAnimBtnTotalDuration() + dLyt_HIO_c::getAnimBtnTransitionDuration() -
                   mButtonWiggleAnimTimer) /
                  duration * ret;
        } else if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 2) {
            // Invisible
            ret = 0.0f;
        } else if (mButtonWiggleAnimTimer <
                   dLyt_HIO_c::getAnimBtnTransitionDuration() + dLyt_HIO_c::getAnimBtnTotalDuration() * 2) {
            // Ramp-up base variant, become visible
            ret = (mButtonWiggleAnimTimer - 2 * dLyt_HIO_c::getAnimBtnTotalDuration()) / duration * ret;
        } else if (mButtonWiggleAnimTimer < dLyt_HIO_c::getAnimBtnTotalDuration() * 3) {
            // Base variant fully visible
            ret = 255.0f;
        } else if (mButtonWiggleAnimTimer <
                   dLyt_HIO_c::getAnimBtnTransitionDuration() + dLyt_HIO_c::getAnimBtnTotalDuration() * 3) {
            // Ramp-down base variant, become invisible
            ret = ((dLyt_HIO_c::getAnimBtnTransitionDuration() + 3 * dLyt_HIO_c::getAnimBtnTotalDuration()) -
                   mButtonWiggleAnimTimer) /
                  duration * ret;
        } else {
            // Invisible
            ret = 0.0f;
        }
    }

    return ret;
}

void dTagProcessor_c::setStringArg(const wchar_t *arg, s32 index) {
    for (int i = 0; i < 0x40; i++) {
        mStringArgs[index][i] = arg[i];
    }
}

f32 dTagProcessor_c::fn_800B8040(s8 factor, u32 windowType) {
    // Fun little recursion here
    f32 f1 = dLyt_HIO_c::getFn800B1F70();
    if (windowType == dLytMsgWindow_c::MSG_WINDOW_WOOD) {
        f32 f2 = fn_800B8040(0, 0);
        f32 f3 = fn_800B8040(factor, 0);
        return f1 * ((f3 / f2) * 0.93f);
    } else if (windowType == dLytMsgWindow_c::MSG_WINDOW_STONE) {
        f32 f2 = fn_800B8040(0, 0);
        f32 f3 = fn_800B8040(factor, 0);
        return f1 * ((f3 / f2) * 0.9f);
    } else if (windowType == dLytMsgWindow_c::MSG_WINDOW_LINK) {
        f32 f2 = fn_800B8040(0, 0);
        f32 f3 = fn_800B8040(factor, 0);
        return f1 * ((f3 / f2) * 0.68f);
    } else if (windowType == dLytMsgWindow_c::MSG_WINDOW_DEMO) {
        f32 f2 = fn_800B8040(0, 0);
        f32 f3 = fn_800B8040(factor, 0);
        return f1 * ((f3 / f2) * 0.9f);
    } else if (windowType == dLytMsgWindow_c::MSG_WINDOW_34) {
        f32 f2 = fn_800B8040(0, 0);
        f32 f3 = fn_800B8040(factor, 0);
        return f1 * ((f3 / f2) * 0.86f);
    } else {
        f32 x;
        switch (factor) {
            case -2: x = 0.55f; break;
            case -1: x = 0.68f; break;
            case -0: x = 0.8f; break;
            case 1:  x = 0.95f; break;
            case 2:  x = 1.1f; break;
        }
        x *= dLyt_HIO_c::getFn800B1F10();
        return x * f1;
    }
}

// Copied exactly from nw4r::ut::CharWriter
void dTagProcessor_c::SetupGXCommon() {
    static const nw4r::ut::Color fog = 0;

    GXSetFog(GX_FOG_NONE, fog, 0.0f, 0.0f, 0.0f, 0.0f);
    GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
    GXSetZTexture(GX_ZT_DISABLE, GX_TF_Z8, 0);
    GXSetNumChans(1);
    GXSetChanCtrl(GX_COLOR0A0, FALSE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
    GXSetChanCtrl(GX_COLOR1A1, FALSE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
    GXSetNumTexGens(1);
    GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, FALSE, GX_DUALMTX_IDENT);
    GXSetNumIndStages(0);
    GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_SET);
}

// Copied from nw4r::ut::CharWriter with one modification
void dTagProcessor_c::SetupGXWithColorMapping(nw4r::ut::Color min, nw4r::ut::Color max) {
    GXSetNumTevStages(2);
    GXSetTevDirect(GX_TEVSTAGE0);
    GXSetTevDirect(GX_TEVSTAGE1);

    GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
    GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);
    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);

    GXSetTevColor(GX_TEVREG0, min);
    GXSetTevColor(GX_TEVREG1, max);

    // GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_C0, GX_CC_C1, GX_CC_TEXC, GX_CC_ZERO);
    GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO);
    GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_A0, GX_CA_A1, GX_CA_TEXA, GX_CA_ZERO);
    GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
    GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);

    GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
    GXSetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_CPREV, GX_CC_RASC, GX_CC_ZERO);
    GXSetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_APREV, GX_CA_RASA, GX_CA_ZERO);
    GXSetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
    GXSetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
}

// Copied exactly from nw4r::ut::CharWriter
void dTagProcessor_c::SetupVertexFormat() {
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U16, 15);

    GXClearVtxDesc();
    GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
    GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
    GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
}

f32 dTagProcessor_c::getMarginForCenteredLine(s32 lineIdx) {
    nw4r::lyt::Size size = mpTextBox->GetSize();
    f32 margin = (size.width - getLineWidth(lineIdx)) / 2.0f;
    if (margin < 0.0f) {
        margin = 0.0f;
    }
    return margin;
}

f32 dTagProcessor_c::fn_800B85C0(s32 lineIdx) {
    nw4r::lyt::Size size = mpTextBox->GetSize();
    nw4r::lyt::Size fontSize = mpTextBox->GetFontSize();

    s32 v = 0;
    s32 u = 0;

    s32 numLines = getMaxNumLines(mMsgWindowSubtype);
    s32 rem = lineIdx % numLines;
    if (rem != 0) {
        lineIdx -= rem;
    }
    // similar code in fn_800B4FF0
    for (s32 i = lineIdx; i < getMaxNumLines(mMsgWindowSubtype) + lineIdx && i < 0x32; i++) {
        if (getLineWidth(i) > 0.0f) {
            v++;
            if (u != 0) {
                v += u;
                u = 0;
            }
        } else {
            u++;
        }
    }

    f32 ret = 0.0f;
    if (u < getMaxNumLines(mMsgWindowSubtype)) {
        // Begin unused code - the results here are unused and disappear
        // in the assembly, so we don't know which fields are accessed in
        // the tmp vars, but control flow from the inlines remains
        f32 w1 = fontSize.width * 0.5f;
        f32 h1 = fontSize.height * 0.5f;
        if (w1 < dLyt_HIO_c::getField0x758() || h1 < dLyt_HIO_c::getField0x758()) {
            if (w1 < h1) {
                f32 tmp = dLyt_HIO_c::getField0x758();
            } else {
                f32 tmp2 = dLyt_HIO_c::getField0x758();
            }
        }
        f32 tmp3 = dLyt_HIO_c::getField0x758();
        // End unused code

        f32 f7 = (fontSize.height + getTextBox()->GetLineSpace()) / 2.0f;
        ret = -(f7 * (getMaxNumLines(mMsgWindowSubtype) - v));
        if (getTextBox()->GetTextPositionV() == 2) {
            ret -=
                (size.height - (fontSize.height * getMaxNumLines(mMsgWindowSubtype) +
                                (mpTextBox->GetLineSpace() * (getMaxNumLines(mMsgWindowSubtype) - 1))));
        }
        if (ret > 0.0f) {
            ret = 0.0f;
        }
    }
    if (rem > 0) {
        ret -= rem * (fontSize.height + getTextBox()->GetLineSpace());
    }
    return ret;
}

s32 dTagProcessor_c::getMaxNumLines(s32 windowType) {
    if (windowType >= dLytMsgWindow_c::MSG_WINDOW_WOOD && windowType < dLytMsgWindow_c::MSG_WINDOW_WOOD + 2) {
        return 4;
    } else if (windowType == dLytMsgWindow_c::MSG_WINDOW_LINK) {
        return 2;
    } else if (windowType == dLytMsgWindow_c::MSG_WINDOW_DEMO) {
        return 2;
    }
    return 4;
}

void dTagProcessor_c::getTextCommand(
    wchar_t _0xe, const wchar_t *src, u8 *outCmdLen, s32 *outCmd, wchar_t **outEndPtr
) {
    *outCmdLen = READ_U16(src, 4) + 6;
    *outCmd = READ_U32(src, 0);
    if (*outCmdLen > 6) {
        *outEndPtr = (wchar_t *)src + 3;
    } else {
        *outEndPtr = nullptr;
    }
}

void dTagProcessor_c::process0xFCommand(wchar_t _0xf, const wchar_t *src, s32 *outCmd) {
    *outCmd = READ_U32(src, 0);
}

s32 dTagProcessor_c::tick0x830() {
    if (field_0x830 >= 0) {
        field_0x830--;
    }
    if (field_0x830 < -1) {
        field_0x830 = -1;
    }
    return field_0x830;
}

void dTagProcessor_c::resetSomething() {
    field_0x810 = 0.0f;
    field_0x818 = 0.0f;
    field_0x814 = 0.0f;
    field_0xEE4 = 0;
}

s32 dTagProcessor_c::tickPauseFrame() {
    if (mPauseFramesLeft > 0) {
        mPauseFramesLeft--;
    }
    if (mPauseFramesLeft < 0) {
        mPauseFramesLeft = 0;
    }
    return mPauseFramesLeft;
}

void dTagProcessor_c::resetLineData() {
    for (int i = 0; i < 0x32; i++) {
        mLineData.mLineWidths[i] = 0.0f;
    }
    mLineData.mNumLines = 0;
}

f32 dTagProcessor_c::getLineWidth(s32 i) {
    return mLineData.mLineWidths[i];
}

void dTagProcessor_c::setNumericArg0(s32 arg) {
    mNumericArgs[0] = arg;
    mFirstNumericArg = arg;
}

void dTagProcessor_c::setNumericArgs(const s32 *args, s32 numArgs) {
    // Holy unrolling, Batman!
    for (int i = 0; i < numArgs; i++) {
        mNumericArgs[i] = args[i];
    }
}

void dTagProcessor_c::executeBtnPressTimer() {
    mButtonPressAnimTimer++;
    if (mButtonPressAnimTimer >= dLyt_HIO_c::getAnimBtnTotalDuration() * 2) {
        mButtonPressAnimTimer = 0;
    }
}

void dTagProcessor_c::executeBtnWiggleTimer() {
    mButtonWiggleAnimTimer++;
    if (mButtonWiggleAnimTimer >= dLyt_HIO_c::getAnimBtnTotalDuration() * 4) {
        mButtonWiggleAnimTimer = 0;
    }
}

void dTagProcessor_c::execute() {
    executeBtnPressTimer();
    executeBtnWiggleTimer();
}
