//
// Generated by dtk
// Translation Unit: JUTException.cpp
//

#include "JSystem/JSystem.h" // IWYU pragma: keep

#include "JSystem/JUtility/JUTException.h"
#include "JSystem/JUtility/JUTConsole.h"
#include "JSystem/JUtility/JUTDirectFile.h"
#include "JSystem/JUtility/JUTDirectPrint.h"
#include "math.h"
#include "stdio.h"
#include "stdlib.h"
#include "new.h"
#include "dolphin/base/PPCArch.h"
#include "dolphin/gx/GX.h"
#include "dolphin/os/OS.h"
#include "dolphin/vi/vi.h"

struct CallbackObject {
    /* 0x00 */ JUTExceptionUserCallback callback;
    /* 0x04 */ u16 error;
    /* 0x06 */ u16 pad_0x06;
    /* 0x08 */ OSContext* context;
    /* 0x0C */ int param_3;
    /* 0x10 */ int param_4;
};

void search_name_part(u8*, u8*, int);

OSMessageQueue JUTException::sMessageQueue = {};
static OSTime c3bcnt[4] = {0, 0, 0, 0};
const char* JUTException::sCpuExpName[] = {
    "SYSTEM RESET",
    "MACHINE CHECK",
    "DSI",
    "ISI",
    "EXTERNAL INTERRUPT",
    "ALIGNMENT",
    "PROGRAM",
    "FLOATING POINT",
    "DECREMENTER",
    "SYSTEM CALL",
    "TRACE",
    "PERFORMACE MONITOR",
    "BREAK POINT",
    "SYSTEM INTERRUPT",
    "THERMAL INTERRUPT",
    "PROTECTION",
    "FLOATING POINT",
};
JUTException* JUTException::sErrorManager;
JUTExceptionUserCallback JUTException::sPreUserCallback;
JUTExceptionUserCallback JUTException::sPostUserCallback;

/* 802C4AC8-802C4BAC       .text __ct__12JUTExceptionFP14JUTDirectPrint */
JUTException::JUTException(JUTDirectPrint* directPrint) : JKRThread(0x4000, 0x10, 0) {
    mDirectPrint = directPrint;
    OSSetErrorHandler(EXCEPTION_DSI, (OSErrorHandler)errorHandler);
    OSSetErrorHandler(EXCEPTION_ISI, (OSErrorHandler)errorHandler);
    OSSetErrorHandler(EXCEPTION_PROGRAM, (OSErrorHandler)errorHandler);
    OSSetErrorHandler(EXCEPTION_ALIGNMENT, (OSErrorHandler)errorHandler);
    OSSetErrorHandler(EXCEPTION_MEMORY_PROTECTION, (OSErrorHandler)errorHandler);
    setFPException(0);

    sPreUserCallback = NULL;
    sPostUserCallback = NULL;

    setGamePad(NULL);

    this->mPrintWaitTime0 = 10;
    this->mPrintWaitTime1 = 10;
    this->mTraceSuppress = -1;
    this->field_0x98 = 0;
    this->mPrintFlags =
        JUT_PRINT_GPR | JUT_PRINT_GPR_MAP | JUT_PRINT_SRR0_MAP | JUT_PRINT_FLOAT | JUT_PRINT_STACK;
}

/* 802C4BAC-802C4C14       .text create__12JUTExceptionFP14JUTDirectPrint */
JUTException* JUTException::create(JUTDirectPrint* directPrint) {
    if (!sErrorManager) {
        JKRHeap* systemHeap = JKRGetSystemHeap();
        sErrorManager = new (systemHeap, 0) JUTException(directPrint);
        sErrorManager->resume();
    }

    return sErrorManager;
}

OSMessage JUTException::sMessageBuffer[1] = {0};

/* 802C4C14-802C4CEC       .text run__12JUTExceptionFv */
void* JUTException::run() {
    PPCMtmsr(PPCMfmsr() & ~0x0900);
    OSInitMessageQueue(&sMessageQueue, sMessageBuffer, 1);
    OSMessage message;
    while (true) {
        OSReceiveMessage(&sMessageQueue, &message, OS_MESSAGE_BLOCK);
        CallbackObject* cb = (CallbackObject*)message;
        JUTExceptionUserCallback callback = cb->callback;
        u16 error = cb->error;
        OSContext* context = cb->context;
        int r24 = cb->param_3;
        int r23 = cb->param_4;
        mFrameMemory = (JUTExternalFB*)sErrorManager->mDirectPrint->getFrameBuffer();
        if (!sErrorManager->mDirectPrint->getFrameBuffer()) {
            sErrorManager->createFB();
        }
        if (callback) {
            callback(error, context, r24, r23);
        }
        OSDisableInterrupts();
        sErrorManager->printContext(error, context, r24, r23);
    }
}

static CallbackObject exCallbackObject;
void* JUTException::sConsoleBuffer;
u32 JUTException::sConsoleBufferSize;
JUTConsole* JUTException::sConsole;
u32 JUTException::msr;
u32 JUTException::fpscr;

/* 802C4CEC-802C5084       .text errorHandler__12JUTExceptionFUsP9OSContextUlUl */
void JUTException::errorHandler(OSError error, OSContext* context, u32 param_3, u32 param_4) {
    if (error == 0x10) {
        OSReport("\x1b[41;37m");
#if VERSION == VERSION_DEMO
        OSReport(" FPE: 浮動小数点例外が発生しました。アドレスは %08x\n", context->srr0);
#else
        OSReport(" FPE: 浮動小数点例外が発生しました。アドレスは %08x fpscr=%08x\n", context->srr0, context->fpscr);
#endif
        u8 stack_38[0x20];
        u32 stack_3c;
        u32 stack_40;
        u32 stack_44;
        u32 stack_48;
        if (searchPartialModule(context->srr0, &stack_40, &stack_44, &stack_48, &stack_3c) == 1) {
            search_name_part((u8*)stack_3c, stack_38, 0x20);
            OSReport("%s:%x section:%d\n", stack_38, stack_48, stack_44);
        }
        u32 ctx_fpcsr = context->fpscr;
        ctx_fpcsr &= ((ctx_fpcsr & 0xf8) << 22) | 0x01f80700;

        if (ctx_fpcsr & 0x20000000) {
            context->fpscr &= ~0x80;
            OSReport(" FPE: Invalid operation(無効な演算)\n");
            if (fpscr & 0x01000000) {
                OSReport(" SNaN\n");
            }
            if (fpscr & 0x00800000) {
                OSReport(" Infinity - Infinity\n");
            }
            if (fpscr & 0x00400000) {
                OSReport(" Infinity / Infinity\n");
            }
            if (fpscr & 0x00200000) {
                OSReport(" 0 / 0\n");
            }
            if (fpscr & 0x00100000) {
                OSReport(" Infinity * 0\n");
            }
            if (fpscr & 0x00080000) {
                OSReport(" Invalid compare\n");
            }
            if (fpscr & 0x0400) {
                OSReport(" Software request\n");
            }
            if (fpscr & 0x0200) {
                OSReport(" Invalid square root\n");
            }
            if (fpscr & 0x0100) {
                OSReport(" Invalid integer convert\n");
            }
        }
        if (ctx_fpcsr & 0x10000000) {
            context->fpscr &= ~0x40;
            OSReport(" FPE: Overflow(オーバーフロー)\n");
        }
        if (ctx_fpcsr & 0x08000000) {
            context->fpscr &= ~0x20;
            OSReport(" FPE: Underflow(アンダーフロー)\n");
        }
        if (ctx_fpcsr & 0x04000000) {
            context->fpscr &= ~0x10;
            OSReport(" FPE: Zero division(０による割り算)\n");
        }
        if (ctx_fpcsr & 0x02000000) {
            context->fpscr &= ~0x08;
            OSReport(" FPE: Inexact result(不正確な結果)\n");
        }
        OSReport("\x1b[m");
        return;
    }

    msr = PPCMfmsr();
    fpscr = context->fpscr;
    OSFillFPUContext(context);
    OSSetErrorHandler(error, NULL);
    if (error == OS_ERROR_MEMORY_PROTECTION) {
        OSProtectRange(0, NULL, 0, 3);
        OSProtectRange(1, NULL, 0, 3);
        OSProtectRange(2, NULL, 0, 3);
        OSProtectRange(3, NULL, 0, 3);
    }

    exCallbackObject.callback = sPreUserCallback;
    exCallbackObject.error = error;
    exCallbackObject.context = context;
    exCallbackObject.param_3 = param_3;
    exCallbackObject.param_4 = param_4;

    OSSendMessage(&sMessageQueue, &exCallbackObject, OS_MESSAGE_BLOCK);
    OSEnableScheduler();
    OSYieldThread();
}

/* 802C5084-802C50CC       .text setFPException__12JUTExceptionFUl */
void JUTException::setFPException(u32 fpscr_enable_bits) {
    __OSFpscrEnableBits = fpscr_enable_bits;
    if (fpscr_enable_bits) {
        OSSetErrorHandler(EXCEPTION_FLOATING_POINT_EXCEPTION, (OSErrorHandler)errorHandler);
    } else {
        OSSetErrorHandler(EXCEPTION_FLOATING_POINT_EXCEPTION, NULL);
    }
}

/* 802C50CC-802C525C       .text showFloatSub__12JUTExceptionFif */
void JUTException::showFloatSub(int index, f32 value) {
    if (isnan(value)) {
        sConsole->print_f("F%02d: Nan      ", index);
    } else if (isinf(value)) {
        if (*((u8*)&value) & 0x80) {
            sConsole->print_f("F%02d:+Inf     ", index);
        } else {
            sConsole->print_f("F%02d:-Inf     ", index);
        }
    } else if (value == 0.0f) {
        sConsole->print_f("F%02d: 0.0      ", index);
    } else {
        sConsole->print_f("F%02d:%+.3E", index, value);
    }
}

/* 802C525C-802C5380       .text showFloat__12JUTExceptionFP9OSContext */
void JUTException::showFloat(OSContext* context) {
    if (!sConsole) {
        return;
    }

    sConsole->print("-------------------------------- FPR\n");
    for (int i = 0; i < 10; i++) {
        showFloatSub(i, context->fpr[i]);
        sConsole->print(" ");
        showFloatSub(i + 11, context->fpr[i + 11]);
        sConsole->print(" ");
        showFloatSub(i + 22, context->fpr[i + 22]);
        sConsole->print("\n");
    }
    showFloatSub(10, context->fpr[10]);
    sConsole->print(" ");
    showFloatSub(21, context->fpr[21]);
    sConsole->print("\n");
}

/* 802C5380-802C5440       .text searchPartialModule__12JUTExceptionFUlPUlPUlPUlPUl */
bool JUTException::searchPartialModule(u32 address, u32* module_id, u32* section_id, u32* section_offset, u32* name_offset) {
    if (!address) {
        return false;
    }

    OSModuleInfo* module = __OSModuleList.head;
    for (; module != NULL; module = (OSModuleInfo*)module->link.next) {
        OSSectionInfo* section = (OSSectionInfo*)module->sectionInfoOffset;
        for (u32 i = 0; i < module->numSections; section++, i++) {
            if (section->size != 0) {
                u32 addr = section->offset & ~0x01;
                if ((addr <= address) && (address < addr + section->size)) {
                    if (module_id)
                        *module_id = module->id;
                    if (section_id)
                        *section_id = i;
                    if (section_offset)
                        *section_offset = address - addr;
                    if (name_offset)
                        *name_offset = module->nameOffset;
                    return true;
                }
            }
        }
    }

    return false;
}

/* 802C5440-802C54B8       .text search_name_part__FPUcPUci */
void search_name_part(u8* src, u8* dst, int dst_length) {
    for (u8* p = src; *p; p++) {
        if (*p == '\\') {
            src = p;
        }
    }

    if (*src == '\\') {
        src++;
    }

    for (int i = 0; (*src != 0) && (i < dst_length);) {
        if (*src == '.')
            break;
        *dst++ = *src++;
        i++;
    }

    *dst = '\0';
}

/* 802C54B8-802C55BC       .text showStack__12JUTExceptionFP9OSContext */
void JUTException::showStack(OSContext* context) {
    if (!sConsole) {
        return;
    }

    u32 i;
    u32* stackPointer;
    sConsole->print("-------------------------------- TRACE\n");
    sConsole->print_f("Address:   BackChain   LR save\n");

    for (i = 0, stackPointer = (u32*)context->gpr[1]; (stackPointer != NULL) && (stackPointer != (u32*)0xFFFFFFFF) && (i++ < 0x10);) {
        if (i > mTraceSuppress) {
            sConsole->print("Suppress trace.\n");
            return;
        }

        sConsole->print_f("%08X:  %08X    %08X\n", stackPointer, stackPointer[0], stackPointer[1]);
        showMapInfo_subroutine(stackPointer[1], false);
        JUTConsoleManager* manager = JUTConsoleManager::sManager;
        manager->drawDirect(true);
        waitTime(mPrintWaitTime1);
        stackPointer = (u32*)stackPointer[0];
    }
}

/* 802C55BC-802C586C       .text showMainInfo__12JUTExceptionFUsP9OSContextUlUl */
void JUTException::showMainInfo(u16 error, OSContext* context, u32 dsisr, u32 dar) {
    if (!sConsole) {
        return;
    }

    sConsole->print_f("CONTEXT:%08XH  (%s EXCEPTION)\n", context, sCpuExpName[error]);

    if (error == OS_ERROR_FLOATING_POINT_EXCEPTION) {
        u32 flags = fpscr & (((fpscr & 0xf8) << 0x16) | 0x1f80700);
        if ((flags & 0x20000000) != 0) {
            sConsole->print_f(" FPE: Invalid operation\n");
            if ((fpscr & 0x1000000) != 0) {
                sConsole->print_f(" SNaN\n");
            }
            if ((fpscr & 0x800000) != 0) {
                sConsole->print_f(" Infinity - Infinity\n");
            }
            if ((fpscr & 0x400000) != 0) {
                sConsole->print_f(" Infinity / Infinity\n");
            }
            if ((fpscr & 0x200000) != 0) {
                sConsole->print_f(" 0 / 0\n");
            }
            if ((fpscr & 0x100000) != 0) {
                sConsole->print_f(" Infinity * 0\n");
            }
            if ((fpscr & 0x80000) != 0) {
                sConsole->print_f(" Invalid compare\n");
            }
            if ((fpscr & 0x400) != 0) {
                sConsole->print_f(" Software request\n");
            }
            if ((fpscr & 0x200) != 0) {
                sConsole->print_f(" Invalid square root\n");
            }
            if ((fpscr & 0x100) != 0) {
                sConsole->print_f(" Invalid integer convert\n");
            }
        }
        if ((flags & 0x10000000) != 0) {
            sConsole->print_f(" FPE: Overflow\n");
        }
        if ((flags & 0x8000000) != 0) {
            sConsole->print_f(" FPE: Underflow\n");
        }
        if ((flags & 0x4000000) != 0) {
            sConsole->print_f(" FPE: Zero division\n");
        }
        if ((flags & 0x2000000) != 0) {
            sConsole->print_f(" FPE: Inexact result\n");
        }
    }
    sConsole->print_f("SRR0:   %08XH   SRR1:%08XH\n", context->srr0, context->srr1);
    sConsole->print_f("DSISR:  %08XH   DAR: %08XH\n", dsisr, dar);
}

/* 802C586C-802C592C       .text showGPR__12JUTExceptionFP9OSContext */
void JUTException::showGPR(OSContext* context) {
    if (!sConsole) {
        return;
    }

    sConsole->print("-------------------------------- GPR\n");
    for (int i = 0; i < 10; i++) {
        sConsole->print_f("R%02d:%08XH  R%02d:%08XH  R%02d:%08XH\n", i, context->gpr[i], i + 11,
                          context->gpr[i + 11], i + 22, context->gpr[i + 22]);
    }
    sConsole->print_f("R%02d:%08XH  R%02d:%08XH\n", 10, context->gpr[10], 21, context->gpr[21]);
}

JSUList<JUTException::JUTExMapFile> JUTException::sMapFileList(false);

/* 802C592C-802C5A88       .text showMapInfo_subroutine__12JUTExceptionFUlb */
bool JUTException::showMapInfo_subroutine(u32 address, bool begin_with_newline) {
    if ((address < 0x80000000) || (0x82ffffff < address)) {
        return false;
    }

    u32 name_offset;
    u32 module_id;
    u32 section_id;
    u32 section_offset;
    u8 name_part[36];

    const char* new_line = "\n";
    if (begin_with_newline == false) {
        new_line = "";
    }

    bool result =
        searchPartialModule(address, &module_id, &section_id, &section_offset, &name_offset);
    if (result == true) {
        search_name_part((u8*)name_offset, name_part, 32);
        sConsole->print_f("%s %s:%x section:%d\n", new_line, name_part, section_offset, section_id);
        begin_with_newline = false;
    }

    JSUListIterator<JUTException::JUTExMapFile> last = sMapFileList.getEnd();
    JSUListIterator<JUTException::JUTExMapFile> first = sMapFileList.getFirst();
    if (first != last) {
        u32 out_addr;
        u32 out_size;
        char out_line[256];

        if (result == true) {
            result =
                queryMapAddress((char*)name_part, section_offset, section_id, &out_addr, &out_size,
                                out_line, ARRAY_SIZE(out_line), true, begin_with_newline);
        } else {
            result = queryMapAddress(NULL, address, -1, &out_addr, &out_size, out_line,
                                     ARRAY_SIZE(out_line), true, begin_with_newline);
        }

        if (result == true) {
            return true;
        }
    }

    return false;
}

/* 802C5A88-802C5B94       .text showGPRMap__12JUTExceptionFP9OSContext */
void JUTException::showGPRMap(OSContext* context) {
    if (!sConsole) {
        return;
    }

    bool found_address_register = false;
    sConsole->print("-------------------------------- GPRMAP\n");

    for (int i = 0; i < 31; i++) {
        u32 address = context->gpr[i];

        if (address >= 0x80000000 && 0x83000000 - 1 >= address) {
            found_address_register = true;

            sConsole->print_f("R%02d: %08XH", i, address);
            if (!showMapInfo_subroutine(address, true)) {
                sConsole->print("  no information\n");
            }
            JUTConsoleManager::sManager->drawDirect(true);
            waitTime(mPrintWaitTime1);
        }
    }

    if (!found_address_register) {
        sConsole->print("  no register which seem to address.\n");
    }
}

/* 802C5B94-802C5C58       .text showSRR0Map__12JUTExceptionFP9OSContext */
void JUTException::showSRR0Map(OSContext* context) {
    if (!sConsole) {
        return;
    }

    sConsole->print("-------------------------------- SRR0MAP\n");
    u32 address = context->srr0;
    if (address >= 0x80000000 && 0x83000000 - 1 >= address) {
        sConsole->print_f("SRR0: %08XH", address);
        if (showMapInfo_subroutine(address, true) == false) {
            sConsole->print("  no information\n");
        }
        JUTConsoleManager::getManager()->drawDirect(true);
    }
}

/* 802C5C58-802C5D00       .text printDebugInfo__12JUTExceptionFQ212JUTException9EInfoPageUsP9OSContextUlUl */
void JUTException::printDebugInfo(JUTException::EInfoPage page, OSError error, OSContext* context, u32 param_3, u32 param_4) {
    switch (page) {
    case EINFO_PAGE_GPR:
        return showGPR(context);
    case EINFO_PAGE_FLOAT:
        showFloat(context);
        if (sConsole) {
            sConsole->print_f(" MSR:%08XH\t FPSCR:%08XH\n", msr, fpscr);
        }
        break;
    case EINFO_PAGE_STACK:
        return showStack(context);
    case EINFO_PAGE_GPR_MAP:
        return showGPRMap(context);
    case EINFO_PAGE_SSR0_MAP:
        return showSRR0Map(context);
    }
}

/* 802C5D00-802C5D3C       .text isEnablePad__12JUTExceptionCFv */
bool JUTException::isEnablePad() const {
    if (mGamePad == (JUTGamePad*)0xFFFFFFFF)
        return true;

    if (mGamePadPort >= 0)
        return true;

    return mGamePad;
}

/* 802C5D3C-802C62A8       .text readPad__12JUTExceptionFPUlPUl */
bool JUTException::readPad(u32* out_trigger, u32* out_button) {
    bool result = false;
    OSTime start_time = OSGetTime();
    OSTime ms;
    do {
        OSTime end_time = OSGetTime();
        OSTime ticks = end_time - start_time;
        ms = ticks / (OS_TIMER_CLOCK / 1000);
    } while (ms < 0x32);

    if (mGamePad == (JUTGamePad*)0xffffffff) {
        JUTGamePad gamePad0(JUTGamePad::Port_1);
        JUTGamePad gamePad1(JUTGamePad::Port_2);
        JUTGamePad gamePad2(JUTGamePad::Port_3);
        JUTGamePad gamePad3(JUTGamePad::Port_4);
        JUTGamePad::read();

        c3bcnt[0] =
            (gamePad0.isPushing3ButtonReset() ? (c3bcnt[0] != 0 ? c3bcnt[0] : OSGetTime()) : 0);
        c3bcnt[1] =
            (gamePad1.isPushing3ButtonReset() ? (c3bcnt[1] != 0 ? c3bcnt[1] : OSGetTime()) : 0);
        c3bcnt[2] =
            (gamePad2.isPushing3ButtonReset() ? (c3bcnt[2] != 0 ? c3bcnt[2] : OSGetTime()) : 0);
        c3bcnt[3] =
            (gamePad3.isPushing3ButtonReset() ? (c3bcnt[3] != 0 ? c3bcnt[3] : OSGetTime()) : 0);

        OSTime resetTime0 = (c3bcnt[0] != 0) ? (OSGetTime() - c3bcnt[0]) : 0;
        OSTime resetTime1 = (c3bcnt[1] != 0) ? (OSGetTime() - c3bcnt[1]) : 0;
        OSTime resetTime2 = (c3bcnt[2] != 0) ? (OSGetTime() - c3bcnt[2]) : 0;
        OSTime resetTime3 = (c3bcnt[3] != 0) ? (OSGetTime() - c3bcnt[3]) : 0;

        gamePad0.checkResetCallback(resetTime0);
        gamePad1.checkResetCallback(resetTime1);
        gamePad2.checkResetCallback(resetTime2);
        gamePad3.checkResetCallback(resetTime3);

        if (out_trigger) {
            *out_trigger = gamePad0.getTrigger() | gamePad1.getTrigger() | gamePad2.getTrigger() |
                           gamePad3.getTrigger();
        }
        if (out_button) {
            *out_button = gamePad0.getButton() | gamePad1.getButton() | gamePad2.getButton() |
                          gamePad3.getButton();
        }

        result = true;
    } else if (mGamePadPort >= 0) {
        JUTGamePad gamePad(mGamePadPort);
        OSTime& gamePadTime = c3bcnt[0];
        gamePadTime =
            (gamePad.isPushing3ButtonReset() ? (gamePadTime != 0 ? gamePadTime : OSGetTime()) : 0);

        OSTime resetTime = (gamePadTime != 0) ? (OSGetTime() - gamePadTime) : 0;
        gamePad.checkResetCallback(resetTime);

        JUTGamePad::read();
        if (out_trigger) {
            *out_trigger = gamePad.getTrigger();
        }
        if (out_button) {
            *out_button = gamePad.getButton();
        }

        result = true;
    } else if (mGamePad) {
        JUTGamePad::read();
        if (out_trigger) {
            *out_trigger = mGamePad->getTrigger();
        }
        if (out_button) {
            *out_button = mGamePad->getButton();
        }

        result = true;
    }

    return result;
}

/* 802C62A8-802C6724       .text printContext__12JUTExceptionFUsP9OSContextUlUl */
void JUTException::printContext(OSError error, OSContext* context, u32 dsisr, u32 dar) {
    bool is_pad_enabled = isEnablePad() == 0;
    if (!sErrorManager->mDirectPrint->isActive()) {
        return;
    }

    if (!sConsole) {
        return;
    }

    sConsole->print_f("******** EXCEPTION OCCURRED! ********\nFrameMemory:%XH\n", getFrameMemory());

    int post_callback_executed = false;
    while (true) {
        showMainInfo(error, context, dsisr, dar);

        JUTConsoleManager::sManager->drawDirect(true);
        waitTime(mPrintWaitTime0);

        if ((mPrintFlags & JUT_PRINT_GPR) != 0) {
            printDebugInfo(EINFO_PAGE_GPR, error, context, dsisr, dar);
            JUTConsoleManager::sManager->drawDirect(true);
            waitTime(mPrintWaitTime0);
        }
        if ((mPrintFlags & JUT_PRINT_SRR0_MAP) != 0) {
            printDebugInfo(EINFO_PAGE_SSR0_MAP, error, context, dsisr, dar);
            JUTConsoleManager::sManager->drawDirect(true);
            waitTime(mPrintWaitTime0);
        }
        if ((mPrintFlags & JUT_PRINT_GPR_MAP) != 0) {
            printDebugInfo(EINFO_PAGE_GPR_MAP, error, context, dsisr, dar);
            JUTConsoleManager::sManager->drawDirect(true);
            waitTime(mPrintWaitTime0);
        }
        if ((mPrintFlags & JUT_PRINT_FLOAT) != 0) {
            printDebugInfo(EINFO_PAGE_FLOAT, error, context, dsisr, dar);
            JUTConsoleManager::sManager->drawDirect(true);
            waitTime(mPrintWaitTime0);
        }
        if ((mPrintFlags & JUT_PRINT_STACK) != 0) {
            printDebugInfo(EINFO_PAGE_STACK, error, context, dsisr, dar);
            JUTConsoleManager::sManager->drawDirect(true);
            waitTime(mPrintWaitTime1);
        }

        sConsole->print("--------------------------------\n");
        JUTConsoleManager::sManager->drawDirect(true);

        if (post_callback_executed == 0 && sPostUserCallback) {
            BOOL enable = OSEnableInterrupts();
            post_callback_executed = true;
            (*sPostUserCallback)(error, context, dsisr, dar);
            OSRestoreInterrupts(enable);
        }

        if (this->field_0x98 == 0 || !is_pad_enabled) {
            break;
        }

        sConsole->setOutput(sConsole->getOutput() & 1);
    }

    if (!is_pad_enabled) {
        OSEnableInterrupts();

        u32 button;
        u32 trigger;

        int down = 0;
        int up = 0;
        do {
            readPad(&trigger, &button);

            bool draw = false;
            if (trigger == 0x100) {
                sConsole->scrollToLastLine();
                draw = true;
            }

            if (trigger == 0x200) {
                sConsole->scrollToFirstLine();
                draw = true;
            }

            if (button == 8) {
                JUTConsole* console = sConsole;
                up = (down < 3) ? -1 : ((down < 5) ? -2 : ((down < 7) ? -4 : -8));

                console->scroll(up);
                draw = true;
                up = 0;
                down++;
            } else if (button == 4) {
                JUTConsole* console = sConsole;
                down = (up < 3) ? 1 : ((up < 5) ? 2 : ((up < 7) ? 4 : 8));

                console->scroll(down);
                draw = true;
                down = 0;
                up++;
            } else {
                down = 0;
                up = 0;
            }

            if (draw == true) {
                u32 start = VIGetRetraceCount();
                while (start == VIGetRetraceCount())
                    ;
                JUTConsoleManager::sManager->drawDirect(true);
            }

            waitTime(30);
        } while (true);
    }

    while (true) {
        sConsole->scrollToFirstLine();
        JUTConsoleManager::sManager->drawDirect(true);
        waitTime(2000);

        int line_offset;
        int used_line;
        u32 height;
    next:
        for (u32 i = sConsole->getHeight(); i > 0; i--) {
            sConsole->scroll(1);
            JUTConsoleManager::sManager->drawDirect(true);

            height = sConsole->getHeight();
            JUTConsole* console = sConsole;
            line_offset = console->getLineOffset();
            used_line = console->getUsedLine();
            if ((used_line - height) + 1U <= line_offset)
                break;
            waitTime(20);
        }

        waitTime(3000);
        height = sConsole->getHeight();
        JUTConsole* console = sConsole;
        line_offset = console->getLineOffset();
        used_line = console->getUsedLine();
        if ((used_line - height) + 1U <= line_offset) {
            continue;
        }
        goto next;
    }
}

/* 802C6724-802C67AC       .text waitTime__12JUTExceptionFl */
void JUTException::waitTime(s32 timeout_ms) {
    if (timeout_ms) {
        OSTime start_time = OSGetTime();
        OSTime ms;
        do {
            OSTime end_time = OSGetTime();
            OSTime ticks = end_time - start_time;
            ms = ticks / (OS_TIMER_CLOCK / 1000);
        } while (ms < timeout_ms);
    }
}

/* 802C67AC-802C6868       .text createFB__12JUTExceptionFv */
void JUTException::createFB() {
    GXRenderModeObj* renderMode = &GXNtsc480Int;
    void* end = (void*)OSGetArenaHi();
    u16 width = ALIGN_NEXT(renderMode->fbWidth, 16);
    u16 height = renderMode->xfbHeight;
    u32 pixel_count = width * height;
    u32 size = pixel_count * 2;

    void* begin = (void*)ALIGN_PREV((u32)end - size, 32);
    void* object = (void*)ALIGN_PREV((s32)begin - sizeof(JUTExternalFB), 32);
    new (object) JUTExternalFB(renderMode, GX_GM_1_7, begin, size);

    mDirectPrint->changeFrameBuffer(object);
    VIConfigure(renderMode);
    VISetNextFrameBuffer(begin);
    VISetBlack(FALSE);
    VIFlush();

    mFrameMemory = (JUTExternalFB*)object;
}

/* 802C6868-802C6878       .text setPreUserCallback__12JUTExceptionFPFUsP9OSContextUlUl_v */
JUTExceptionUserCallback JUTException::setPreUserCallback(JUTExceptionUserCallback callback) {
    JUTExceptionUserCallback previous = sPreUserCallback;
    sPreUserCallback = callback;
    return previous;
}

/* 802C6878-802C6888       .text setPostUserCallback__12JUTExceptionFPFUsP9OSContextUlUl_v */
JUTExceptionUserCallback JUTException::setPostUserCallback(JUTExceptionUserCallback callback) {
    JUTExceptionUserCallback previous = sPostUserCallback;
    sPostUserCallback = callback;
    return previous;
}

/* 802C6888-802C691C       .text appendMapFile__12JUTExceptionFPCc */
void JUTException::appendMapFile(const char* path) {
    if (!path) {
        return;
    }

    JSUListIterator<JUTExMapFile> iterator;
    for (iterator = sMapFileList.getFirst(); iterator != sMapFileList.getEnd(); ++iterator) {
        if (strcmp(path, iterator->mPath) == 0) {
            return;
        }
    }

    JUTExMapFile* mapFile = new JUTExMapFile((char*)path);
    sMapFileList.append(&mapFile->mLink);
}

/* 802C691C-802C6A0C       .text queryMapAddress__12JUTExceptionFPcUllPUlPUlPcUlbb */
bool JUTException::queryMapAddress(char* mapPath, u32 address, s32 section_id, u32* out_addr, u32* out_size, char* out_line, u32 line_length, bool print, bool begin_with_newline) {
    if (mapPath) {
        char buffer[80];
        snprintf(buffer, sizeof(buffer), "/maps/%s.map", mapPath);
        if (queryMapAddress_single(buffer, address, section_id, out_addr, out_size, out_line,
                                   line_length, print, begin_with_newline) == true)
        {
            return true;
        }
    } else if (sMapFileList.getFirst() != sMapFileList.getEnd()) {
        if (queryMapAddress_single(sMapFileList.getFirst()->getObject()->mPath, address, -1,
                                   out_addr, out_size, out_line, line_length, print,
                                   begin_with_newline) == true)
        {
            return true;
        }
    }

    return false;
}

/* 802C6A0C-802C6D60       .text queryMapAddress_single__12JUTExceptionFPcUllPUlPUlPcUlbb */
bool JUTException::queryMapAddress_single(char* mapPath, u32 address, s32 section_id, u32* out_addr, u32* out_size, char* out_line, u32 line_length, bool print, bool begin_with_newline) {
    if (!mapPath) {
        return false;
    }

    char section_name[16];
    char buffer[0x200];
    JUTDirectFile file;
    int section_idx = 0;
    if (!file.fopen(mapPath)) {
        return false;
    }

    bool result = false;
    bool found_section;

    while (true) {
        section_idx++;
        found_section = false;
        while (true) {
            char* src;
            char* dst;

            if (file.fgets(buffer, ARRAY_SIZE(buffer)) < 0)
                break;
            if (buffer[0] != '.')
                continue;

            int i = 0;
            src = buffer + 1;
            while (*src != '\0') {
                section_name[i] = *src;
                if (*src == ' ' || i == 0xf)
                    break;
                i++;
                src++;
            }

            section_name[i] = 0;
            if (*src == 0)
                break;

            if (src[1] == 's' && src[2] == 'e' && src[3] == 'c' && src[4] == 't') {
                found_section = true;
                break;
            }
        }

        if (!found_section)
            break;

        if (section_id >= 0 && section_id != section_idx)
            continue;

        int length;

        while (true) {
            if ((length = file.fgets(buffer, ARRAY_SIZE(buffer))) <= 4)
                break;
            if ((length < 28))
                continue;
            if (buffer[28] == '4') {
                u32 addr = ((buffer[18] - '0') << 28) | strtol(buffer + 19, NULL, 16);
                int size = strtol(buffer + 11, NULL, 16);
                if ((addr <= address && address < addr + size)) {
                    if (out_addr)
                        *out_addr = addr;

                    if (out_size)
                        *out_size = size;

                    if (out_line) {
                        const u8* src = (const u8*)&buffer[0x1e];
                        u8* dst       = (u8*)out_line;
                        u32 i         = 0;

                        for (i = 0; i < line_length - 1; ++src) {
                            if ((u32)(*src) < ' ' && (u32)*src != '\t')
                                break;
                            if ((*src == ' ' || (u32)*src == '\t') && (i != 0)) {
                                if (dst[-1] != ' ') {
                                    *dst = ' ';
                                    dst++;
                                    ++i;
                                }
                            } else {
                                *dst++ = *src;
                                i++;
                            }
                        }
                        if (i != 0 && dst[-1] == ' ') {
                            dst--;
                            i--;
                        }
                        *dst = 0;
                        if (print) {
                            if (begin_with_newline) {
                                sConsole->print("\n");
                            }
                            sConsole->print_f("  [%08X]: .%s [%08X: %XH]\n  %s\n", address, section_name, addr, size, out_line);
                            begin_with_newline = false;
                        }
                    }
                    result = true;
                    break;
                }
            }
        }

        if ((section_id < 0 || section_id != section_idx)) {
            continue;
        }
        if (print && begin_with_newline) {
            sConsole->print("\n");
        }
        break;
    }

    file.fclose();
    return result ? true : false;
}

/* 802C6D60-802C6E40       .text createConsole__12JUTExceptionFPvUl */
void JUTException::createConsole(void* console_buffer, u32 console_buffer_size) {
    if (!console_buffer || !console_buffer_size) {
        return;
    }

    u32 lines = JUTConsole::getLineFromObjectSize(console_buffer_size, 0x32);
    if (lines != 0) {
        sConsoleBuffer = console_buffer;
        sConsoleBufferSize = console_buffer_size;
        sConsole = JUTConsole::create(0x32, console_buffer, console_buffer_size);

        JUTConsoleManager* manager = JUTConsoleManager::sManager;
        manager->setDirectConsole(sConsole);

        sConsole->setFontSize(10.0, 6.0);
#if VERSION == VERSION_DEMO
        sConsole->setPosition(15, 26);
#else
        sConsole->setPosition(12, 40);
#endif
        sConsole->setHeight(23);
        sConsole->setVisible(true);
        sConsole->setOutput(JUTConsole::OUTPUT_OSREPORT | JUTConsole::OUTPUT_CONSOLE);
    }
}

/* 802C6E40-802C6E60       .text __ct__13JUTExternalFBFP16_GXRenderModeObj8_GXGammaPvUl */
JUTExternalFB::JUTExternalFB(GXRenderModeObj* renderMode, GXGamma gamma, void* buffer, u32 size) {
    mRenderMode = renderMode;
    mSize = size;
    field_0x0C = 1;
    mGamma = gamma;
    field_0x10 = false;
}
