#include "Famicom/famicomPriv.h"
#include "Famicom/famicomInternal.hpp"

#include "libc/string.h"
#include "dolphin/gx.h"
#include "dolphin/os.h"
#include "dolphin/mtx.h"
#include "dolphin/card.h"
#include "_mem.h"
#include "libultra/libultra.h"
#include "m_personal_id.h"
#include "JSystem/JUtility/JUTDbPrint.h"
#include "JSystem/JKernel/JKRFileFinder.h"
#include "JSystem/JKernel/JKRFileLoader.h"
#include "JSystem/JKernel/JKRArchive.h"
#include "JSystem/JKernel/JKRAram.h"
#include "JSystem/ResTIMG.h"
#include "JSystem/J2D/J2DGrafContext.h"
#include "libc64/sprintf.h"
#include "libjsys/jsyswrapper.h"
#include "jsyswrap_cpp.h"
#include "MSL_C/printf.h"
#include "jaudio_NES/emusound.h"
#include "JSystem/JUtility/JUTProcBar.h"
#include "JSystem/JUtility/JUTGamePad.h"
#include "nintendo_hi_0.h"
#include "m_nmibuf.h"
// #include "JSystem/J2D/J2DGrafContext.h" // needed for ~J2DOrthoGraph

/* For some reason, there are a bunch of unused implicit one-byte 4-byte-aligned bss variables */
#ifdef MUST_MATCH
static u8 __unused_implicit[0x40] ATTRIBUTE_ALIGN(32);
#endif

static u8 commentImageBuffer[CARD_COMMENT_SIZE + 0x5800] ATTRIBUTE_ALIGN(32);
u8 save_game_image = false;
FamicomCommon famicomCommon;
u8 famicomCommonSave[0x1980 + sizeof(FamicomSaveDataHeader)];
u8** nesrom_filename_ptrs = nullptr;
static char* nesrom_filename_strbuf = nullptr;
u8 InputValid[4];
u32 InputData[4];
u32 InputButton[4];
u32 InputTrigger[4];
u32 InputRepeat[4];
u32 InputRepeatCount[4];
static u8 filer_mode_enable = FALSE;
static u8 filer_mode = 0;
static u8 filer_demo_mode = FILER_DEMO_MODE_NORMAL;
static int filer_demo_count = 0;
static int nesrom_idx_loaded = 0;
static int nesrom_idx = 0;
static int nesrom_count = 0;
static int rapid_count = 0;
static u8 rapid_mode_a = FALSE;
static u8 rapid_mode_b = FALSE;
static u8 nines_over_double_click = FALSE;
static u8 nines_over_mode = FALSE;
static u8 hispeed_mode = FALSE;
static u8 slow_mode = FALSE;
static u8 slow_mode_sub = FALSE;
static u8 speed_show = FALSE;
static FAMICOM_GETSAVECHAN_PROC famicom_getSaveChanCallback = nullptr;
Famicom_MallocInfo* my_malloc_current = nullptr;
static JKRArchive* famicom_arc = nullptr;
static bool famicom_arc_mounting = FALSE;
static OSThread ksnes_thread;
static s8 errorChan = -1;

/* @unused decode_szs__FPUcPUc - static void decode_szs(u8*, u8*) */

static u8 calcSum(u8* data, size_t len) {
    u8 checksum = 0;
    size_t i;

    for (i = 0; i != len; i++) {
        checksum += *data++;
    }

    return checksum;
}

extern int famicom_getErrorChan() {
    return errorChan;
}

static void famicom_save_data_setup(FamicomSaveDataHeader* header, u32 size, u8* identifier) {
    if (strncmp((char*)header->name, (char*)identifier, FAMICOM_SAVE_DATA_NAME_LEN) == 0 &&
        calcSum((u8*)header, FAMICOM_SAVE_HEADER_SIZE) == 0 &&
        header->_08 == 1 &&
        header->_09 == 0 &&
        header->headerSize == FAMICOM_SAVE_HEADER_SIZE &&
        header->size == size) {
        // The header ID & checksum are correct.
        OSReport("ヘッダID&チェックサムは正しいです\n");
    }
    else {
        // Repairing the header.
        OSReport("ヘッダを修復します\n");
        header->_08 = 1;
        header->_09 = 0;
        header->headerSize = FAMICOM_SAVE_HEADER_SIZE;
        header->size = size;
        memcpy(header->name, identifier, FAMICOM_SAVE_DATA_NAME_LEN);
        int checksum = calcSum((u8*)header, FAMICOM_SAVE_HEADER_SIZE);
        header->checksum -= checksum;
    }

    u8* data = ((u8*)header) + header->headerSize;
    for (u32 i = 0; i < PLAYER_NUM; i++) {
        int versionTag = data[1] >> 4;
        int versionNo = data[1] & 0xF;

        if (data[0] == 'D' && calcSum(data, size) == 0 &&
            versionTag == 3 && versionNo <= 1 && // check version character is either '0' or '1'
            data[3] == (u8)i) {
            // The personal data checksum %d is correct.
            OSReport("個人データチェックサム%dは正しいです\n", i);
        }
        else {
            // Repairing personal data %d.
            OSReport("個人データ%dを修復します\n", i);
            data[0] = 'D';
            data[1] = '1';
            data[3] = i;
            data[2] -= calcSum(data, size);
        }

        data += size;
    }
}

static void famicom_save_data_init(FamicomSaveDataHeader* header, u32 size, u8* identifier) {
    bzero(header, FAMICOM_SAVE_HEADER_SIZE + size * PLAYER_NUM);
    header->no_save = TRUE;
    famicom_save_data_setup(header, size, identifier);
}

static int famicom_save_data_check(const FamicomSaveDataHeader* header, int ignore_player_no, const u8* identifier) {
    if (strncmp((char*)header->name, (char*)identifier, FAMICOM_SAVE_DATA_NAME_LEN) == 0) {
        // The header ID is correct.
        OSReport("ヘッダIDは正しいです\n");

        if (calcSum((u8*)header, FAMICOM_SAVE_HEADER_SIZE) == 0) {
            // The header checksum is correct.
            OSReport("ヘッダチェックサムは正しいです\n");

            if (((header->_08 == 1 && header->_09 == 0) || (header->_08 == 0 && header->_09 == 1)) && header->headerSize == FAMICOM_SAVE_HEADER_SIZE) {
                // The version and header size match.
                OSReport("バージョンとヘッダサイズが合致しました\n");

                int i;
                u8* data = ((u8*)header) + header->headerSize;

                for (i = 0; i < PLAYER_NUM; i++) {
                    if (i != ignore_player_no) {
                        if (data[0] != 'D' || calcSum(data, header->size) != 0) {
                            goto fail;
                        }

                        // The personal ID & data checksum %d is correct.
                        OSReport("個人ID&データチェックサム%dは正しいです\n", i);
                    }

                    data += header->size;
                }

                return 0;
            }
        }
    }

    fail:
    // The Famicom common save is invalid.
    OSReport("ファミコン共通セーブは不正です\n");
    return -1;
}

static void process_filer_mode() {
    static const Vec camPt = { 0.0f, 0.0f, 800.0f };
    static const Vec at = { 0.0f, 0.0f, -100.0f };
    static const Vec up = { 0.0f, 1.0f, 0.0f };
    Mtx44 m;
    Mtx34 lookat_m;

    GXInvalidateTexAll();
    GXInvalidateVtxCache();
    GXSetClipMode(GX_CLIP_DISABLE);
    GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
    GXSetCopyFilter(GX_FALSE, nullptr, GX_FALSE, nullptr);
    GXSetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
    GXSetZTexture(GX_ZT_DISABLE, GX_TF_Z8, 0);
    GXSetZCompLoc(GX_FALSE);
    GXSetColorUpdate(GX_TRUE);
    GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
    GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
    GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);
    GXSetTevSwapMode(GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0);
    GXSetTevSwapMode(GX_TEVSTAGE3, GX_TEV_SWAP0, GX_TEV_SWAP0);
    C_MTXOrtho(m, 0.0, -480.0, 0.0, 640.0, 0.0, 2000.0);
    GXSetProjection(m, GX_ORTHOGRAPHIC);
    C_MTXLookAt(lookat_m, &camPt, &up, &at);
    GXSetNumChans(1);
    GXSetNumTexGens(0);
    GXSetNumTevStages(1);
    GXSetNumIndStages(0);
    GXSetTevDirect(GX_TEVSTAGE0);
    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
    GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
    GXSetChanCtrl(GX_COLOR0A0, GX_FALSE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
    GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
    GXClearVtxDesc();
    GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
    GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_S16, 0);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
    GXSetCurrentMtx(GX_PNMTX0);
    GXLoadPosMtxImm(lookat_m, GX_PNMTX0);
    GXBegin(GX_QUADS, GX_VTXFMT0, 4);

    // Vertex 0
    GXPosition2s16(0, 0);
    GXColor1u32(0x00500000);
    // Vertex 1
    GXPosition2s16(640, 0);
    GXColor1u32(0x00000000);
    // Vertex 2
    GXPosition2s16(640, -480);
    GXColor1u32(0x00005000);
    // Vertex 3
    GXPosition2s16(0, -480);
    GXColor1u32(0x00000000);

    int start_idx;
    if (nesrom_idx < 10 || nesrom_count < 18) {
        start_idx = 0;
    }
    else if ((nesrom_idx + 10) > nesrom_count) {
        start_idx = nesrom_count - 18;
    }
    else {
        start_idx = nesrom_idx - 9;
    }

    JUTReport(20, 60, 1, "QFC ver.011012   (C)2001 Nintendo");
    if (filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
        JUTReport(30, 80, 1, "[%d/%d]   -auto demo-  R: cancel", nesrom_idx + 1, nesrom_count);
    }
    else {
        JUTReport(30, 80, 1, "[%d/%d]   R: back  B: demo", nesrom_idx + 1, nesrom_count);
    }

    if (famicomCommon.nesrom_memcard == FALSE) {
        for (u32 i = 0; i < 18; i++) {
            u32 idx = start_idx + i;

            if (idx < nesrom_count) {
                JUTReport(30, 100 + i * 20, 1, "%s%s", (nesrom_idx == idx && (filer_demo_mode == FILER_DEMO_MODE_NORMAL || (filer_demo_count & 0x20) == 0)) ? "->" : "  ", nesrom_filename_ptrs[idx]);
            }
        }
    }

    if (famicomCommon.nesrom_memcard == FALSE) {
        u32 cur_idx;
    
        if ((InputTrigger[0] & (JUTGamePad::MAINSTICK_LEFT | JUTGamePad::DPAD_LEFT))) {
            cur_idx = nesrom_idx;
            nesrom_idx--;
    
            do {
                nesrom_idx--;
    
                while (nesrom_idx < 0) {
                    nesrom_idx += nesrom_count;  
                }
            } while (nesrom_filename_ptrs[nesrom_idx][0] != ':' && cur_idx != nesrom_idx);
        }
    
        if ((InputTrigger[0] & (JUTGamePad::MAINSTICK_RIGHT | JUTGamePad::DPAD_RIGHT))) {
            cur_idx = nesrom_idx;
            do {
                nesrom_idx++;
    
                if (nesrom_idx >= nesrom_count) {
                    nesrom_idx = 0;
                }
            } while (nesrom_filename_ptrs[nesrom_idx][0] != ':' && cur_idx != nesrom_idx);
        }
    
        if ((InputRepeat[0] & (JUTGamePad::MAINSTICK_UP | JUTGamePad::DPAD_UP))) {
            cur_idx = nesrom_idx;
            do {
                if (nesrom_idx > 0) {
                    nesrom_idx--;
                }
                else {
                    nesrom_idx = nesrom_count - 1;
                }
            } while (nesrom_filename_ptrs[nesrom_idx][0] == ':' && cur_idx != nesrom_idx);
        }
    
        if ((InputRepeat[0] & (JUTGamePad::MAINSTICK_DOWN | JUTGamePad::DPAD_DOWN)) != 0) {
            if (nesrom_idx < nesrom_count - 1) {
                nesrom_idx++;
            }
            else {
                nesrom_idx = 0;
            }
        }
    }

    if ((InputTrigger[0] & BUTTON_DLEFT)) {
        filer_mode = 0;
        filer_demo_mode = FILER_DEMO_MODE_AUTO;
        filer_demo_count = 0;
    }
    
    if ((InputTrigger[0] & BUTTON_START)) {
        filer_mode = 0;
        filer_demo_mode = FILER_DEMO_MODE_NORMAL;
    }
}

extern void famicom_setCallback_getSaveChan(FAMICOM_GETSAVECHAN_PROC proc) {
    famicom_getSaveChanCallback = proc;
}

static s32 memcard_mount(int, void*);

static const s32 checkTableNG[] = {
    CARD_RESULT_FATAL_ERROR,
    CARD_RESULT_IOERROR,
    CARD_RESULT_BROKEN,
    CARD_RESULT_ENCODING,
    CARD_RESULT_WRONGDEVICE
};

static s32 famicom_getSaveChan(int nesrom_memcard, s32* result) {
    s32 chan_result[2];
    s32 card_err;
    int chan;
    int mounted = FALSE;
    void* workArea = nullptr;
    char* file_name;

    chan_result[0] = CARD_RESULT_READY;
    chan_result[1] = CARD_RESULT_READY;

    if (nesrom_memcard == FALSE) {
        if (famicom_getSaveChanCallback != NULL) {
            chan = famicom_getSaveChanCallback(nullptr, chan_result);

            if (chan >= 0) {
                errorChan = chan;
            }
        }
        else {
            chan = -1;
            card_err = CARD_RESULT_FATAL_ERROR;
            goto exit;
        }
        
        if (chan < 0 && famicomCommon._8C) {
            nesrom_memcard = TRUE;
        }
    }

    if (nesrom_memcard != FALSE) {
        file_name = (char*)famicomCommon.mura_save_name;
        workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
        if (workArea == nullptr) {
            card_err = CARD_RESULT_FATAL_ERROR;
            chan = -1;
            goto exit;
        }

        for (chan = 0; chan < CARD_NUM_CHANS; chan++) {
            if (mounted) {
                CARDUnmount(chan);
            }
            
            s32 mount_res = memcard_mount(chan, workArea);
            chan_result[chan] = mount_res;
            mounted = mount_res == CARD_RESULT_READY;

            if (mounted) {
                if (file_name != nullptr) {
                    CARDFileInfo file_info;
                    card_err = CARDOpen(chan, file_name, &file_info);

                    chan_result[chan] = card_err;
                    if (card_err == CARD_RESULT_READY) {
                        CARDClose(&file_info);
                        OSReport("チェックファイル存在\n"); // Check file exists.
                        errorChan = chan;
                        goto exit;
                    }
                }

                CARDUnmount(chan);
                mounted = FALSE;
            }
        }
        
        chan = -1;
        card_err = CARD_RESULT_NOFILE;
    }

    if (chan < 0) {    
        for (int i = 0; i < ARRAY_COUNT(checkTableNG); i++) {        
            if (chan_result[0] == checkTableNG[i]) {
                card_err = *(s32*)(chan_result + 0);
                errorChan = 0;
                goto exit;
            }
            
            if (chan_result[1] == checkTableNG[i]) {
                card_err = *(s32*)(chan_result + 1);
                errorChan = 1;
                goto exit;
            }
        }

        if ((chan_result[0] == CARD_RESULT_NOFILE || chan_result[0] == CARD_RESULT_NOCARD || chan_result[0] == CARD_RESULT_READY) &&
            (chan_result[1] == CARD_RESULT_NOFILE || chan_result[1] == CARD_RESULT_NOCARD || chan_result[1] == CARD_RESULT_READY)) {
            card_err = nesrom_memcard ? CARD_RESULT_NOFILE : CARD_RESULT_NOCARD;
            errorChan = CARD_NUM_CHANS;
        }
        else {
            card_err = CARD_RESULT_FATAL_ERROR;
        }
    }

    exit:
    if (mounted) {
        CARDUnmount(chan);
    }

    if (workArea != nullptr) {
        my_free(workArea);
    }

    if (result != nullptr) {
        *result = card_err;
    }

    return chan;
}

static s32 memcard_mount(int chan, void* workArea) {
    s32 result;
    s32 sector_size;

    // Checking whether the memory card is inserted in the slot.
    OSReport("メモリーカードが、スロットに挿入されているかどうかを調べます。\n");

    do {
        result = CARDProbeEx(chan, nullptr, &sector_size);
    } while (result == CARD_RESULT_BUSY);

    if (result == CARD_RESULT_READY) {
        if (sector_size != 0x2000) {
            // The sector size is not 8KB.
            OSReport("セクターサイズが8KBではありません。\n");
            result = CARD_RESULT_WRONGDEVICE;
        }
        else {
            // Mounting the memory card.
            OSReport("メモリーカードをマウントする\n");
            result = CARDMount(chan, workArea, nullptr);
            
            if (result == CARD_RESULT_READY || result == CARD_RESULT_BROKEN || result == CARD_RESULT_ENCODING) {
                // The memory card has been mounted.
                OSReport("メモリーカードはマウント状態になった\n");

                if (result == CARD_RESULT_READY || result == CARD_RESULT_BROKEN) {
                    if (result == CARD_RESULT_BROKEN) {
                        // The memory card is damaged.
                        OSReport("メモリーカードは壊れています\n");
                        // Attempting to repair the memory card.
                        OSReport("メモリーカードの修復を試みます\n");
                    }
                    
                    result = CARDCheck(chan);
                    if (result != CARD_RESULT_READY) {
                        // Failed to repair the memory card.
                        OSReport("メモリーカードの修復に失敗しました\n");
                        CARDUnmount(chan);
                    }
                }
                else {
                    // The character encoding of the memory card does not match.
                    OSReport("メモリーカードの文字コードがあっていません\n");
                    CARDUnmount(chan);
                }
            }
        }
    }
    else if (result != CARD_RESULT_NOCARD) {
        // The memory card is not inserted.
        OSReport("メモリーカードは刺さってない\n");
    }

    if (result != CARD_RESULT_READY && result != CARD_RESULT_NOCARD && result != CARD_RESULT_FATAL_ERROR) {
        errorChan = chan;
    }

    return result;
}

static s32 memcard_getFreeBlocks(s32 chan, s32* unused_bytes, s32* unused_files) {
    s32 result;
    int mounted = FALSE;
    void* workArea = my_malloc(CARD_WORKAREA_SIZE, 32);

    if (workArea == nullptr) {
        result = CARD_RESULT_FATAL_ERROR;
    }
    else {
        result = memcard_mount(chan, workArea);
        mounted = result == CARD_RESULT_READY;

        if (mounted) {
            result = CARDFreeBlocks(chan, unused_bytes, unused_files);
        }
    }

    if (result != CARD_RESULT_READY && result != CARD_RESULT_NOCARD && result != CARD_RESULT_FATAL_ERROR) {
        errorChan = chan;
    }

    if (result != CARD_RESULT_READY) {
        // Error code = %d
        OSReport("エラーコード＝%d\n", result);
    }

    if (mounted) {
        CARDUnmount(chan);
    }

    if (workArea != nullptr) {
        my_free(workArea);
    }

    return result;
}

/* @unused @fabricated - strings necessary for matching */
static s32 memcard_list(int chan) {
    OSReport("No. Name Co B FileName                         (YYYY/MM/DD hh:mm:ss) IconAddr IFmt ISpd Pm Cp StB Lng Com\n");
    OSReport("%3d %.4s %.2s %d %-32.32s (%04d/%02d/%02d %02d:%02d:%02d) %03x %04x %04x %02x %2d %3d %3d %04x\n");
}

static size_t getBannerSizeFromFormat(u8 format) {
    switch (format) {
        case CARD_STAT_BANNER_C8:
            return 0xE00;
        case CARD_STAT_BANNER_RGB5A3:
            return 0x1800;
        default:
            return 0;
    }
}

static size_t getIconSizeFromFormat(u16 format) {
    size_t size = 0;
    int has_palette = FALSE;
    
    for (int i = 0; i < CARD_ICON_MAX; i++) {
        switch ((format >> (i * 2)) & CARD_STAT_ICON_MASK) {
            case CARD_STAT_ICON_C8:
            {
                has_palette = TRUE;
                size += 0x400;
                break;
            }

            case CARD_STAT_ICON_RGB5A3:
            {
                size += 0x800;
                break;
            }
        }
    }

    if (has_palette) {
        size += 0x200;
    }

    return size;
}

static int SetupResBanner(const ResTIMG*, u8*, size_t, size_t*, u8*);
static int SetupResIcon(const ResTIMG*, u8*, size_t, size_t*, u16*, u16*);

static void SetupExternCommentImage(u8* embedded_save_comment_img, u8* dst, u8* rom_file_comment_img) {
    u32 size;

    switch (famicomCommon.memcard_game_header.flags0.comment_type) {
        case MEMCARD_COMMENT_TYPE_NONE:
            break;
        case MEMCARD_COMMENT_TYPE_COPY_ROM: {
            memcpy(dst, rom_file_comment_img, CARD_COMMENT_SIZE);
            u8 temp = dst[CARD_COMMENT_SIZE];
            dst[CARD_COMMENT_SIZE] = '\0';
            char* image_name_end = strstr((char*)dst + 32, "] ROM ");
            dst[CARD_COMMENT_SIZE] = temp;

            if (image_name_end != nullptr) {
                strncpy(image_name_end, "] SAVE", 6);
                dst += CARD_COMMENT_SIZE;
                break;
            }
            // Fallthrough in the case where "] ROM " doesn't exist
        }
        case MEMCARD_COMMENT_TYPE_DEFAULT: {
            strncpy((char*)dst, "Animal Crossing", 32);
            strncpy((char*)dst + 32, "NES Cassette Save Data  ", 32);
            dst += CARD_COMMENT_SIZE;
            break;
        }
        case MEMCARD_COMMENT_TYPE_COPY_EMBEDDED: {
            memcpy(dst, embedded_save_comment_img, CARD_COMMENT_SIZE);
            dst += CARD_COMMENT_SIZE;
            embedded_save_comment_img += CARD_COMMENT_SIZE;
            break;
        }
    }

    rom_file_comment_img += CARD_COMMENT_SIZE;
    switch (famicomCommon.memcard_game_header.flags0.banner_type) {
        case MEMCARD_BANNER_TYPE_NONE:
            break;
        case MEMCARD_BANNER_TYPE_DEFAULT: {
            void* banner = JKRFileLoader::getGlbResource("/FAMICOM/newdisk_system.bti.szs");
            ResTIMG* banner_data = (ResTIMG*)banner;
            if (banner_data != nullptr) {
                u8 banner_fmt;
                SetupResBanner(banner_data, dst, 0x1800, &size, &banner_fmt);
                JKRFileLoader::removeResource(banner_data, nullptr);
                dst += size;
                famicomCommon.memcard_game_header.flags1.banner_fmt = banner_fmt;
            }
            break;
        }
        case MEMCARD_BANNER_TYPE_COPY_ROM: {
            size = getBannerSizeFromFormat(famicomCommon.memcard_game_header.flags1.banner_fmt);
            memcpy(dst, rom_file_comment_img, size);
            dst += size;
            break;
        }
        case MEMCARD_BANNER_TYPE_COPY_EMBEDDED: {
            size = getBannerSizeFromFormat(famicomCommon.memcard_game_header.flags1.banner_fmt);
            memcpy(dst, embedded_save_comment_img, size);
            dst += size;
            embedded_save_comment_img += size;
            break;
        }
    }

    size_t banner_size = getBannerSizeFromFormat(famicomCommon.memcard_game_header.flags1.banner_fmt);
    rom_file_comment_img += banner_size;
    switch (famicomCommon.memcard_game_header.flags0.icon_type) {
        case MEMCARD_ICON_TYPE_NONE:
            break;
        case MEMCARD_ICON_TYPE_DEFAULT: {
            void* icon = JKRFileLoader::getGlbResource("/FAMICOM/newdisk.bti.szs");
            ResTIMG* icon_data = (ResTIMG*)icon;
            if (icon_data != nullptr) {
                u16 icon_fmts;
                u16 icon_flags;
                SetupResIcon(icon_data, dst, 0x800, nullptr, &icon_fmts, &icon_flags);
                JKRFileLoader::removeResource(icon_data, nullptr);
                famicomCommon.memcard_game_header.icon_format = icon_fmts;
                famicomCommon.memcard_game_header.icon_flags = icon_flags;
                size = getIconSizeFromFormat(icon_fmts);
            }
            break;
        }
        case MEMCARD_ICON_TYPE_COPY_ROM: {
            size = getIconSizeFromFormat(famicomCommon.memcard_game_header.icon_format);
            memcpy(dst, rom_file_comment_img, size);
            break;
        }
        case MEMCARD_ICON_TYPE_COPY_EMBEDDED: {
            size = getIconSizeFromFormat(famicomCommon.memcard_game_header.icon_format);
            memcpy(dst, embedded_save_comment_img, size);
            break;
        }
    }
    
    size_t icon_size = getIconSizeFromFormat(famicomCommon.memcard_game_header.icon_format); // unnecessary
}

static void SetupInternalCommentImage(u8* data) {
    u8* data_p = data;

    // Setup comment
    famicomCommon.memcard_game_header.flags0.comment_type = MEMCARD_COMMENT_TYPE_DEFAULT;
    strncpy((char*)data, "Animal Crossing", 32);
    strncpy((char*)data + 32, "NES Save Data           ", 32);
    data += CARD_COMMENT_SIZE;

    // Setup banner
    famicomCommon.memcard_game_header.flags0.banner_type = MEMCARD_BANNER_TYPE_NONE;
    famicomCommon.memcard_game_header.flags1.banner_fmt = CARD_STAT_BANNER_NONE;

    // Setup icon
    void* icon = JKRFileLoader::getGlbResource("/FAMICOM/famikon.bti.szs");
    ResTIMG* icon_res = (ResTIMG*)icon;
    if (icon_res != nullptr) {
        u16 icon_fmts;
        u16 icon_flags;
        
        SetupResIcon(icon_res, data, 0x800, nullptr, &icon_fmts, &icon_flags);
        JKRFileLoader::removeResource(icon_res, nullptr);
        famicomCommon.memcard_game_header.flags0.icon_type = MEMCARD_ICON_TYPE_DEFAULT;
        famicomCommon.memcard_game_header.icon_format = icon_fmts;
        famicomCommon.memcard_game_header.icon_flags = icon_flags;
        data += getIconSizeFromFormat(icon_fmts);
    }
    else {
        famicomCommon.memcard_game_header.flags0.icon_type = MEMCARD_ICON_TYPE_NONE;
        famicomCommon.memcard_game_header.icon_format = 0;
        famicomCommon.memcard_game_header.icon_flags = 0;
    }

    famicomCommon.memcard_game_header.comment_img_size = (u32)data - (u32)data_p;
}

static s32 memcard_data_save(
    int chan,
    const char* file_name,
    const char* check_file_name,
    const FamicomSaveDataHeader* header,
    size_t save_data_size,
    const u8* comment_img,
    u8 banner_fmt,
    u32 icon_addr,
    u16 icon_fmts,
    u16 icon_flags,
    u32 comment_addr,
    int do_not_save
) {
    char temp_name[33];
    s32 result;
    size_t save_data_size_block_aligned;
    int mounted = FALSE;
    int opened = FALSE;
    void* workArea;
    u8* save_data = nullptr;
    void* buf = nullptr;
    CARDFileInfo fileInfo;
    u8 cardAttr;
    size_t save_size = 0;
    size_t comment_img_size;
    
    // format temp string using file_name as "file_name~"
    sprintf(temp_name, "%-.31s~", file_name);
    workArea = my_malloc(CARD_WORKAREA_SIZE, 32);

    if (workArea == nullptr) {
        result = CARD_RESULT_FATAL_ERROR;
        goto exit;
    }

    save_size = icon_addr != 0xFFFFFFFF ? icon_addr : 0;
    save_size += getBannerSizeFromFormat(banner_fmt);
    save_size += getIconSizeFromFormat(icon_fmts);
    comment_img_size = save_size;
    save_size += save_data_size;
    save_data_size_block_aligned = ALIGN_NEXT(save_size, 0x2000);
    save_data = (u8*)my_malloc(save_data_size_block_aligned, 32);

    if (save_data == nullptr) {
        result = CARD_RESULT_FATAL_ERROR;
        goto exit;
    }

    bzero(save_data, save_data_size_block_aligned);
    memcpy(save_data, comment_img, comment_img_size); // copy memcard file info data
    memcpy(save_data + comment_img_size, header, save_data_size); // copy data header & save data

    result = memcard_mount(chan, workArea);
    mounted = result == CARD_RESULT_READY;

    if (mounted) {
        if (check_file_name != nullptr) {
            result = CARDOpen(chan, check_file_name, &fileInfo);
            if (result != CARD_RESULT_READY) {
                // The check file does not exist.
                OSReport("チェックファイル存在しないよ\n");
                goto exit;
            }

            CARDClose(&fileInfo);
        }

        buf = my_malloc(save_data_size_block_aligned, 32);
        if (buf == nullptr) {
            result = CARD_RESULT_FATAL_ERROR;
            // Not enough memory!
            OSReport("メモリが足りん！\n");
            goto exit;
        }

        // Add the contents of the file (check for need to save).
        OSReport("ファイルの内容を取り込む(セーブの必要性チェック)\n");
        result = CARDOpen(chan, file_name, &fileInfo);
        if (result == CARD_RESULT_READY) {
            if (do_not_save) {
                result = CARD_RESULT_FATAL_ERROR;
                goto exit;
            }

            result = CARDRead(&fileInfo, buf, save_data_size_block_aligned, 0);
            if (result != CARD_RESULT_READY) {
                goto exit;
            }

            bool save_updated = bcmp(save_data, buf, save_data_size_block_aligned) == 0;
            if (save_updated) {
                // No need to save, so the process has completed successfully.
                OSReport("セーブする必要が無いので正常終了\n");
                goto exit;
            }

            // Need to save (update mode).
            OSReport("セーブする必要がある(更新モード)\n");
        }
        else {
            if (result != CARD_RESULT_NOFILE) {
                goto exit;
            }

            // Need to save (new creation mode).
            OSReport("セーブする必要がある(新規作成モード)\n");
        }

        result = CARDRename(chan, file_name, temp_name);
        if (result == CARD_RESULT_EXIST) {
            result = CARDDelete(chan, temp_name); // delete temp file
            if (result != CARD_RESULT_READY) {
                goto exit; // failed to delete
            }
            result = CARDRename(chan, file_name, temp_name); // try renaming again
            if (result != CARD_RESULT_READY) {
                goto exit; // failed to rename after deleting
            }
        }
        else if (result != CARD_RESULT_NOFILE && result != CARD_RESULT_READY) {
            goto exit;
        }

        // Creating file chan=%d [%s] length=%d.
        OSReport("ファイル作成 chan=%d [%s] length=%d\n", chan, file_name, save_data_size_block_aligned);
        result = CARDCreate(chan, file_name, save_data_size_block_aligned, &fileInfo);
        switch (result) {
            case CARD_RESULT_READY:
                break; // success
            case CARD_RESULT_NOENT:
            case CARD_RESULT_INSSPACE: {
                s32 del_res = CARDDelete(chan, temp_name);
                if (del_res == CARD_RESULT_NOFILE) {
                    goto exit; // temp file didn't exist?
                }

                // check for other errors
                if (del_res != CARD_RESULT_READY) {
                    result = del_res;
                    goto exit;
                }

                result = CARDCreate(chan, file_name, save_data_size_block_aligned, &fileInfo);
                if (result != CARD_RESULT_READY) {
                    goto exit; // still failed to create
                }
                break;
            }
            default:
                // Failed to create file.
                OSReport("ファイル作成失敗\n");
                goto exit;
        }

        CARDGetAttributes(chan, fileInfo.fileNo, &cardAttr);
        u8 saved_attr = cardAttr;

        if ((famicomCommon.memcard_game_header.flags0.no_copy_flag)) {
            saved_attr |= CARD_ATTR_NO_COPY;
        }

        if ((famicomCommon.memcard_game_header.flags1.no_move_flag)) {
            saved_attr |= CARD_ATTR_NO_MOVE;
        }

        saved_attr |= cardAttr;
        if ((saved_attr) != 0) {
            CARDSetAttributes(chan, fileInfo.fileNo, saved_attr);
        }

        // Writing the entire file.
        OSReport("ファイル全体の書き込み\n");
        result = CARDWrite(&fileInfo, save_data, save_data_size_block_aligned, 0);
        if (result == CARD_RESULT_READY) {
            // Verifying the entire file.
            OSReport("ファイル全体のベリファイ\n");
            result = CARDRead(&fileInfo, buf, save_data_size_block_aligned, 0);
            if (result == CARD_RESULT_READY) {
                if (bcmp(save_data, buf, save_data_size_block_aligned) == 0) {
                    // Verification successful.
                    OSReport("ベリファイ成功\n");
                }
                else {
                    // Verification failed.
                    OSReport("ベリファイ失敗\n");
                    result = CARD_RESULT_IOERROR;
                    goto exit;
                }
                
                // Close.
                OSReport("クローズ\n");
                result = CARDClose(&fileInfo);
                if (result != CARD_RESULT_READY) {
                    // Failed to close?
                    OSReport("クローズに失敗って？\n");
                    goto exit;
                }
                else {
                    // Get status.
                    opened = FALSE;
                    OSReport("ステータス取得\n");
                }

                CARDStat status;
                result = CARDGetStatus(chan, fileInfo.fileNo, &status);
                if (result != CARD_RESULT_READY) {
                    // Failed to get status.
                    OSReport("ステータス取得失敗\n");
                    goto exit;
                }

                status.bannerFormat = banner_fmt;
                status.iconSpeed = icon_flags;
                status.iconAddr = icon_addr;
                status.iconFormat = icon_fmts;
                status.commentAddr = comment_addr;
                // Change status.
                OSReport("ステータス変更\n");
                result = CARDSetStatus(chan, fileInfo.fileNo, &status);
                if (result != CARD_RESULT_READY) {
                    // Failed to change status.
                    OSReport("ステータス変更失敗\n");
                }
                else {
                    result = CARDDelete(chan, temp_name);
                    if (result == CARD_RESULT_NOFILE) {
                        result = CARD_RESULT_READY; // successfully deleted temp file
                    }
                }
            }
        }
    }

exit:
    if (result != CARD_RESULT_READY && result != CARD_RESULT_NOCARD && result != CARD_RESULT_FATAL_ERROR) {
        errorChan = chan;
    }

    if (result != CARD_RESULT_READY) {
        // Error code = %d.
        OSReport("エラーコード＝%d\n", result);
    }

    if (buf != nullptr) {
        my_free(buf);
    }

    if (save_data != nullptr) {
        my_free(save_data);
    }

    if (opened) {
        CARDClose(&fileInfo);
    }

    if (mounted) {
        CARDUnmount(chan);
    }

    if (workArea != nullptr) {
        my_free(workArea);
    }

    return result;
}

static s32 memcard_data_load(
    int chan,
    const char* file_name,
    const char* check_file_name,
    FamicomSaveDataHeader* save_header,
    size_t save_data_size,
    const u8* comment_img
) {
    char temp_name[33];
    s32 result;
    int mounted = FALSE;
    void* workArea;
    u8* save_data = nullptr;
    int i;
    void* buf = nullptr;
    CARDFileInfo fileInfo;
    CARDStat status;
    
    // format temp string using file_name as "file_name~"
    sprintf(temp_name, "%-.31s~", file_name);
    workArea = my_malloc(CARD_WORKAREA_SIZE, 32);

    if (workArea == nullptr) {
        // Not enough memory!
        OSReport("メモリが足りん！！\n");
        result = CARD_RESULT_FATAL_ERROR;
        goto exit;
    }

    result = memcard_mount(chan, workArea);
    mounted = result == CARD_RESULT_READY;

    if (!mounted) {
        // Can't mount!
        OSReport("マウントできない！\n");
        goto exit;
    }
    
    if (check_file_name != nullptr) {
        result = CARDOpen(chan, check_file_name, &fileInfo);
        if (result != CARD_RESULT_READY) {
            // The check file does not exist.
            OSReport("チェックファイル存在しないよ\n");
            goto exit;
        }

        CARDClose(&fileInfo);
    }

    for (i = 0; i < 2; i++) {
        // Opening file [%s]
        OSReport("ファイルのオープン [%s]\n", i == 0 ? file_name : temp_name);
        result = CARDOpen(chan, i == 0 ? file_name : temp_name, &fileInfo);
        if (result != CARD_RESULT_READY) {
            // Can't open the file.
            OSReport("ファイルがオープンできない\n");
            continue;
        }
        
        if (buf != nullptr) {
            my_free(buf);
            buf = nullptr;
        }

        result = CARDGetStatus(chan, fileInfo.fileNo, &status);
        if (status.offsetData == 0) {
            // File has no icon
            OSReport("アイコンのないファイル\n");

            if (status.commentAddr == 0xFFFFFFFF) {
                // File has no comment
                OSReport("コメントもないファイル\n");
                // The file is currently being saved
                OSReport("どう考えてもセーブ中のファイル\n");
                if (CARDDelete(chan, file_name) == CARD_RESULT_READY) {
                    // Erased the file that was being saved.
                    OSReport("セーブ中のファイルを消しました\n");
                }

                continue;
            }
        }
        
        size_t len = status.length;
        buf = my_malloc(len, 32);

        if (buf == nullptr) {
            // Not enough memory!
            OSReport("メモリが足りん！！\n");
            result = CARD_RESULT_FATAL_ERROR;
            goto exit;
        }

        // Read the file.
        OSReport("ファイル読む\n");
        result = CARDRead(&fileInfo, buf, len, 0);
        if (result != CARD_RESULT_READY) {
            // Can't read the file.
            OSReport("ファイル読めない\n");
            continue;
        }

        // Close the file.
        OSReport("ファイル閉じる\n");
        result = CARDClose(&fileInfo);
        if (result != CARD_RESULT_READY) {
            // Can't close the file.
            OSReport("ファイル閉じれない\n");
            continue;
        }

        // Reading successful!
        OSReport("読み込み成功！！\n");
        FamicomSaveDataHeader* read_save_header = (FamicomSaveDataHeader*)((u32)buf + status.offsetData);
        
        if (famicom_save_data_check(read_save_header, -1, comment_img) == 0) {
            // The data is normal!
            OSReport("データは正常！！\n");
            
            if (i == 0) {
                if (CARDDelete(chan, temp_name) == CARD_RESULT_READY) {
                    // Deleted the old data backup.
                    OSReport("旧データバックアップを消しました\n");
                }
            }
            else {
                if (CARDDelete(chan, file_name) == CARD_RESULT_READY) {
                    // Deleted invalid 'real' file.
                    OSReport("不正なホンチャンファイルを消しました\n");
                }

                if (CARDRename(chan, temp_name, file_name) == CARD_RESULT_READY) {
                    // Renamed backup file to 'real' file.
                    OSReport("バックアップをホンチャンにリネームしました\n");
                }
            }

            if (save_header != nullptr) {
                memcpy(save_header, read_save_header, save_data_size);
            }

            break;
        }
        
        result = CARD_RESULT_NOFILE;
    }

exit:
    if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR) {
        errorChan = chan;
    }

    if (result != CARD_RESULT_READY) {
        // Error code = %d.
        OSReport("エラーコード＝%d\n", result);
    }

    if (buf != nullptr) {
        my_free(buf);
    }

    if (save_data != nullptr) {
        my_free(save_data);
    }

    if (mounted) {
        CARDUnmount(chan);
    }

    if (workArea != nullptr) {
        my_free(workArea);
    }

    return result;
}

static s32 memcard_game_load(
    u8* nesromp,
    int rom_idx,
    u8** tags_pp,
    char* mura_save_name,
    char* famicom_save_name,
    MemcardGameHeader_t* game_header,
    u8* memcard_save_comment,
    size_t unused_save_data_start_ofs,
    u8* chr_to_i8_bufp,
    size_t chr_to_i8_buf_size
) {
    s32 result;
    s32 chan;
    CARDFileInfo fileInfo;
    int mounted = FALSE;
    CARDStat cardStatus;
    int now_rom_idx = 0;
    void* workArea;
    s32 fileNo;
    
    workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
    if (workArea != nullptr) {
        for (chan = 0; chan < CARD_NUM_CHANS; chan++) {
            result = memcard_mount(chan, workArea);
            mounted = result == CARD_RESULT_READY;
    
            if (mounted) {
                for (fileNo = 0; fileNo < 127; fileNo++) {
                    result = CARDGetStatus(chan, fileNo, &cardStatus);
                    if (result == CARD_RESULT_READY) {
                        if (
                            memcmp(cardStatus.gameName, "GAFE", 4) == 0 &&
                            memcmp(cardStatus.company, "01", 2) == 0 &&
                            memcmp(cardStatus.fileName, "DobutsunomoriP_F_", 17) == 0 &&
                            strcmp(&cardStatus.fileName[17], "SAVE") != 0 /* filename can't be Doubutsunomori_P_F_SAVE* */
                        ) {
                            if (rom_idx == now_rom_idx) {
                                /* Incorporate the contents of the file. */
                                OSReport("ファイルの内容を取り込む\n");
                                result = CARDFastOpen(chan, fileNo, &fileInfo);
                                if (result != CARD_RESULT_READY) {
                                    goto exit;
                                }
    
                                if (cardStatus.offsetData == 0) {
                                    result = CARD_RESULT_FATAL_ERROR;
                                    goto exit;
                                }
    
                                /* Reading the entire file. */
                                OSReport("ファイル全体の読み出し\n");
                                u8* data_bufp = cardStatus.length <= chr_to_i8_buf_size ? chr_to_i8_bufp : nullptr;
    
                                if (data_bufp == nullptr) {
                                    result = CARD_RESULT_FATAL_ERROR;
                                    goto exit;
                                }
    
                                result = CARDRead(&fileInfo, data_bufp, cardStatus.length, 0);
                                if (result != CARD_RESULT_READY) {
                                    goto exit;
                                }
    
                                /* File close. */
                                OSReport("ファイルクローズ\n");
                                result = CARDClose(&fileInfo);
                                if (result != CARD_RESULT_READY) {
                                    goto exit;
                                }
    
                                /* Unmount. */
                                OSReport("アンマウント\n");
                                result = CARDUnmount(chan);
                                mounted = FALSE;
                                if (result != CARD_RESULT_READY) {
                                    goto exit;
                                }
    
                                /* Reading successful! */
                                OSReport("読み込み成功！！\n");
                                if (calcSum(data_bufp, cardStatus.length) != 0 || cardStatus.offsetData == 0) {
                                    result = CARD_RESULT_FATAL_ERROR;
                                    goto exit;
                                }
    
                                if (mura_save_name != nullptr) {
                                    memcpy(mura_save_name, cardStatus.fileName, 32);
                                }
    
                                if (famicom_save_name != nullptr) {
                                    memcpy(famicom_save_name, cardStatus.fileName, 32);
                                    famicom_save_name[15] = 'S';
                                }
    
                                u8* datap = &data_bufp[cardStatus.offsetData];
                                memcpy(game_header, datap, sizeof(MemcardGameHeader_t));
                                datap += sizeof(MemcardGameHeader_t);
    
                                /* Copy nesinfo tags */
                                size_t tags_size = game_header->nestags_size;
                                if (tags_size != 0) {
                                    if (tags_pp != nullptr) {
                                        *tags_pp = (u8*)my_malloc(tags_size, 1);
                                    }
    
                                    /* @BUG - tags_pp might be a nullptr but we're dereferencing it anyway */
                                    memcpy(*tags_pp, datap, tags_size);
                                }
                                else {
                                    /* @BUG - tags_pp might be a nullptr but we're dereferencing it anyway */
                                    *tags_pp = nullptr;
                                }
    
                                datap += ALIGN_NEXT(tags_size, 16);
    
                                size_t comment_img_size;
                                if (game_header->flags0.has_comment_img && (comment_img_size = game_header->comment_img_size, comment_img_size != 0)) {
                                    if (JC_JKRDecomp_checkCompressed(datap) == JKRCOMPRESSION_NONE) {
                                        SetupExternCommentImage(datap, memcard_save_comment, data_bufp);
                                    }
                                    else {
                                        JC_JKRDecomp_decode(datap, nesromp, CHR_TO_I8_BUF_SIZE, 0);
                                        SetupExternCommentImage(nesromp, memcard_save_comment, data_bufp);
                                    }
    
                                    datap += ALIGN_NEXT(comment_img_size, 16);
                                }
                                else {
                                    SetupExternCommentImage(nullptr, memcard_save_comment, data_bufp);
                                }
    
                                if (game_header->nesrom_size != 0) {
                                    u32 real_size = game_header->nesrom_size << 4;
    
                                    if (JC_JKRDecomp_checkCompressed(datap) == JKRCOMPRESSION_NONE) {
                                        memcpy(nesromp, datap, real_size);
                                        nesinfo_data_size = real_size;
                                    }
                                    else {
                                        JC_JKRDecomp_decode(datap, nesromp, CHR_TO_I8_BUF_SIZE, 0);
                                        nesinfo_data_size = (u32)((datap[4] << 24) | (datap[5] << 16) | (datap[6]<< 8) | datap[7]);
                                    }
                                }
    
                                result = CARD_RESULT_READY;
                                goto exit; // success
                            }
                            else {
                                now_rom_idx++;
                            }
                        }
                    }
                    else if (result != CARD_RESULT_NOFILE) {
                        goto exit;
                    }
                }
    
                if (fileNo >= 127) {
                    /* There was no target game image in slot %d. */
                    OSReport("スロット%dには目的のゲームイメージがありませんでした\n", chan);
                    CARDUnmount(chan);
                    mounted = FALSE;
                }
            }
        }
        
        /* The rom file was not found */
        result = CARD_RESULT_NOFILE;
    }
    else {
        result = CARD_RESULT_FATAL_ERROR;
    }

exit:
    if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR) {
        errorChan = chan;
    }

    if (result != CARD_RESULT_READY) {
        // Error code = %d.
        OSReport("エラーコード＝%d\n", result);
    }

    if (mounted) {
        CARDUnmount(chan);
    }

    if (workArea != nullptr) {
        my_free(workArea);
    }

    return result;
}

static s32 memcard_game_list(
    int* n_games,
    char* namebufp,
    int namebuf_size
) {
    CARDFileInfo fileInfo;
    CARDStat cardStatus;
    s32 result;
    s32 chan;
    int mounted = FALSE;
    int game_count = 0;
    void* workArea;
    s32 fileNo;
    void* buf = nullptr;
    u32 data_ofs;

    workArea = my_malloc(CARD_WORKAREA_SIZE, 32);
    if (workArea == nullptr) {
        result = CARD_RESULT_FATAL_ERROR;
        goto exit;
    }

    buf = my_malloc(0x200, 32);
    if (buf == nullptr) {
        result = CARD_RESULT_FATAL_ERROR;
        goto exit;
    }

    for (chan = 0; chan < CARD_NUM_CHANS; chan++) {
        mounted = memcard_mount(chan, workArea) == CARD_RESULT_READY;

        if (mounted) {
            for (fileNo = 0; fileNo < 127; fileNo++) {
                result = CARDGetStatus(chan, fileNo, &cardStatus);
                if (result == CARD_RESULT_READY) {
                    if (
                        memcmp(cardStatus.gameName, "GAFE", 4) == 0 &&
                        memcmp(cardStatus.company, "01", 2) == 0 &&
                        memcmp(cardStatus.fileName, "DobutsunomoriP_F_", 17) == 0 &&
                        strcmp(&cardStatus.fileName[17], "SAVE") != 0 /* filename can't be Doubutsunomori_P_F_SAVE* */
                    ) {
                        if (namebuf_size >= 16) {
                            int invalid_game = FALSE;

                            /* Incorporate the contents of the file. */
                            OSReport("ファイルの内容を取り込む\n");
                            static char brokenTitle[] = "##BROKEN GAME##";

                            if (CARDFastOpen(chan, fileNo, &fileInfo) != CARD_RESULT_READY) {
                                invalid_game = TRUE;
                            }
                            else if (cardStatus.offsetData == 0) {
                                invalid_game = TRUE;
                            }
                            else {
                                /* Reading the beginning of the data. */
                                OSReport("データ先頭の読み出し\n");
                                data_ofs = cardStatus.offsetData - ALIGN_PREV(cardStatus.offsetData, 0x200);
                                if (CARDRead(&fileInfo, buf, 0x200, ALIGN_PREV(cardStatus.offsetData, 0x200)) != CARD_RESULT_READY) {
                                    invalid_game = TRUE;
                                }
                                else {
                                    /* File close. */
                                    OSReport("ファイルクローズ\n");
                                    if (CARDClose(&fileInfo) != CARD_RESULT_READY) {
                                        invalid_game = TRUE;
                                    }
                                }
                            }

                            if (invalid_game == FALSE) {
                                /* Reading successful! */
                                OSReport("読み込み成功！！\n");
                                memcpy(namebufp, (u8*)buf + data_ofs + 2, MURA_GAME_NAME_SIZE); // read mori name
                            }
                            else {
                                /* Reading failed! */
                                OSReport("読み込み失敗！！\n");
                                memcpy(namebufp, brokenTitle, MURA_GAME_NAME_SIZE);
                            }

                            namebufp += MURA_GAME_NAME_SIZE;
                            namebuf_size -= MURA_GAME_NAME_SIZE;
                        }

                        (*n_games)++;
                        game_count++;
                    }
                }
                else if (result != CARD_RESULT_NOFILE) {
                    goto exit;
                }
            }

            if (fileNo >= 127) {
                /* Finished searching slot %d. */
                OSReport("スロット%d検索終了\n", chan);
                CARDUnmount(chan);
                mounted = FALSE;
            }
        }
    }

    result = CARD_RESULT_READY;
    *n_games = game_count;
exit:
    if (result != CARD_RESULT_READY) {
        // Error code = %d.
        OSReport("エラーコード＝%d\n", result);
    }

    if (buf != nullptr) {
        my_free(buf);
    }

    if (mounted) {
        CARDUnmount(chan);
    }

    if (workArea != nullptr) {
        my_free(workArea);
    }

    return result;
}

inline JKRFileFinder* getFirstFile(const char* dir) {
    return JKRFileLoader::findFirstFile(dir);
}

static size_t entrylist(const char* dir, char* filename_strbuf) {
    char pathbuf[256];
    u8* filename_buf = (u8*)filename_strbuf;
    JKRFileFinder* fileFinder;
    s32 dir_path_len;
    size_t len;
    
    fileFinder = getFirstFile(dir);
    dir_path_len = strlen(dir);

    if (filename_strbuf != nullptr) {
        filename_strbuf[0] = ':';
        memcpy(filename_strbuf + 1, dir, dir_path_len + 1);
        nesrom_filename_ptrs[nesrom_count] = filename_buf;
    }

    nesrom_count++;
    len = dir_path_len + 2;
    if (fileFinder != nullptr) {
        while (fileFinder->isAvailable()) {
            if (fileFinder->mBase.mFileName[0] != '.') {
                snprintf(pathbuf, sizeof(pathbuf), "%s/%s", dir, fileFinder->mBase.mFileName);
                dir_path_len = strlen(pathbuf);
                if (fileFinder->mIsDir) {
                    len += entrylist(pathbuf, filename_strbuf != nullptr ? filename_strbuf + len : nullptr);
                }
                else {
                    if (filename_strbuf != nullptr) {
                        u8* romname_p = filename_buf + len;

                        memcpy(romname_p, pathbuf, dir_path_len + 1);
                        nesrom_filename_ptrs[nesrom_count] = romname_p;
                    }

                    nesrom_count++;
                    len = 1 + dir_path_len + len;
                }
            }

            fileFinder->findNextFile();
        }

        delete fileFinder;
    }

    return len;
}

static void select_game() {
    int rom_no = famicomCommon.rom_no;
    int rom = rom_no;

    if (famicomCommon.nesrom_memcard == FALSE) {
        nesrom_idx = 0;
        
        while (TRUE) {
            if (*nesrom_filename_ptrs[nesrom_idx] != ':' &&--rom <= 0) {
                break;
            }
            
            nesrom_idx++;

            if (nesrom_idx >= nesrom_count) {
                if (nesrom_idx_loaded < 0) {
                    OSPanic(__FILE__, 4239, "no nesfile specified");
                    continue;
                }

                nesrom_idx = 0;
            }
        }
    }
    else {
        nesrom_idx = rom_no;
    }
}

extern int famicom_mount_archive_end_check() {
    return famicom_arc != nullptr;
}

extern void famicom_mount_archive() {
    if (famicom_arc == nullptr && famicom_arc_mounting == false) {
        famicom_arc_mounting = true;
        famicom_arc = (JKRArchive*)JC__JKRMountArchive("famicom.arc", JKRArchive::MOUNT_COMP, JC__JKRGetSystemHeap(), JKRArchive::MOUNT_DIRECTION_HEAD);
        
        if (famicom_arc == nullptr) {
            OSDVDFatalError();
        }

        famicom_arc_mounting = false;
    }

    JKRAram::getAramHeap()->dump();
}

static void famicom_mount_archive_wait() {
    if (famicom_arc == nullptr) {
        // Waiting because famicom.arc has not been mounted yet.
        OSReport("famicom.arc がまだマウントされていないので待っています\n");

        do {
            VIWaitForRetrace();
        } while (famicom_arc == nullptr);
    }
}

// clang-format off
#define MALLOC_MALLOC(type, size, dst, line) \
    if (dst != nullptr) {   \
        OSPanic(__FILE__, line, "MALLOC_MALLOC: " #dst " isn't NULL");  \
    }   \
    dst = (type *)my_malloc(size, 32);  \
    if (dst == nullptr) {   \
        /* MALLOC_MALLOC: %s %s %dByte allocation failed. */    \
        OSReport("MALLOC_MALLOC: %s %s %dByte 確保失敗\n", #type, #size, size); \
        goto exit;  \
    }   \
    /* MALLOC_MALLOC: %s %s %dByte successfully allocated. */    \
    OSReport("MALLOC_MALLOC: %s %s %dByte 確保成功\n", #type, #size, size);
// clang-format on

#define CONV_BYTE_KB(bytes) ((f32)(bytes) / 1024.0f)

extern int famicom_init(int rom_idx, Famicom_MallocInfo* malloc_info, int player_no) {
    famicom_mount_archive_wait();
    bzero(&famicomCommon, sizeof(FamicomCommon));
    my_malloc_current = malloc_info;
    
    if (player_no >= 0 && player_no < PLAYER_NUM) {
        famicomCommon.save_pl_no = player_no;
    }
    else {
        famicomCommon.save_pl_no = 0;
    }
    
    if (rom_idx == 0) {
        filer_mode_enable = TRUE;
    }
    else {
        filer_mode_enable = FALSE;
    }
    
    filer_mode = 0;
    filer_demo_mode = FILER_DEMO_MODE_NORMAL;
    filer_demo_count = 0;
    nesrom_idx_loaded = -1;

    if (rom_idx == 0) {
        famicomCommon.nesrom_memcard = FALSE;
        famicomCommon.rom_no = 0;
        nesrom_idx = 1;
    }
    else if (rom_idx > 0) {
        famicomCommon.nesrom_memcard = FALSE;
        famicomCommon.rom_no = rom_idx;

        if (famicomCommon.save_pl_no >= 0 && save_game_image == 0) {
            famicomCommon.save_data_name = (u8*)"GAFEFSC\x1A";
            famicomCommon.save_data_single_size = 0x660;
            famicomCommon.save_data_total_size = sizeof(FamicomSaveDataHeader) + 0x660 * PLAYER_NUM;
            famicomCommon.save_data_header = (FamicomSaveDataHeader*)famicomCommonSave;
            famicomCommon.internal_save_datap = famicomCommonSave + sizeof(FamicomSaveDataHeader) + 0x660 * famicomCommon.save_pl_no;
        }

        nesrom_idx = famicomCommon.rom_no + 1;
        strncpy((char*)famicomCommon.mura_save_name, "DobutsunomoriP_MURA", 32);
        strncpy((char*)famicomCommon.famicom_save_name, "DobutsunomoriP_F_SAVE", 32);
        famicomCommon.unused_save_data_start_ofs = 0x640;
    }
    else if (rom_idx < 0) {
        famicomCommon.nesrom_memcard = TRUE;
        famicomCommon.rom_no = -rom_idx;
        famicomCommon.unused_save_data_start_ofs = 0x3040;
    }

    nesrom_count = 0;
    rapid_count = 0;
    rapid_mode_a = 0;
    rapid_mode_b = 0;
    nines_over_double_click = 0;
    nines_over_mode = 0;
    hispeed_mode = 0;
    slow_mode = 0;
    slow_mode_sub = 0;
    speed_show = 0;
    JKRFileLoader::changeDirectory("/FAMICOM/");
    size_t unused_totalfreesize = my_gettotalfreesize();

    MALLOC_MALLOC(u8, CHR_TO_I8_BUF_SIZE, famicomCommon.chr_to_i8_bufp, 4497);
    MALLOC_MALLOC(u8, KS_NES_NESFILE_HEADER_SIZE + KS_NES_PRGROM_SIZE + KS_NES_CHRROM_SIZE, famicomCommon.nesromp, 4499);
    MALLOC_MALLOC(ksNesCommonWorkObj, sizeof(ksNesCommonWorkObj), famicomCommon.wp, 4501);
    MALLOC_MALLOC(ksNesStateObj, sizeof(ksNesStateObj), famicomCommon.sp, 4502);
    MALLOC_MALLOC(u8, KS_NES_CHRRAM_SIZE, famicomCommon.chrramp, 4505);
    MALLOC_MALLOC(u8, KS_NES_BBRAM_SIZE, famicomCommon.bbramp, 4510);
    MALLOC_MALLOC(u8, KS_NES_NOISE_DATA_SIZE, famicomCommon.noise_bufp, 4515);
    MALLOC_MALLOC(u8, KS_NES_DRAW_RESULT_BUF_SIZE, famicomCommon.result_bufp, 4517);

    famicomCommon.memcard_save_comment = commentImageBuffer;

    OSReport("buffer used:\n");
    OSReport("wp            =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.wp)));
    OSReport("sp            =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.sp)));
    OSReport("nesromp       =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.nesromp)));
    OSReport("chrramp       =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chrramp)));
    OSReport("bbramp        =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.bbramp)));
    OSReport("noise_bufp    =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.noise_bufp)));
    OSReport("chr_to_i8_bufp=%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chr_to_i8_bufp)));
    OSReport("result_bufp   =%8.3fKB\n", CONV_BYTE_KB(my_getmemblocksize(famicomCommon.result_bufp)));
    OSReport("total          %8.3fKB.\nif chrrom-type, and no bbram, minimum total size: %8.3fKB + nesrom size max + (nesrom chr size max * 4) bytes.\n",
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.wp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.sp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.nesromp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chrramp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.bbramp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.noise_bufp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.chr_to_i8_bufp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.result_bufp)),
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.wp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.sp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.noise_bufp)) +
        CONV_BYTE_KB(my_getmemblocksize(famicomCommon.result_bufp))
    );

    if (famicomCommon.noise_bufp == nullptr || JKRFileLoader::readGlbResource(famicomCommon.noise_bufp, KS_NES_NOISE_DATA_SIZE, "noise.bin.szs", EXPAND_SWITCH_DECOMPRESS)) {
        if (famicomCommon.noise_bufp != nullptr) {
            EmuSound_Start(famicomCommon.noise_bufp + 0x2000);
        }

        nesrom_idx_loaded = -1;
        if (famicomCommon.nesrom_memcard == FALSE) {
            for (u32 i = 0; i < 2; i++) {
                nesrom_count = 0;
                size_t length = entrylist("GAME", nesrom_filename_strbuf);
                if (i == 0) {
                    if (nesrom_count == 0) {
                        OSReport("no nesfile specified\n");
                        goto exit;
                    }

                    MALLOC_MALLOC(u8 *, nesrom_count * sizeof(u8*), nesrom_filename_ptrs, 4676);
                    MALLOC_MALLOC(char, length, nesrom_filename_strbuf, 4677);
                }
            }
        }
        else {
            nesrom_count = 0;
        }

        select_game();
        JUTProcBar::getManager()->setVisible(false);
        famicomCommon.low_res_mode = true;
        JW_SetFamicomMode(TRUE);
        JW_SetLowResoMode(famicomCommon.low_res_mode);
        return 0; // success
    }

    // error
exit:
    if (famicomCommon.wp != nullptr) {
        my_free(famicomCommon.wp);
    }
    famicomCommon.wp = nullptr;
    
    if (famicomCommon.sp != nullptr) {
        my_free(famicomCommon.sp);
    }
    famicomCommon.sp = nullptr;
    
    if (famicomCommon.nesromp != nullptr) {
        my_free(famicomCommon.nesromp);
    }
    famicomCommon.nesromp = nullptr;
    
    if (famicomCommon.chrramp != nullptr) {
        my_free(famicomCommon.chrramp);
    }
    famicomCommon.chrramp = nullptr;
    
    if (famicomCommon.bbramp != nullptr) {
        my_free(famicomCommon.bbramp);
    }
    famicomCommon.bbramp = nullptr;
    
    if (famicomCommon.noise_bufp != nullptr) {
        my_free(famicomCommon.noise_bufp);
    }
    famicomCommon.noise_bufp = nullptr;
    
    if (famicomCommon.chr_to_i8_bufp != nullptr) {
        my_free(famicomCommon.chr_to_i8_bufp);
    }
    famicomCommon.chr_to_i8_bufp = nullptr;
    
    if (famicomCommon.result_bufp != nullptr) {
        my_free(famicomCommon.result_bufp);
    }
    famicomCommon.result_bufp = nullptr;
    
    if (famicomCommon.highscore_flagsp != nullptr) {
        my_free(famicomCommon.highscore_flagsp);
    }
    famicomCommon.highscore_flagsp = nullptr;

    if (famicomCommon.nesinfo_tagsp != nullptr) {
        my_free(famicomCommon.nesinfo_tagsp);
    }
    famicomCommon.nesinfo_tagsp = nullptr;
    
    return -1;
}

static int SetupResBanner(const ResTIMG* img, u8* dst, size_t max_size, size_t* size, u8* type) {
    size_t banner_size;
    u8* data_p = dst;
    int banner_type;

    if (img != nullptr) {
        banner_size = img->getWidth() * img->getHeight();
        if (img->mTextureFormat != ResTIMG_FORMAT_C8) {
            banner_size *= sizeof(u16);
            banner_type = CARD_STAT_BANNER_RGB5A3;
        }
        else {
            banner_type = CARD_STAT_BANNER_C8;
        }

        bcopy(((u8*)img) + img->mImageDataOffset, data_p, banner_size);
        data_p += banner_size;

        if (img->mPaletteFormat != ResTIMG_NO_PALETTE) {
            size_t pal_size = img->mPaletteEntryCount * sizeof(u16);

            bcopy(((u8*)img) + img->mPaletteOffset, data_p, pal_size);
            bzero(data_p + pal_size, (256 * sizeof(u16)) - pal_size); // zero out unused entries
            data_p += 256 * sizeof(u16);
        }
    }
    else {
        banner_type = CARD_STAT_BANNER_NONE;
    }

    if (size != nullptr) {
        *size = (u32)data_p - (u32)dst;
    }

    if (type != nullptr) {
        *type = banner_type;
    }

    return 0;
}

static int SetupResIcon(const ResTIMG* img, u8* dst, size_t max_size, size_t* size_p, u16* icon_fmt_p, u16* icon_flags_p) {
    u8* data_p = dst;
    size_t icon_size;
    u16 icon_fmt;
    u16 icon_flags;
    int banner_type;
    u32 icon_type;
    int i;
    u32 icon_count;
    int j;

    if (img != nullptr) {
        icon_size = img->getWidth() * img->getHeight();
        icon_count = icon_size / 0x400;

        if (img->mTextureFormat != ResTIMG_FORMAT_C8) {
            icon_size *= sizeof(u16);
            icon_type = CARD_STAT_ICON_RGB5A3;
        }
        else {
            icon_type = CARD_STAT_ICON_C8;
        }

        /* Make icon format flags & speed flags for each frame */
        icon_fmt = 0;
        icon_flags = 0;
        i = 0;
        
        for (j = 0; j < icon_count; j++) {
            icon_fmt |= (icon_type << (i * 2));
            icon_flags |= (CARD_STAT_SPEED_MIDDLE << (i * 2));
            i++;
        }

        bcopy(((u8*)img) + img->mImageDataOffset, data_p, icon_size);
        data_p += icon_size;

        if (img->mPaletteFormat != ResTIMG_NO_PALETTE) {
            size_t pal_size = img->mPaletteEntryCount * sizeof(u16);

            bcopy(((u8*)img) + img->mPaletteOffset, data_p, pal_size);
            bzero(data_p + pal_size, (256 * sizeof(u16)) - pal_size); // zero out unused entries
            data_p += 256 * sizeof(u16);
        }
    }
    else {
        icon_fmt = CARD_STAT_ICON_NONE;
    }

    if (size_p != nullptr) {
        *size_p = (u32)data_p - (u32)dst;
    }

    if (icon_fmt_p != nullptr) {
        *icon_fmt_p = icon_fmt;
    }

    if (icon_flags_p != nullptr) {
        *icon_flags_p = icon_flags;
    }

    return 0;
}

extern int famicom_cleanup() {
    int res = FALSE;

    if (nesrom_idx_loaded != -1 && save_game_image == 0) {
        OSReport("nesrom_memcard = %d nesrom_idx = %d\n", famicomCommon.nesrom_memcard, nesrom_idx);

        if (famicomCommon.internal_save_datap != nullptr) {
            nesinfo_tag_process3(famicomCommon.internal_save_datap);
        }

        if (famicomCommon.save_pl_no >= 0 && famicomCommon.save_data_header != nullptr) {
            if (famicom_save_data_check((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_pl_no, famicomCommon.save_data_name) == 0) {
                famicom_save_data_setup((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
            }
            else {
                // Will not save because the common save area is damaged.
                OSReport(VT_COL(RED, WHITE) "共通セーブ領域が壊れているのでセーブしません\n" VT_RST);
                res = TRUE;
            }
        }
    }

    if (famicomCommon.noise_bufp != nullptr) {
        EmuSound_Exit();
    }

    if (famicomCommon.wp != nullptr) {
        my_free(famicomCommon.wp);
    }
    famicomCommon.wp = nullptr;
    
    if (famicomCommon.sp != nullptr) {
        my_free(famicomCommon.sp);
    }
    famicomCommon.sp = nullptr;
    
    if (famicomCommon.nesromp != nullptr) {
        my_free(famicomCommon.nesromp);
    }
    famicomCommon.nesromp = nullptr;
    
    if (famicomCommon.chrramp != nullptr) {
        my_free(famicomCommon.chrramp);
    }
    famicomCommon.chrramp = nullptr;
    
    if (famicomCommon.bbramp != nullptr) {
        my_free(famicomCommon.bbramp);
    }
    famicomCommon.bbramp = nullptr;
    
    if (famicomCommon.noise_bufp != nullptr) {
        my_free(famicomCommon.noise_bufp);
    }
    famicomCommon.noise_bufp = nullptr;
    
    if (famicomCommon.chr_to_i8_bufp != nullptr) {
        my_free(famicomCommon.chr_to_i8_bufp);
    }
    famicomCommon.chr_to_i8_bufp = nullptr;
    
    if (famicomCommon.result_bufp != nullptr) {
        my_free(famicomCommon.result_bufp);
    }
    famicomCommon.result_bufp = nullptr;
    
    if (famicomCommon.highscore_flagsp != nullptr) {
        my_free(famicomCommon.highscore_flagsp);
    }
    famicomCommon.highscore_flagsp = nullptr;

    if (nesrom_filename_ptrs != nullptr) {
        my_free(nesrom_filename_ptrs);
    }
    nesrom_filename_ptrs = nullptr;

    if (nesrom_filename_strbuf != nullptr) {
        my_free(nesrom_filename_strbuf);
    }
    nesrom_filename_strbuf = nullptr;

    my_malloc_current = nullptr;
    JW_SetLowResoMode(FALSE);
    JW_SetFamicomMode(FALSE);
    return res;
}

static s32 famicom_common_save_initial();

static int famicom_rom_load() {
    int nesrom_buffer_size = my_getmemblocksize(famicomCommon.nesromp);
    nesinfo_init();
    OSReport("nesrom_buffer_size=%d\n", nesrom_buffer_size);

    if (nesrom_buffer_size >= KS_NES_CHRROM_SIZE) {
        bzero(famicomCommon.nesromp, KS_NES_CHRROM_SIZE);
    }

    if (famicomCommon.nesrom_memcard != FALSE) {
        /* Loading the game image from the memory card. */
        OSReport("ゲームイメージをメモリーカードから読み込みます\n");
        s32 load_res = memcard_game_load(
            famicomCommon.nesromp,
            famicomCommon.rom_no - 1,
            &famicomCommon.nesinfo_tagsp,
            (char*)famicomCommon.mura_save_name,
            (char*)famicomCommon.famicom_save_name,
            &famicomCommon.memcard_game_header,
            famicomCommon.memcard_save_comment, // comment & images
            famicomCommon.unused_save_data_start_ofs,
            famicomCommon.chr_to_i8_bufp,
            CHR_TO_I8_BUF_SIZE
        );

        if (load_res == CARD_RESULT_READY) {
            /* Loaded the game image from the memory card. */
            OSReport("ゲームイメージをメモリーカードから読み込みました\n");
            nesinfo_tags_start = famicomCommon.nesinfo_tagsp;
            nesinfo_tags_end = nesinfo_tags_start + famicomCommon.memcard_game_header.nestags_size;
            nesinfo_tags_size = nesinfo_tags_end - nesinfo_tags_start;
            nesinfo_data_start = famicomCommon.nesromp;
            nesinfo_data_end = nesinfo_data_start + nesinfo_data_size;
        }
        else {
            /* Failed to load game [%d]. */
            OSReport("ゲーム読み込みに失敗しました [%d]\n", load_res);
            return -1;
        }
    }
    else {
        const char* filename = (char*)nesrom_filename_ptrs[nesrom_idx];
        OSReport("%d:<%s>\n", nesrom_idx, filename);
        bzero(famicomCommon.nesromp, nesrom_buffer_size);
        size_t loaded_size = JKRFileLoader::readGlbResource(famicomCommon.nesromp, nesrom_buffer_size, filename, EXPAND_SWITCH_DECOMPRESS);
        if (loaded_size == 0) {
            return -1;
        }

        nesinfo_tags_set(famicomCommon.rom_no - 1);
        
        nesinfo_data_size = loaded_size;
        nesinfo_data_start = famicomCommon.nesromp;
        nesinfo_data_end = nesinfo_data_start + loaded_size;

        MemcardGameHeader_t* memcard_game_header = &famicomCommon.memcard_game_header;
        bzero(memcard_game_header, sizeof(MemcardGameHeader_t));
        memcard_game_header->_00 = 'G';
        memcard_game_header->_01 = 0;
        strncpy((char*)memcard_game_header->mori_name, (char*)nesinfo_get_moriName(), MURA_GAME_NAME_SIZE);
        memcard_game_header->nesrom_size = 0;
        memcard_game_header->nestags_size = 0;
        memcard_game_header->flags0.has_comment_img = TRUE;
        memcard_game_header->flags0.no_copy_flag = FALSE;
        memcard_game_header->flags1.no_move_flag = FALSE;
        SetupInternalCommentImage(famicomCommon.memcard_save_comment);
    }

    u32 max_ofs = 0;
    tcs_bad = false;
    ics_bad = false;

    nesinfo_tag_process1(nullptr, 0, &max_ofs);

    if (ics_bad || tcs_bad) {
        if (tcs_bad) {
            // Tag checksum is incorrect.
            OSReport(VT_COL(YELLOW, BLACK) "タグチェックサムが違います\n" VT_RST);
        }

        if (ics_bad) {
            // Image checksum is incorrect.
            OSReport(VT_COL(YELLOW, BLACK) "イメージチェックサムが違います\n" VT_RST);
        }

        return -1;
    }

    if (famicomCommon.nesrom_memcard && famicomCommon.save_pl_no >= 0) {
        famicomCommon.save_data_name = (u8*)"GAFEFSE\x1A";
        famicomCommon.save_data_single_size = max_ofs + 0xB;
        famicomCommon.save_data_total_size = famicomCommon.save_data_single_size * PLAYER_NUM + sizeof(FamicomSaveDataHeader);
        famicomCommon.save_data_header = (FamicomSaveDataHeader*)nintendo_hi_0; // overwritten for use with save data
        famicomCommon.internal_save_datap = nintendo_hi_0 + sizeof(FamicomSaveDataHeader) + famicomCommon.save_data_single_size * famicomCommon.save_pl_no;
        bzero(famicomCommon.save_data_header, famicomCommon.save_data_total_size);
    }

    if (famicomCommon.nesrom_memcard == FALSE) {
        famicomCommon.internal_save_datap = famicomCommonSave + sizeof(FamicomSaveDataHeader) + 0x660 * famicomCommon.save_pl_no;
    }
    else if (famicomCommon.save_data_header != nullptr) {
        famicom_common_save_initial();
    }

    if (famicomCommon.internal_save_datap != nullptr) {
        nesinfo_tag_process1(famicomCommon.internal_save_datap, 1, nullptr);
    }

    famicomCommon.highscore_flagsp = (u8*)my_malloc(nesinfo_get_highscore_num(), 32);
    highscore_setup_flags(famicomCommon.highscore_flagsp);
    nesinfo_tag_process2();
    
    ksNesCommonWorkObj* wp = famicomCommon.wp;
    wp->nesromp = famicomCommon.nesromp;
    u32 flags = 0;
    wp->prg_size = KS_NES_PRGROM_SIZE;
    wp->noise_bufp = famicomCommon.noise_bufp;
    wp->fds_disk_count = 4;
    wp->chr_to_u8_bufp = famicomCommon.chr_to_i8_bufp;
    wp->chr_to_i8_buf_size = CHR_TO_I8_BUF_SIZE;
    wp->result_bufp = famicomCommon.result_bufp;
    wp->total_cpu_cycles = 0;

    if (famicomCommon.noise_bufp == nullptr) {
        flags |= 0x40; // no sound?
    }

    int reset_res = ksNesReset(wp, famicomCommon.sp, flags | 0x10, famicomCommon.chrramp, famicomCommon.bbramp);

    if (reset_res != 0) {
        OSReport("err code=%d (0x%x), %x,%x,%x,%x,%x,%x\n",
            reset_res, reset_res, famicomCommon.wp, famicomCommon.sp,
            famicomCommon.wp->draw_ctx.sprite_scanline_limit, famicomCommon.wp->draw_ctx.ppu_scanline_regs,
            famicomCommon.wp->draw_ctx.post_process_lut, famicomCommon.sp->ppu_chr_banks
        );
        OSReport("NES emu reset failed!!");
    }
    else {
        OSReport("ksNesReset() OK PC=%x, prg_size = 0x%x, chr_size = 0x%x\n",
            famicomCommon.sp->PC, famicomCommon.sp->prg_size, famicomCommon.sp->chr_size);
        nesrom_idx_loaded = nesrom_idx;
        return 0;
    }

    return -1;
}

static void famicom_key_convert() {
    u32 input;
    
    for (int i = 0; i < PAD_MAX_CONTROLLERS; i++) {

        if (InputValid[i]) {
            input = InputData[i];
        }
        else {
            input = 0;
        }

        if (rapid_mode_a && (input & JUTGamePad::A) && (rapid_count & 0x80000000)) {
            input ^= JUTGamePad::A;
        }

        if (rapid_mode_b && (input & JUTGamePad::B) && (rapid_count & 0x40000000)) {
            input ^= JUTGamePad::B;
        }

        if (filer_mode != 0 || filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
            input &= ~(JUTGamePad::A | JUTGamePad::B);
        }

        input =
          (((input >>  2) & 3) << 26) // DPad up & down
        | (((input >>  0) & 1) << 25) // DPad left (0x02000000)
        | (((input >>  1) & 1) << 24) // DPad right (0x01000000)
        | (((input >>  8) & 1) << 31) // A -> A (0x80000000)
        | (((input >>  9) & 1) << 30) // B -> B (0x40000000)
        | (((input >> 10) & 1) << 29) // X -> Select (0x20000000)
        | (((input >>  4) & 1) << 29) // Z -> Select (0x20000000)
        | (((input >> 11) & 1) << 28) // Y -> Start (0x10000000)
        | (((input >> 12) & 1) << 28) // Start -> Start (0x10000000)
        | (((input >> 24) & 1) ? (1 << 25) : (0 << 25)) // Left analog stick -> Pad Left
        | (((input >> 25) & 1) ? (1 << 24) : (0 << 24)) // Right analog stick -> Pad Right
        | (((input >> 26) & 1) ? (1 << 26) : (0 << 26))     // Down analog stick -> Pad Down
        | (((input >> 27) & 1) ? (1 << 27) : (0 << 27))     // Up analog stick -> Pad Up
        ;
        
        switch (i) {
            case 0:
                famicomCommon.wp->pads[0] = input;
                break;
            case 1:
                famicomCommon.wp->pads[2] = input;
                break;
            case 2:
                famicomCommon.wp->pads[1] = input;
                break;
            case 3:
                famicomCommon.wp->pads[3] = input;
                break;
        }
    }

    rapid_count += 0xABCD0000;
}

static void famicom_draw() {
    GXTexObj famicomTexObj;

    GXSetScissor(0, 0, 640, 480);
    GXSetViewport(0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 1.0f);
    ksNesDrawInit(famicomCommon.wp);
    GXSetNumChans(0);
    GXSetNumTexGens(1);
    GXSetNumTevStages(1);
    GXSetNumIndStages(0);
    GXClearVtxDesc();
    GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
    GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGBA4, 0);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGBX8, 9);
    GXInitTexObj(&famicomTexObj, famicomCommon.wp->result_bufp, (u16)KS_NES_WIDTH, (u16)KS_NES_HEIGHT, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE);
    GXInitTexObjLOD(&famicomTexObj, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1);
    GXLoadTexObj(&famicomTexObj, GX_TEXMAP0);
    GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY, GX_FALSE, GX_PTIDENTITY);
    GXSetTexCoordScaleManually(GX_TEXCOORD0, GX_FALSE, 0, 0);
    GXSetTevDirect(GX_TEVSTAGE0);
    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
    GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
    GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
    GXBegin(GX_QUADS, GX_VTXFMT0, 4);

    int n2; // r0
    int n0; // r4
    int n3; // r3
    int n1; // r6

    if (filer_mode != 0 || filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
        n2 = 256;
        n0 = KS_NES_HEIGHT;
        n3 = -288;
        n1 = KS_NES_HEIGHT;

    }
    else {
        n2 = 256;
        n0 = KS_NES_HEIGHT;
        n3 = 30;
        n1 = 6;
    }
    

    // vertex 0
    GXPosition2s16(320 - n3, -8 - n1);
    GXTexCoord2s16(512, 0);
    
    // vertex 1
    GXPosition2s16(320 - n3, -8 - n1 - n0);
    GXTexCoord2s16(512, 512);
    
    // vertex 2
    GXPosition2s16(320 - n3 - n2, -8 - n1 - n0);
    GXTexCoord2s16(0, 512);

    // vertex 3
    GXPosition2s16(320 - n3 - n2, -8 - n1);
    GXTexCoord2s16(0, 0);

    ksNesDrawEnd();
}

static void* ksnes_proc(void* param) {
    u32* flags_p = (u32*)param;

    ksNesEmuFrame(famicomCommon.wp, famicomCommon.sp, *flags_p);
    return nullptr;
}

static void ksnes_thread_kill(OSAlarm* alarm, OSContext* context) {
    /* Famicom emulator crashed. */
    OSReport(VT_COL(YELLOW, BLACK) "ファミコンエミュ死亡\n" VT_RST);
    OSCancelThread(&ksnes_thread);
}

static int ksnes_thread_exec(u32 flags) {
    int res;
    u8* ksnes_emu_stack = (u8*)my_malloc(KS_NES_EMU_STACK_SIZE, 32);

    if (ksnes_emu_stack != nullptr) {
        OSTime timeout = OSMillisecondsToTicks(2000); // 2 second timeout

        if (famicomCommon.sp->motor_timer != 0) {
            // Disk motor is on.
            OSReport("ディスクモーターオン\n");
        }
        
        OSTime frame_start_time = OSGetTime();
        s32 prio = OSGetThreadPriority(OSGetCurrentThread());
        OSAlarm alarm;

        OSCreateThread(&ksnes_thread, &ksnes_proc, &flags, ksnes_emu_stack + KS_NES_EMU_STACK_SIZE, KS_NES_EMU_STACK_SIZE, prio + 1, 0);
        OSCreateAlarm(&alarm);
        OSResumeThread(&ksnes_thread);
        OSSetAlarm(&alarm, timeout, &ksnes_thread_kill);
        OSJoinThread(&ksnes_thread, (void**)&res);
        OSCancelAlarm(&alarm);

        OSTime frame_end_time = OSGetTime();

        if ((frame_end_time - frame_start_time) > OSMillisecondsToTicks(16)) {
            OSTime ms = OSTicksToMicroseconds(frame_end_time - frame_start_time); // isn't this supposed to be OSTicksToMilliseconds?

            // Emulator slow %d ms.
            OSReport(VT_COL(YELLOW, BLACK) "エミュ遅い %d ms\n" VT_RST, ms);
        }

        my_free(ksnes_emu_stack);
        return res;
    }
    else {
        return -2; // failed to allocate stack
    }
}

#pragma dont_inline on // @HACK - necessary to not inline JUTGamePad::getPortStatus
static void nogbaInput() {
    int port;
    u32 disconnected_ports = 0;

    for (port = 0; port < PAD_MAX_CONTROLLERS; port++) {
        InputValid[port] = false;

        // switch (GetPortStatus(port)) {
        switch ((int)JUTGamePad::getPortStatus((JUTGamePad::EPadPort)port)) {
            case PAD_ERR_NONE:
            {
                InputValid[port] = true;

                InputData[port] = ((JUTGamePad*)gamePad)[port].mButtons.mButton;
                
                if (((JUTGamePad*)gamePad)[port].mButtons.mAnalogL != 0) {
                    InputData[port] |= JUTGamePad::L;
                }

                if (((JUTGamePad*)gamePad)[port].mButtons.mAnalogR != 0) {
                    InputData[port] |= JUTGamePad::R;
                }
                break;
            }

            case PAD_ERR_NO_CONTROLLER:
            {
                disconnected_ports |= (0x80000000 >> port);
                break;
            }
        }
    }

    if (disconnected_ports != 0) {
        PADReset(disconnected_ports);
    }

    for (int port = 0; port < PAD_MAX_CONTROLLERS; port++) {
        if (InputValid[port]) {            
            InputTrigger[port] = InputData[port] & (InputButton[port] ^ InputData[port]); // Update trigger to only newly pressed buttons
            InputButton[port] = InputData[port]; // update pressed buttons

            if (InputButton[port] == 0) {
                InputRepeat[port] = 0;
            }
            else if (InputTrigger[port] != 0) {
                InputRepeat[port] = InputButton[port];
                InputRepeatCount[port] = 15;
            }
            else {
                if (InputRepeatCount[port] != 0) {
                    InputRepeatCount[port]--;
                    InputRepeat[port] = 0;
                }
                else {
                    InputRepeatCount[port] = 0;
                    InputRepeat[port] = InputButton[port];
                }
            }
        }
        else {
            InputButton[port] = 0;
            InputTrigger[port] = 0;
            InputRepeat[port] = 0;
        }
    }
}
#pragma dont_inline reset

// @HACK - we need to force J2DOrthoGraph's dtor to emit
static void fake() {
    J2DOrthoGraph graph;

    graph.~J2DOrthoGraph();
}

extern void famicom_1frame() {
    nogbaInput();
    u32 flags = 0;

    if (famicomCommon.noise_bufp == nullptr) {
        flags |= 0x1000;
    }

    famicom_key_convert();
    famicomCommon.wp->pads[4] = 0;
    famicomCommon.wp->pads[5] = 0;
    famicomCommon.wp->pads[6] = 0;

    /* Special input modes activated when L & R are held */
    /*
        Always - L + R + START = reset console
        Zurumode 1 - L + R + D-Left = Toggle obj 9 'over' mode
        Zurumode 1 - L + R + A = Toggle rapid A mode
        Zurumode 1 - L + R + B = Toggle rapid B mode
    */
    if ((InputButton[0] & (JUTGamePad::L | JUTGamePad::R)) == (JUTGamePad::L | JUTGamePad::R) &&
        filer_mode == 0 && filer_demo_mode == FILER_DEMO_MODE_NORMAL) {
        if ((InputTrigger[0] & JUTGamePad::START)) {
            if (famicomCommon.internal_save_datap != nullptr) {
                nesinfo_update_highscore(famicomCommon.internal_save_datap, 1);
            }

            ksNesPushResetButton(famicomCommon.sp);
        }

        if (APPNMI_ZURUMODE_GET()) {
            if ((InputTrigger[0] & JUTGamePad::DPAD_LEFT)) {
                nines_over_mode ^= TRUE;

                JUTReport(30, 46, 30, "OBJ 9'S OVER %s", nines_over_mode ? "DISABLED" : "ENABLED");
            }

            if ((InputTrigger[0] & JUTGamePad::DPAD_RIGHT)) {
                famicomCommon.low_res_mode ^= TRUE;
            }

            if ((InputTrigger[0] & JUTGamePad::A)) {
                rapid_mode_a ^= TRUE;
            }

            if ((InputTrigger[0] & JUTGamePad::B)) {
                rapid_mode_b ^= TRUE;
            }
        }
    }

    if (nesrom_idx_loaded != -1 &&
        ((InputButton[0] & (JUTGamePad::L | JUTGamePad::R)) !=  (JUTGamePad::L | JUTGamePad::R) || ((InputButton[0] & JUTGamePad::START) == 0 && APPNMI_ZURUMODE_GET() == 0))) {
        u32 frames = 1;
        
        if (APPNMI_ZURUMODE_GET() && (InputButton[0] & JUTGamePad::L)) {
            /* When zurumode 1 is enabled, L + C-Stick (Left/Right) adjusts emulation speed */
            JUTGamePad* pad = (JUTGamePad*)&gamePad[0];
            if (pad->mSubStick.mX > 0.1) {
                hispeed_mode = 2.0f + (pad->mSubStick.mX - 0.1f) * 8.9f;
                slow_mode = 0;
                speed_show = 60;
            }
            else if (pad->mSubStick.mX < -0.1) {
                slow_mode = 2.0f + (-pad->mSubStick.mX - 0.1f) * 3.4f;
                hispeed_mode = 0;
                speed_show = 60;
            }
            else {
                slow_mode = 0;
                hispeed_mode = 0;
                speed_show = 0;
            }
        }

        if (nines_over_mode) {
            flags |= KS_NES_FLAG_NINES_OVER_MODE;
        }

        
        if (slow_mode != 0) {
            frames = 1;
            if (speed_show != 0) {
                JUTReport(32, 48, 1, "1/%d SPEED", slow_mode);
            }
        }


        if (hispeed_mode != 0) {
            frames = hispeed_mode;
            if (speed_show != 0) {
                JUTReport(32, 48, 1, "%2dX SPEED", hispeed_mode);
            }
        }

        if (speed_show != 0) {
            speed_show--;
        }

        famicomCommon.wp->cpu_cycle_count = 0;
        famicomCommon.wp->frames = frames;

        do {
            if (slow_mode != 0) {
                slow_mode_sub++;
                if (slow_mode_sub < slow_mode) {
                    for (int j = 0; j < 262; j++) {
                        Sound_Write(0, 0, j * 114);   
                    }
                    break;
                }

                slow_mode_sub = 0;
            }

            ksnes_thread_exec(flags);
        } while (--frames > 0);
    }
    else {
        for (int j = 0; j < 262; j++) {
            Sound_Write(0, 0, j * 114);   
        }
    }

    GXSetScissor(0, 0, 640, 480);
    GXSetViewport(0.0f, 0.0f, 640.0f, 480.0f, 0.0f, 1.0f);
    ksNesDraw(famicomCommon.wp, famicomCommon.sp);

    if (filer_mode != 0 || filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
        JW_SetLowResoMode(FALSE);
        process_filer_mode();
    }
    else {
        JW_SetLowResoMode(famicomCommon.low_res_mode);
    }

    JW_SetFamicomMode(TRUE);

    famicom_draw();

    /* If filer mode is enabled, controller 4 L press toggles process profiling bar */
    if (filer_mode_enable && (((JUTGamePad*)gamePad)[3].mButtons.mTrigger & JUTGamePad::L)) {
        JUTProcBar::getManager()->setVisible(JUTProcBar::getManager()->isVisible() ? FALSE : TRUE);
    }

    if (filer_demo_mode != FILER_DEMO_MODE_NORMAL) {
        filer_demo_count++;

        if (filer_demo_count > 1200) {
            filer_demo_count = 0;
            nesrom_idx = nesrom_idx < (nesrom_count - 1) ? (nesrom_idx + 1) : 0;
        }

        if ((InputTrigger[0] & (
            JUTGamePad::MAINSTICK_LEFT |
            JUTGamePad::MAINSTICK_RIGHT |
            JUTGamePad::MAINSTICK_DOWN |
            JUTGamePad::MAINSTICK_UP |
            JUTGamePad::DPAD_LEFT |
            JUTGamePad::DPAD_RIGHT |
            JUTGamePad::DPAD_DOWN |
            JUTGamePad::DPAD_UP |
            JUTGamePad::L |
            JUTGamePad::R |
            JUTGamePad::A |
            JUTGamePad::X |
            JUTGamePad::Y
        ))) {
            filer_demo_mode = FILER_DEMO_MODE_NORMAL;
            filer_mode = 1;
        }
    }

    if (filer_mode_enable != FALSE && (InputTrigger[0] & JUTGamePad::R)) {
        filer_mode ^= 1;
        filer_demo_mode = FILER_DEMO_MODE_NORMAL;
    }

    if ((flags & 0x400) != 0 && famicomCommon.sp->motor_timer != 0 && (famicomCommon.sp->vblank_frame_counter & 0x10)) {
        // Please wait
        JUTReport(400, 350, 1, "omati kudasai...");
    }

    if (famicomCommon.internal_save_datap != nullptr) {
        nesinfo_update_highscore(famicomCommon.internal_save_datap, 0);
    }
}

extern int famicom_rom_load_check() {
    if (famicomCommon.nesrom_memcard == FALSE) {
        while (*nesrom_filename_ptrs[nesrom_idx] == ':') {
            nesrom_idx++;
            if (nesrom_idx >= nesrom_count) {
                if (nesrom_idx_loaded < 0) {
                    OSPanic(__FILE__, 6072, "no nesfile specified");
                    continue;
                }

                nesrom_idx = 0;
            }
        }
    }

    if (nesrom_idx_loaded != nesrom_idx) {
        if (famicom_rom_load() == 0) {
            nesrom_idx_loaded = nesrom_idx;
        }
        else {
            nesrom_idx_loaded = -1;
        }
    }

    return nesrom_idx_loaded;
}

static int famicom_cardResultToMiyake(s32 cardResult) {
    switch (cardResult) {
        case CARD_RESULT_READY:
            return FAMICOM_RESULT_OK;
        case CARD_RESULT_INSSPACE:
            return FAMICOM_RESULT_NOSPACE;
        case CARD_RESULT_NOENT:
            return FAMICOM_RESULT_NOENTRY;
        case CARD_RESULT_BROKEN:
            return FAMICOM_RESULT_BROKEN;
        case CARD_RESULT_WRONGDEVICE:
            return FAMICOM_RESULT_WRONGDEVICE;
        case CARD_RESULT_ENCODING:
            return FAMICOM_RESULT_WRONGENCODING;
        case CARD_RESULT_NOCARD:
            return FAMICOM_RESULT_NOCARD;
        case CARD_RESULT_NOFILE:
            return FAMICOM_RESULT_NOFILE;
        default:
            return -1;
    }
}

extern int famicom_internal_data_load() {
    famicom_mount_archive_wait();
    bzero(&famicomCommon, sizeof(FamicomCommon));
    famicomCommon.nesrom_memcard = FALSE;
    strncpy((char*)famicomCommon.mura_save_name, "DobutsunomoriP_MURA", 32);
    strncpy((char*)famicomCommon.famicom_save_name, "DobutsunomoriP_F_SAVE", 32);
    famicomCommon.save_data_name = (u8*)"GAFEFSC\x1A";
    famicomCommon.save_data_single_size = 0x660;
    famicomCommon.save_data_total_size = sizeof(FamicomSaveDataHeader) + 0x660 * PLAYER_NUM;
    famicomCommon.save_data_header = (FamicomSaveDataHeader*)famicomCommonSave;
    famicomCommon.internal_save_datap = nullptr;

    s32 cardResult = famicom_common_save_initial();
    s32 chan;
    if (cardResult == CARD_RESULT_NOFILE) {
        chan = famicom_getSaveChan(famicomCommon.nesrom_memcard, &cardResult);
        if (chan >= 0) {
            s32 freeBytes;
            s32 freeEntries;

            cardResult = memcard_getFreeBlocks(chan, &freeBytes, &freeEntries);
            if (cardResult == CARD_RESULT_READY) {
                if (freeEntries < 1) {
                    cardResult = CARD_RESULT_NOENT;
                }
                else if (freeBytes < 0x2000) {
                    cardResult = CARD_RESULT_INSSPACE;
                }
            }
        }
    }

    /* @BUG - chan can be an invalid value due to being allocted on the stack & cardResult not being CARD_RESULT_NOFILE */
    if (cardResult != CARD_RESULT_READY && cardResult != CARD_RESULT_FATAL_ERROR && chan >= 0) {
        errorChan = chan;
    }

    return famicom_cardResultToMiyake(cardResult);
}

extern int famicom_internal_data_save() {
    s32 result;
    s32 chan;
    
    famicom_mount_archive_wait();
    bzero(&famicomCommon, sizeof(FamicomCommon));
    famicomCommon.nesrom_memcard = FALSE;
    strncpy((char*)famicomCommon.mura_save_name, "DobutsunomoriP_MURA", 32);
    strncpy((char*)famicomCommon.famicom_save_name, "DobutsunomoriP_F_SAVE", 32);
    famicomCommon.save_data_name = (u8*)"GAFEFSC\x1A";
    famicomCommon.save_data_single_size = 0x660;
    famicomCommon.save_data_total_size = sizeof(FamicomSaveDataHeader) + 0x660 * PLAYER_NUM;
    famicomCommon.save_data_header = (FamicomSaveDataHeader*)famicomCommonSave;
    famicomCommon.internal_save_datap = nullptr;

    chan = famicom_getSaveChan(FALSE, &result);
    if (chan >= 0) {
        famicomCommon.unused_save_data_start_ofs = 0x640;
        famicomCommon.memcard_save_comment = commentImageBuffer;
        famicomCommon.memcard_game_header.flags0.no_copy_flag = FALSE;
        famicomCommon.memcard_game_header.flags1.no_move_flag = FALSE;
        SetupInternalCommentImage(commentImageBuffer);

        if (famicom_save_data_check(famicomCommon.save_data_header, famicomCommon.save_pl_no, famicomCommon.save_data_name) == 0) {
            int no_save = famicomCommon.save_data_header->no_save;
            famicomCommon.save_data_header->no_save = FALSE;

            famicom_save_data_setup(famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
            memcard_data_load(chan, (char*)famicomCommon.famicom_save_name, (char*)famicomCommon.mura_save_name, nullptr, famicomCommon.save_data_total_size, famicomCommon.save_data_name);
            
            result = memcard_data_save(
                chan,
                (char*)famicomCommon.famicom_save_name,
                (char*)famicomCommon.mura_save_name,
                famicomCommon.save_data_header,
                famicomCommon.save_data_total_size,
                famicomCommon.memcard_save_comment,
                famicomCommon.memcard_game_header.flags1.banner_fmt,
                famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : CARD_COMMENT_SIZE,
                famicomCommon.memcard_game_header.icon_format,
                famicomCommon.memcard_game_header.icon_flags,
                famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : 0,
                no_save
            );
        }
    }

    if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR && chan >= 0) {
        errorChan = chan;
    }

    return famicom_cardResultToMiyake(result);
}

extern int famicom_external_data_save() {
    s32 result;
    s32 chan;

    famicom_mount_archive_wait();
    if (famicomCommon.nesrom_memcard == FALSE) {
        result = CARD_RESULT_FATAL_ERROR;
    }
    else if (famicomCommon.save_data_header == nullptr) {
        result = CARD_RESULT_NOCARD;
        chan = 2;
    }
    else {
        chan = famicom_getSaveChan(TRUE, &result);
        if (chan >= 0) {
            if (famicom_save_data_check((FamicomSaveDataHeader*)famicomCommon.save_data_header, -1, famicomCommon.save_data_name) == 0) {
                if (famicomCommon.nesrom_memcard && famicomCommon.memcard_game_header.flags0.has_comment_img) {
                    int no_save = ((FamicomSaveDataHeader*)famicomCommon.save_data_header)->no_save;
                    ((FamicomSaveDataHeader*)famicomCommon.save_data_header)->no_save = FALSE;

                    famicom_save_data_setup((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
                    memcard_data_load(chan, (char*)famicomCommon.famicom_save_name, (char*)famicomCommon.mura_save_name, nullptr, famicomCommon.save_data_total_size, famicomCommon.save_data_name);
                    
                    result = memcard_data_save(
                        chan,
                        (char*)famicomCommon.famicom_save_name,
                        (char*)famicomCommon.mura_save_name,
                        (FamicomSaveDataHeader*)famicomCommon.save_data_header,
                        famicomCommon.save_data_total_size,
                        famicomCommon.memcard_save_comment,
                        famicomCommon.memcard_game_header.flags1.banner_fmt,
                        famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : CARD_COMMENT_SIZE,
                        famicomCommon.memcard_game_header.icon_format,
                        famicomCommon.memcard_game_header.icon_flags,
                        famicomCommon.memcard_game_header.flags0.comment_type == MEMCARD_COMMENT_TYPE_NONE ? 0xFFFFFFFF : 0,
                        no_save
                    );
                }
                else {
                    result = CARD_RESULT_READY;
                }
            }
            else {
                result = CARD_RESULT_FATAL_ERROR;
            }
        }
    }

    if (result != CARD_RESULT_READY && result != CARD_RESULT_FATAL_ERROR && chan >= 0) {
        errorChan = chan;
    }

    return famicom_cardResultToMiyake(result);
}

extern int famicom_external_data_save_check() {
    return FALSE;
}

static s32 famicom_common_save_initial() {
    s32 result;
    s32 chan = famicom_getSaveChan(famicomCommon.nesrom_memcard, &result);

    if (chan >= 0) {
        // Loading Famicom save data.
        OSReport("ファミコンセーブデータを読み込みます\n");
        result = memcard_data_load(chan, (char*)famicomCommon.famicom_save_name, (char*)famicomCommon.mura_save_name, (FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_total_size, famicomCommon.save_data_name);

        if (famicom_save_data_check((FamicomSaveDataHeader*)famicomCommon.save_data_header, -1, famicomCommon.save_data_name) != 0) {
            famicom_save_data_init((FamicomSaveDataHeader*)famicomCommon.save_data_header, famicomCommon.save_data_single_size, famicomCommon.save_data_name);
        }
    }

    return result;
}

extern int famicom_get_disksystem_titles(int* n_games, char* title_name_bufp, int namebuf_size) {
    famicom_mount_archive_wait();
    return memcard_game_list(n_games, title_name_bufp, namebuf_size) == CARD_RESULT_READY;
}
