/**
 *  Sega Saturn Cartridge Firmware Flash Utility
 *  by cafe-alpha.
 *  WWW: http://ppcenter.free.fr/pskai/
 *
 *  Made from "Sega Saturn cartridge flash tool", by Anders Montonen.
 *
 *  See LICENSE file for details.
**/

#include <stdio.h>
#include <string.h> /* For memset,memcpy */

#include "vdp2.h"
#include "smpc.h"

#include "vdp.h"
#include "conio.h"

#include "menu.h"

#include "flash.h"

#include "../../satcom_lib/sc_compress.h"
#include "romwrap.h"

#include "../release.h"


typedef struct _fw_entry
{
    /* Line displayed in menu. */
    char* desc;

    /* Firmware data and length. */
    unsigned char* data_stt;
    unsigned char* data_end;

    /* Text displayed under the menu. */
    char* detail1;
    char* detail2;

    /* Fill value for unused area after firmware. */
    unsigned char fill_val;
} fw_entry;

/* The firmware list. */
fw_entry _fw_list[] =
{
    { "PseudoSaturn v0.832            ",
        (unsigned char*)&_fw_ps_0832_dat, 
        (unsigned char*)&_fw_ps_0832_end, 
        "Pseudo Saturn Firmware 0.832         ", 
        "                         PS_0832.BIN ", 
        0xFF, 
    }, 
    { "PseudoSaturn v0.832 - no logo  ",
        (unsigned char*)&_fw_ps_0832a_dat, 
        (unsigned char*)&_fw_ps_0832a_end, 
        "Pseudo Saturn Firmware 0.832A        ", 
        "                        PS_0832A.BIN ", 
        0xFF, 
    }, 
    { "PseudoSaturn v0.831            ",
        (unsigned char*)&_fw_ps_0831_dat, 
        (unsigned char*)&_fw_ps_0831_end, 
        "Pseudo Saturn Firmware 0.831         ", 
        "                         PS_0831.BIN ", 
        0xFF, 
    }, 
    { "PseudoSaturn v0.830            ",
        (unsigned char*)&_fw_ps_0830_dat, 
        (unsigned char*)&_fw_ps_0830_end, 
        "Pseudo Saturn Firmware 0.830         ", 
        "                         PS_0830.BIN ", 
        0xFF, 
    }, 
    { "AR PLUS V2.02 + Codes         ",
        (unsigned char*)&_fw_arp_202c_dat, 
        (unsigned char*)&_fw_arp_202c_end, 
        "Action Replay Plus Firmware          ", 
        "Cheat Codes Included    ARP_202C.BIN ", 
        0xFF, 
    }, 
    { "ACTION REPLAY (PLUS) V2.02    ",
        (unsigned char*)&_fw_arp_202_dat, 
        (unsigned char*)&_fw_arp_202_end, 
        "Action Replay Plus Firmware          ", 
        "                         ARP_202.BIN ", 
        0xFF, 
    }, 
    { "ACTION REPLAY (PLUS) V2.01    ",
        (unsigned char*)&_fw_arp_201_dat, 
        (unsigned char*)&_fw_arp_201_end, 
        "Action Replay Plus Firmware          ", 
        "                         ARP_201.BIN ", 
        0xFF, 
    }, 
    { "AR PLUS V2.01 + Saves         ",
        (unsigned char*)&_fw_arp_201d_dat, 
        (unsigned char*)&_fw_arp_201d_end, 
        "Action Replay Plus Firmware          ", 
        "+Darius Backup Saves    ARP_201D.BIN ", 
        0xFF, 
    }, 
    { "GAME SHARK SATURN V1.96       ",
        (unsigned char*)&_fw_gss_196_dat, 
        (unsigned char*)&_fw_gss_196_end, 
        "GameShark Firmware by Interact       ", 
        "                         GSS_196.BIN ", 
        0x00, 
    }, 
    { "MEMORY CARD PLUS + Saves      ",
        (unsigned char*)&_fw_mcp_001_dat, 
        (unsigned char*)&_fw_mcp_001_end, 
        "Memory Card Plus Firmware            ", 
        "Backup Saves Included    MCP_001.BIN ", 
        0xFF, 
    }, 
    { "ACTION REPLAY V1.94 + Codes   ",
        (unsigned char*)&_fw_ars_194c_dat, 
        (unsigned char*)&_fw_ars_194c_end, 
        "Pro Action Replay Firmware by Datel  ", 
        "Cheat Codes Included    ARS_194C.BIN ", 
        0xFF, 
    }, 
    { "ACTION REPLAY V1.94           ",
        (unsigned char*)&_fw_ars_194_dat, 
        (unsigned char*)&_fw_ars_194_end, 
        "Pro Action Replay Firmware by Datel  ", 
        "                         ARS_194.BIN ", 
        0xFF, 
    }, 
    { "ACTION REPLAY V1.81           ",
        (unsigned char*)&_fw_ars_181_dat, 
        (unsigned char*)&_fw_ars_181_end, 
        "Pro Action Replay Firmware by Datel  ", 
        "                        DATEL181.BIN ", 
        0xFF, 
    }, 
    /* Terminating entry. */
    { "", NULL, NULL, "",  "", 0x00}
};


char* empty_line = "                                     ";

unsigned char char2pchar(unsigned char c)
{
    if(c == 0xFF) return 'X';
    if(c < ' ') return '.';
    if(c > 127) return '?';
    return c;
}

void flash_erase_callback(int page_cnt, int page)
{
    int p;

    if(page_cnt == 0)
    {
        p = 0;
        conio_printf(26, 13, COLOR_WHITE, "      ");
    }
    else
    {
        p = (100*page) / page_cnt;
        conio_printf(26, 13, COLOR_WHITE , "(   %%)");
        conio_printf(27, 13, COLOR_YELLOW, "%02d", p);
    }
}

#define ERROR_ERASE 1
#define ERROR_WRITE 2
void DisplayError(Romcheck_t* pCheckResults, int type)
{
    int i, j;
    pad_t* pad;

    /* Display error message. */
    if(type == ERROR_ERASE)
    {
        conio_printf(2, 17, COLOR_RED, "Erase verification failure !");
    }
    else
    {
        conio_printf(2, 17, COLOR_RED, "Write verification failure !");
    }

    /* Display offset where error happened. */
    conio_printf(2, 18, COLOR_YELLOW, "Check Error Offset = 0x%08X", pCheckResults->ErrorOffset);

    /* Display hex dump near where error happened and stop everything. */
    conio_printf(2, 26, COLOR_WHITE, "Press Start to exit");
    while (1)
    {
        pad = pad_read();
        if(pad->delta[0] & PAD_START)
        {
            return;
        }

        /* Refresh dump display, in order to spot furtive memory error. */
        conio_printf(2, 19, COLOR_YELLOW, "Dump Start Address = 0x%08X", pCheckResults->pErrorData);
#define CHAR_PER_LINE 8
        for(j=0; j<4; j++)
        {
            /* Display start address. */
            conio_printf(2, 20+j, COLOR_YELLOW, "+%02X:", j*CHAR_PER_LINE);

            for(i=0; i<CHAR_PER_LINE; i++)
            {
                unsigned char c = pCheckResults->pErrorData[j*CHAR_PER_LINE+i];
                conio_printf(2+4+i*3  , 20+j, COLOR_WHITE, "%02X", c);
                conio_printf(2+4+8*3+i, 20+j, COLOR_GRAY , "%c"  , char2pchar(c));
            }
        }

        WaitForVBLANKIn();
        WaitForVBLANKOut();
    }
}

/**
 * DisplayFlashIds
 * Display vendor/device IDs.
 */
#define PROGRESS_STR_CNT 10
char* _progress_str[] = {
    "O    ", 
    ".O   ", 
    " .O  ", 
    "  .O ", 
    "   .O", 
    "    O", 
    "   O.", 
    "  O. ", 
    " O.  ", 
    "O.   "
};
int _progress_id = 0;
int _progress_divider = 0; /* In order to slow down the progress stuff. */
void DisplayFlashIds(FlashDevice_t* pDevice, unsigned short* pVendorID, unsigned short* pDeviceID)
{
    unsigned short color;

    /* Display progress, in order to know if Saturn is still running or not. */
    if((_progress_id < 0)
    || (_progress_id >= PROGRESS_STR_CNT))
    {
        _progress_divider = 0;
        _progress_id = 0;
    }
    else
    {
        _progress_divider++;
        if(_progress_divider >= 5)
        {
            _progress_divider = 0;
            _progress_id = (_progress_id+1) % PROGRESS_STR_CNT;
        }
    }
    conio_printf(2, 6, COLOR_WHITE, "Updating Flash Info [%s]", _progress_str[_progress_id]);

    conio_printf(2   , 4, COLOR_WHITE, "Flash Vendor ID:");
    conio_printf(2+22, 4, COLOR_WHITE, "-");
    // WaitForVBLANKIn();
    // WaitForVBLANKOut();

    *pVendorID = FlashGetVendorID();
    conio_printf(19, 4, COLOR_YELLOW, "%04X", *pVendorID);
    // WaitForVBLANKIn();
    // WaitForVBLANKOut();

    conio_printf(2   , 5, COLOR_WHITE, "Flash Device ID:");
    conio_printf(2+22, 5, COLOR_WHITE, "-");
    // WaitForVBLANKIn();
    // WaitForVBLANKOut();

    *pDeviceID = FlashGetDeviceID();
    conio_printf(19, 5, COLOR_YELLOW, "%04X", *pDeviceID);
    // WaitForVBLANKIn();
    // WaitForVBLANKOut();

    FillFlashDeviceInfo(*pVendorID, *pDeviceID, pDevice);
    SetFlashEraseCallback(flash_erase_callback, pDevice);

    /* Change color of vendor/device strings according to flashability. */
    switch(pDevice->Flashability)
    {
    default:
    case(FLASH_HOPELESS):
        color = COLOR_RED;
        break;

    case(FLASH_MAYBEOK):
        color = COLOR_YELLOW;
        break;

    case(FLASH_RECOGNIZED):
        color = COLOR_GREEN;
        break;
    }
    conio_printf(26, 4, color, (char*)pDevice->pVendorString);
    conio_printf(26, 5, color, (char*)pDevice->pDeviceString);
}

/**
 * ClearFlashIdsProgress
 */
void ClearFlashIdsProgress(void)
{
    conio_printf(2, 6, COLOR_WHITE, empty_line);
}


/* Flash memory related infos. */
FlashDevice_t  device;
unsigned short vendorID;
unsigned short deviceID;

menu_items_t    _menu_items;
menu_settings_t _menu_settings;
int menu_selected = 0;

void display_flash_ids(void)
{
    /* Flash memory : first get infos about flash chips. */
    DisplayFlashIds(&device, &vendorID, &deviceID);
}

void display_fw_infos(int id)
{
    /* Display additional informations about firmware. */
    conio_printf(2, 24, COLOR_WHITE, empty_line);
    if(id >= 0)
    {
        conio_printf(2, 24, COLOR_SILVER, "%s", _fw_list[id].detail1);
    }

    conio_printf(2, 25, COLOR_WHITE, empty_line);
    if(id >= 0)
    {
        conio_printf(2, 25, COLOR_SILVER, "%s", _fw_list[id].detail2);
    }
}

void FlasherProcess(void)
{
    menu_settings_t* mset   = &_menu_settings;
    menu_items_t   * mitems = &_menu_items;

    unsigned short* pRom = (unsigned short*)0x20200000;
#define PROM_LEN (1024*1024)
    unsigned long   CmpLen;
    unsigned long   RomLen;

    unsigned long  page;
    Romcheck_t     checkRes;
    pad_t* pad;
    int i;

    /* Flash firmware : first get infos about flash chips. */
    DisplayFlashIds(&device, &vendorID, &deviceID);

    /* Initialize menu internals. */
    menu_reset_settings(mset);
    menu_reset_items(mitems);
    menu_set_callback_redraw(mset, display_fw_infos);
    menu_set_callback_vblank(mset, display_flash_ids);

    menu_set_title(mset, "SELECT FIRMWARE");
    i=0;
    while(_fw_list[i].data_stt != NULL)
    {
        menu_set_item(mitems, i/*id*/, _fw_list[i].desc, i/*tag*/);
        i++;
    }
    menu_set_pos(mset, -1/*x0*/, 60/*y0*/, 32/*w*/, i+1/*h*/, 1/*cols*/);
    /* Clear screen after executing menu. */
    menu_set_erase_on_exit(mset, 1);

    /* By default, select first firmware. */
    menu_selected = 0;
    menu_selected = menu_start(mitems, mset, menu_selected/*selected_tag*/);

    /* Erase additional display area. */
    display_fw_infos(-1);
    ClearFlashIdsProgress();

    /* Cancel ? */
    if(mset->exit_code == MENU_EXIT_CANCEL)
    {
        return;
    }

    /* Test flash memory integrity. */
    if(device.BrickWarning)
    {
        /* Unknown device, but maybe Action Replay Flash chips.
         * Warn user that this program will destroy his ARP before continuing.
         */
        conio_printf(2, 21, COLOR_YELLOW, "Uncommon flash chips ! Do you want to");
        conio_printf(2, 22, COLOR_YELLOW, "try with default chip settings ?     ");
        conio_printf(2, 23, COLOR_WHITE , "You can slightly move your cartridge ");
        conio_printf(2, 24, COLOR_WHITE , "in order to improve its detection.   ");
        conio_printf(2, 25, COLOR_RED   , "Please try at your own risk !!!      ");
        conio_printf(2, 26, COLOR_WHITE , "Press Start: exit, A+B+C: continue.  ");

        while (1)
        {
            /* Q&D hack : display vendor/device IDs during idle. */
            DisplayFlashIds(&device, &vendorID, &deviceID);

            pad = pad_read();
            if((pad->new[0] & (PAD_A|PAD_B|PAD_C)) == (PAD_A|PAD_B|PAD_C))
            {
                break;
            }
            if(pad->delta[0] & PAD_START)
            {
                return;
            }
            WaitForVBLANKIn();
            WaitForVBLANKOut();
        }

        /* User decided to continue, so clear disclaimer. */
        conio_printf(2, 21, COLOR_WHITE, empty_line);
        conio_printf(2, 22, COLOR_WHITE, empty_line);
        conio_printf(2, 23, COLOR_WHITE, empty_line);
        conio_printf(2, 24, COLOR_WHITE, empty_line);
        conio_printf(2, 25, COLOR_WHITE, empty_line);
        conio_printf(2, 26, COLOR_WHITE, empty_line);

        /* Flash memory : clear progress display. */
        ClearFlashIdsProgress();
    }

    /* Final check of flash memory integrity. */
    if(device.RomSize == 0 || device.PageSize == 0)
    {
        /* Unknown device.
         * Don't go any further, but update display of vendor/device IDs.
         */
        conio_printf(2, 21, COLOR_RED  , "Unrecognized flash chips !           ");
        conio_printf(2, 22, COLOR_RED  , "Reflash process is interrupted.      ");
        conio_printf(2, 23, COLOR_WHITE , "You can slightly move your cartridge ");
        conio_printf(2, 24, COLOR_WHITE, "in order to improve its detection.   ");
        conio_printf(2, 25, COLOR_RED  , "Please try at your own risk !!!      ");
        conio_printf(2, 26, COLOR_WHITE, "Press Start to exit.                 ");

        while (1)
        {
            /* Q&D hack : display vendor/device IDs during idle. */
            DisplayFlashIds(&device, &vendorID, &deviceID);
 
            pad = pad_read();
            if(pad->delta[0] & PAD_START)
            {
                return;
            }
            WaitForVBLANKIn();
            WaitForVBLANKOut();
        }
    }

    /* Set pointer and length of selected firmware. */
    memset((void*)pRom, _fw_list[menu_selected].fill_val, PROM_LEN);
    CmpLen = (unsigned long)(_fw_list[menu_selected].data_end - _fw_list[menu_selected].data_stt);
    RomLen = decomp_exec(
            (unsigned char*)_fw_list[menu_selected].data_stt, 
            CmpLen, 
            (unsigned char*)pRom);

    conio_printf(2, 7, COLOR_GRAY, "%s", _fw_list[menu_selected].desc);
    conio_printf(2, 8, COLOR_GRAY, "Compressed size  : %6d (%3d KB)", CmpLen, CmpLen>>10);
    conio_printf(2, 9, COLOR_GRAY, "Decompressed size: %6d (%3d KB)", RomLen, RomLen>>10);

    /* Verify that firmware fits into flash memory. */
    if(RomLen > (device.RomSize * sizeof(unsigned short)))
    {
        conio_printf(2, 21, COLOR_RED, "Firmware doesn't fit in flash memory!");
        conio_printf(2, 22, COLOR_RED, "FW sze: %d KB, Flash size: %d KB", 
            RomLen>>10, 
            (device.RomSize * sizeof(unsigned short)) >> 10);
        conio_printf(2, 23, COLOR_RED, "Reflash process is interrupted.       ");

        conio_printf(2, 26, COLOR_WHITE, "Press Start to exit");

        while (1)
        {
            pad = pad_read();
            if(pad->delta[0] & PAD_START)
            {
                return;
            }
            WaitForVBLANKIn();
            WaitForVBLANKOut();
        }
    }

    conio_printf(2, 11, COLOR_WHITE, "Press A+B+C to initiate write...");
    conio_printf(2, 12, COLOR_WHITE, "Press Start to cancel.");
    while (1)
    {
        pad = pad_read();

        if(pad->delta[0] & PAD_START)
        {
            return;
        }

        if((pad->new[0] & (PAD_A|PAD_B|PAD_C)) == (PAD_A|PAD_B|PAD_C))
        {
            break;
        }
        WaitForVBLANKIn();
        WaitForVBLANKOut();
    }

    conio_printf(2, 13, COLOR_WHITE, "Erasing... Please wait.");

    conio_printf(2, 14, COLOR_RED  , "DO NOT RESET OR TURN OFF YOUR SATURN");
    device.EraseFlash(&device);
    conio_printf(2, 14, COLOR_WHITE, empty_line);

    /* Test flash erase. */
    if(!device.EraseOK(&device, &checkRes))
    {
        DisplayError(&checkRes, ERROR_ERASE);
        return;
    }

    /* Re-write whole flash memory. */
    conio_printf(2, 14, COLOR_WHITE, "Writing...");
    conio_printf(2, 15, COLOR_WHITE, "Page          of          ");
    conio_printf(2, 16, COLOR_RED  , "DO NOT RESET OR TURN OFF YOUR SATURN");

    conio_printf(19, 15, COLOR_YELLOW, "%08X", device.RomSize / device.PageSize);

    for (page = 0; page < device.RomSize / device.PageSize; page++)
    {
        conio_printf(7, 15, COLOR_YELLOW, "%08X", page + 1);
        device.WriteFlashPage(&device, page,
                              (unsigned short*)(pRom + (page * device.PageSize)));
    }

    conio_printf(2, 16, COLOR_WHITE, empty_line);

    /* Test flash write. */
    if(!device.WriteOK(&device, &checkRes, (const unsigned short*)(pRom)))
    {
        DisplayError(&checkRes, ERROR_WRITE);
        return;
    }
    else
    {
        conio_printf(2, 17, COLOR_GREEN, "Write verification success.");
    }

    conio_printf(2, 23, COLOR_WHITE, "Done!");
    conio_printf(2, 26, COLOR_WHITE, "Press Start to exit");

    while (1)
    {
        pad = pad_read();
        if(pad->delta[0] & PAD_START)
        {
            return;
        }
        WaitForVBLANKIn();
        WaitForVBLANKOut();
    }
}


void _main(void)
{
    menu_settings_t* mset   = &_menu_settings;
    menu_items_t   * mitems = &_menu_items;

    vdp_init();
    conio_init();
    pad_init();

    TVMD = 0x8000;

    conio_printf(2, 2, COLOR_BG_FG(COLOR_AQUA, COLOR_YELLOW), " Saturn Cartridge Flasher -- V%d.%03d ", RELEASE_ID/1000, RELEASE_ID%1000);

    while(1)
    {
        FlasherProcess();

        /* Clear display. */
        int i;
        for(i=7; i<27; i++)
        {
            conio_printf(2, i, COLOR_WHITE, empty_line);
        }

        /* Display "Do you want to exit ?" menu. */
        menu_reset_settings(mset);
        menu_reset_items(mitems);
        menu_set_title(mset, "Please select action");
        menu_set_item(mitems, 0/*id*/, "   Restart Flasher  ", 0/*tag*/);
        menu_set_item(mitems, 1/*id*/, " Exit to Multiplayer", 1/*tag*/);
        menu_set_pos(mset, -1/*x0*/, 60/*y0*/, 21/*w*/, 3/*h*/, 1/*cols*/);
        menu_set_erase_on_exit(mset, 1);

        menu_selected = 0;
        menu_selected = menu_start(mitems, mset, menu_selected/*selected_tag*/);

        /* Exit to multiplayer ? */
        if(((mset->exit_code == MENU_EXIT_OK) && (menu_selected == 1))
        ||  (mset->exit_code == MENU_EXIT_CANCEL))
        {
            (**(void(**)(void))0x600026C)();
        }
    }
}
