/* 
 * This software is licensed under terms of the GNU GENERAL PUBLIC LICENSE
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software  is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Source code is available at http://ppcenter.free.fr
 */


#include <string.h> /* For memset,memcpy */
#include <stdarg.h>
#include <stdio.h>
size_t strnlen(const char *s, size_t maxlen);



/*
 * SatCom-related include.
 */
#include "sc_saturn.h"

/*
 * SatCom Global data definition.
 * Used for/by copystub, debugger, crappy debugger
 */
#include "sc_global_data.h"
sc_global_t* sc_global = (sc_global_t*)SC_GLOBAL_DATA_ADR;







/* Set to 1 when SatCom has been intialized. */
unsigned char _sc_init_ok = 0;


/*----------------------------------------------------------------------------*/
/* Custom memory allocator routines. -----------------------------------------*/
#include "bget/bget.h"
#include "bget/bget.c"





#define CARTRAM_ID_8M       0x5a
#define CARTRAM_ID_32M      0x5c

#define CartRAM0    ((void *)0x02400000)
#define CartRAM1    ((void *)0x02600000)

//  RB_initRAMCart(unsigned char cs, unsigned char quiet)
//  - checks for present RAM cart, inits and verifies it
//
//  inputs:
//      cs          -   select whether to set CS0 or CS1 space 
//                      (both works, don't know where the difference is)   
//      quiet       -   don't print messages
//
//  returns:
//      cartridge id    -   on success
//                          (8MB: 0x5a or 32MB: 0x5c)
//      -1              -   no cart
//      -2              -   data verify failed 

unsigned int CartRAMsize = 0;

/*
 * When verifying all the data in the memory cart, about 3~4 seconds
 * are required, so in order to speed-up memory cart verify process, 
 * only some data in the memory cart are checked.
 */
#define VERIFY_INCR 17
int RB_CartRAM_init(unsigned char cs, unsigned char quiet)
{
    unsigned long setReg, refReg;
    unsigned char id;

    // cartridge initialization: as in ST-TECH-47.pdf
    // 1. verify cartridge id
    // 5A - 8 MB
    // 5C - 32 MB
    //! is last byte of A-BUS CS1 area
    id = *((unsigned char *)0x24ffffff);
    if(id == 0x5a) {
        CartRAMsize = 0x80000;
        //if(quiet == 0)
        //    slPrint("8 MB RAM cart detected!", slLocate(8,5));
    } else if(id == 0x5c) {
        CartRAMsize = 0x200000;
        //if(quiet == 0)
        //    slPrint("32 MB RAM cart detected!", slLocate(8,5));
    } else {
        CartRAMsize = 0x0;      
        // 2. prompt proper connection
        //if(quiet == 0) {
        //    slPrint("The extended RAM", slLocate(11,5));
        //    slPrint("cartridge is not", slLocate(11,6));
        //    slPrint("inserted properly.", slLocate(11,7));
        //
        //    slPrint("Please turn off", slLocate(11,9));
        //    slPrint("power and reinsert", slLocate(11,10));
        //    slPrint("the extended RAM", slLocate(11,11));
        //    slPrint("cartridge.", slLocate(11,12));
        //}    
        return -1;
    }

    // 3. write 1 to initialize
    // is at the end of A-BUS Dummy area
    // !!! word format !!! (but what is meant?)
    *((unsigned short *)0x257efffe) = 1;
    
    // 4. set A-bus set and refresh registers
    setReg = refReg = 0;
    if(cs == 0)
    {
        // set cs0 for 32MBit
        setReg |= 1 << 29;  // After-READ precharge insert bit
        setReg |= 3 << 24;  // Burst cycle wait number
        setReg |= 3 << 20;  // Normal cycle wait number
    }
    else
    {
        // set cs1 for 16MBit ??
        setReg |= 1 << 28;  // external wait effective bit
        setReg |= 15 << 24; // Burst cycle wait number
        setReg |= 15 << 20; // Normal cycle wait number
    }
    *((unsigned long *)0x25fe00B0) = setReg;
    // A-Bus refresh register
    refReg |= 1 << 4;       // A-Bus refresh enable bit
    // I've just take this value
    // see SCU user manual for value range
    refReg |= 1;            // A-Bus refresh cycle wait
    *((unsigned long *)0x25fe00B8) = refReg;

    unsigned long *DRAM0, *DRAM1, DRAMsize, i, ok;
    // 5. verify data before proceeding
    // cache-through DRAM0 and DRAM1
    DRAMsize = CartRAMsize >> 2; // byte length -> longword length
    DRAM0 = (unsigned long *)0x22400000;
    DRAM1 = (unsigned long *)0x22600000;
    // write
    for(i = 0; i < DRAMsize; i+=VERIFY_INCR) {
        *(DRAM0 + i) = i;
        *(DRAM1 + i) = DRAMsize - 1 - i;
    }    
    // read
    ok = 1;
    for(i = 0; i < DRAMsize; i+=VERIFY_INCR)
    {
        if(*(DRAM0 + i) != i)
        {
            //were_here("DRAM0 %i != %i", *(DRAM0 + i), i);
            ok = 0;
        }    
        if(*(DRAM1 + i) != (DRAMsize - 1 - i))
        {
            //were_here("DRAM1 %i != %i", *(DRAM1 + i), (DRAMsize - 1 - i));
            ok = 0;
        }    
    } 
       
    if(ok == 0)
    {
        //if(quiet == 0)
        //    slPrint("verifying RAM cart FAILED!", slLocate(8,7));
        return -2;
    }

    //if(quiet == 0)
    //    slPrint("verifying RAM cart OK!", slLocate(8,7));
    return id;
}




unsigned long _sc_ramcart_size = 0;

/**
 * Use an arbitrary memory space for custom malloc.
 * Call this function at the VERY BEGINING of your program !
 * Return memory pool total size.
**/
unsigned long _sc_pool_size    = 0;
unsigned long scm_pool(void *buffer, unsigned long len, unsigned short tag)
{
    scd_logout("scm_pool(buffer=0x%08X, len=%d, tag=0x%04X)", buffer, len, tag);

    if((buffer == NULL) || (len == 0)) return _sc_pool_size;

    bpool(buffer, len, tag);
    _sc_pool_size += len;
    return _sc_pool_size;
}

/**
 * Init memory space for custom malloc.
 * Call this function at the VERY BEGINING of your program !
 * Return memory pool total size.
**/
unsigned long _sc_mpool_state = 0; /* Prevent from double memory pooling. */
unsigned long scm_alloc_init(unsigned short init_flags)
{
    scd_logout("scm_alloc_init(init_flags=0x%04X)", init_flags);

    if(init_flags == 0) return _sc_pool_size;

    unsigned long flag = 0;


    flag = SCM_VDP1RAM;
    if(((init_flags      & flag) == flag)
    && ((_sc_mpool_state & flag) != flag))
    {
        _sc_mpool_state |= flag;

        // VRAM is last portion (SGL/DOCS/MEMORY.TXT)
        // not cache-through
        // 466.232 Bytes
        scm_pool((void*)0x05C0E1C0, 0x05C7FEF8-0x05C0E1C0, SCM_VDP1RAM);
    }

    flag = SCM_HRAM;
    if(((init_flags      & flag) == flag)
    && ((_sc_mpool_state & flag) != flag))
    {
        _sc_mpool_state |= flag;

        // HRAM last portion, not usable by user program, because of global data.
        // Used by SGL (see SGL/DOCS/MEMORY.TXT), but free in other case.
        // not cache-through
        // 262.144 Bytes
        scm_pool((void*)0x060C0000, 0x06100000-0x060C0000, SCM_HRAM);
    }

    flag = SCM_RAMCART;
    if(((init_flags      & flag) == flag)
    && ((_sc_mpool_state & flag) != flag))
    {
        _sc_mpool_state |= flag;

        // cartridge RAM, be quiet
        int CartID = RB_CartRAM_init(0, 1);
        if(CartID == CARTRAM_ID_32M)
        {// One continuous bank of cart RAM
            _sc_ramcart_size = 4;
            scm_pool(CartRAM0, CartRAMsize << 1, SCM_RAMCART);
        }
        else if(CartID == CARTRAM_ID_8M)
        { // Two separate, non-continuious cart RAM banks
            _sc_ramcart_size = 1;
            scm_pool(CartRAM0, CartRAMsize, SCM_RAMCART);
            scm_pool(CartRAM1, CartRAMsize, SCM_RAMCART);
        }
        else
        { // No RAM cart
            _sc_ramcart_size = 0;
        }
    }

    // low work RAM
    flag = SCM_LRAM;
    if(((init_flags      & flag) == flag)
    && ((_sc_mpool_state & flag) != flag))
    {
        _sc_mpool_state |= flag;

        scm_pool((void*)0x00200000, 0x100000, SCM_LRAM);
    }

    // // free portion of high work RAM up to SGL variable space (SGL/DOCS/MEMORY.TXT)
    // scm_pool((unsigned long)(&_bend), 0x060E0000 - ((unsigned long)(&_bend)));

    return _sc_pool_size;
}

/**
 * Custom malloc : use theses function instead of malloc/free.
**/
void* scm_malloc(unsigned long size, unsigned short tag)
{
    void* ret = bget(size, tag);
    if(_sc_init_ok == 1)
    {
        bufsize curalloc, totfree, maxfree; long nget, nrel;
        bstats(&curalloc, &totfree, &maxfree, &nget, &nrel);
        scd_logout("sc_malloc(size=%d, tag=0x%04X) = 0x%08X; bstats(curalloc=%d, totfree=%d, maxfree=%d, nget=%d, nrel=%d)", size, tag, (unsigned long)ret, curalloc, totfree, maxfree, nget, nrel);
    }
    return ret;
}
void scm_free(void *buffer)
{
    brel(buffer);
    if(_sc_init_ok == 1)
    {
        bufsize curalloc, totfree, maxfree; long nget, nrel;
        bstats(&curalloc, &totfree, &maxfree, &nget, &nrel);
        scd_logout("sc_mfree(0x%08X); bstats(curalloc=%d, totfree=%d, maxfree=%d, nget=%d, nrel=%d)", (unsigned long)buffer, curalloc, totfree, maxfree, nget, nrel);
    }
}













/**
 * legacy/extended comms engine states definition.
**/
/* Base states definition. */
#define SC_STAT_SYNC      10
#define SC_STAT_RCVPKT    11
/* Comms legacy [l] and/or extended [x] functions states.                */
#define SC_READ_R9        20 /* [l ]Write R9 to PC.                      */
#define SC_RDWR_PARAMS    21 /* [lx]Address, Length, (and load flag).    */
#define SC_READ_DATA      22 /* [lx]Init crcs before reading data.       */
#define SC_READ_DATA_LOOP 23 /* [lx]Read Data and crc to PC.             */
#define SC_WRIT_DATA      24 /* [lx]Write Data from PC.                  */
#define SC_READ_END       25 /* [lX]Send OK.                             */

/* Extended Comms only functions states */
#define SC_RSCODE_START   30 /* [ X]Send RS code.                        */
#define SC_RSCODE_CRC     31 /* [ X]Send RS's previous CRC16.            */
#define SC_RSCODE_END     32 /* [ X]Send CRC16 after sending RS code.    */


#define SC_CRC_SEND       33 /* [ X]Send CRC16.                          */

#define SC_COMP_RET_SEND  40 /* [ X]Send (de)compression return value.   */
#define SC_COMP_ADR_SEND  41 /* [ X]Send (de)compression buffer address. */
#define SC_BUFF_ADR_SEND  42 /* [ X]Send temporary exec buffer address.  */




/**
 * Compression buffer size definition.
 *
 * Note : maximum buffer size for compression is 64K - SC_COMP_HEADER_MAXSIZE.
 * (There is few needs to compress huge data on Saturn, so that this limitation.)
**/
#define SC_BUFF_SIZE 9000





/**
 * Global data.
**/
typedef struct _sc_internal_t
{
    /* Polling loops count in sc_init. */
    unsigned long itcnt;

    /* Wait loop after processing data. */
    unsigned long waitcount;
    unsigned char io_order;

    /* Link/protocol related settings (saved in backup memory). */
    sc_bup_data_t prot;
    /* Current protocol's ID we are processing. */
    unsigned char prot_id;
    /* ARP's IO registers: flag, R, W */
    unsigned long address[3];

    /* The state we are now (idle, send, etc). */
    unsigned short state;
    /* 0: receiving data, 1: sending data. */
    unsigned short st_read;
    /* Number of unsigned chars read/written in the current action. */
    unsigned long st_len;

    /* Number of times data were received last time. */
    unsigned long flagcntp;

    /* Previous input/output values. */
    unsigned short in_bup, out_bup;


    /* Temp buffer for receiving packet. */
    unsigned char pkt_expected_len;
    unsigned char pkt_buffer[256];
    unsigned char command;

    /* Used to compute crcs. */
    unsigned char  crc8;
    unsigned short crc16;

    /* Remember the first written byte. */
    unsigned char first_write_byte;


    /* Base address, length. */
    unsigned char* base_address;
    unsigned long  base_length;

    /* Current start address and length, computed from base address and length. */
    unsigned char* rw_address;
    unsigned long  rw_length;
    unsigned char wr_load_flag;


    /* Buffer used to compute RS code. */
    unsigned char rs[SC_RSLEN];
    unsigned short crc16_back; /* Re-sent with RS code. */

    /* (De)Compression return value. */
    unsigned short comp_return_value;


    /* Buffer used when compressing/extracting data (don't use buffer as-is !). */
    unsigned char buffer[SC_BUFF_SIZE];
    /* Used to share `buffer' into 2 buffers. Use this instead of directly using `buffer'. */
    unsigned char* buff_adr[2];
    unsigned short buff_len[2];

    /* Pointer used when sending data to execute. */
    unsigned char* exec_buffer;


    /* Used for SatCom debugging (internal use only !) */
#ifndef SC_RELEASE
    Fct_scl_print   dbg_prnt;
    Fct_scl_console dbg_cons;
#endif // !SC_RELEASE
} sc_internal_t;



/**
 * Global data read/write.
**/
sc_internal_t sc;


/**
 * SatCom internal debug stuff.
**/
void scl_set_callbacks(Fct_scl_print prnt, Fct_scl_console cons)
{
#ifndef SC_RELEASE
    sc.dbg_prnt = prnt;
    sc.dbg_cons = cons;
#endif // !SC_RELEASE
}


/**
 * Global data for debugger.
**/
#include "debugger/scd_circbuffer.inc.c"
scd_data_t scd;




/* Get/set protocol. */
char* _protocols_name[SC_PROTOCOLS_COUNT] = {"None", "Action Replay Legacy", "Action Replay eXtended", "Serial connector"};
char* sc_get_protname(unsigned char p)
{
    if(p < SC_PROTOCOLS_COUNT)
    {
        return _protocols_name[p];
    }
    return _protocols_name[0]; /*NONE*/
}



/*--- Copy/execute stub. ---*/
/* Start address, maximum length and parameters address */
#include "copystub/copystub.h"
/* The stub code itself */
#include "copystub/copystub_inc.c"


/* Parameters: tmp_address(4), length(4), exec_address(4) */
void sc_execute(unsigned long tmp_address, unsigned long length, unsigned long exec_address)
{
    if(COPYSTUB_BIN_SIZ > COPYSTUB_LENGTH) return;

    /* Set magic numbers to zero so that associated functions are disabled. */
    sc_global->scd_magic  = 0; /* Debugger.       */
    sc_global->fdl_magic1 = 0; /* Fast Data Link. */
    sc_global->fdl_magic2 = 0; /* Fast Data Link. */


    if(tmp_address == exec_address)
    {
        /* Directly call (without copying) the program.
         * (code scrap from antime's satclient.c, http://www.iki.fi/~antime/)
         */
        int (*pFun)(void);
        pFun = (int(*)(void))exec_address;
        (*pFun)();
    }
    else
    {
        /* Copy stub to memory */
        unsigned char* stub = (unsigned char*)COPYSTUB_START_ADRESS;
        memcpy((void*)stub, (void*)copystub_bin_dat, COPYSTUB_BIN_SIZ);

        /* Copy parameters to memory. */
        sc_global->cs_src_stt = tmp_address;
        sc_global->cs_lenght  = length;
        sc_global->cs_dst_stt = exec_address;

        /* Call the copy stub program (-> quit SatCom library).
         * (code scrap from antime's satclient.c, http://www.iki.fi/~antime/)
         */
        int (*pFun)(void);
        pFun = (int(*)(void))stub;
        (*pFun)();
    }
}



/* ------------------------- */
/* Wait for VBlank function. */
void sc_wait_vblank(void)
{
    while(((*(volatile unsigned short *)0x25F80004) & 8) == 8);
    while(((*(volatile unsigned short *)0x25F80004) & 8) == 0);
}



/* ------------------------------------------------ */
/* Action replay Flash memory read/write functions. */

void sc_flash_command(unsigned short command)
{
    volatile unsigned short* const ADDR_55 = (unsigned short*)0x2200AAAA; /* The flash sees this as 0x5555 */
    volatile unsigned short* const ADDR_AA = (unsigned short*)0x22005554; /* The flash sees this as 0xAAAA */

    //sc_flash_unlock();
    *ADDR_55 = 0xAAAA;
    *ADDR_AA = 0x5555;

    *ADDR_55 = command;
}


#define ID_ENTRY 0x9090
#define ID_EXIT 0xF0F0
unsigned short sc_flash_getid(unsigned char vendorid)
{
    volatile unsigned short* const ADDR_VENDOR = (unsigned short*)0x22000000;
    volatile unsigned short* const ADDR_DEVICE = (unsigned short*)0x22000002; /* The flash sees this as 0x0001 */

    unsigned short ret;
    sc_flash_command(ID_ENTRY);
    sc_wait_vblank(); sc_wait_vblank();
    sc_wait_vblank(); sc_wait_vblank();
    sc_wait_vblank(); /* Bodge to wait for flash to complete the command */
    if(vendorid)
    {
        ret = *ADDR_VENDOR;
    }
    else
    {
        ret = *ADDR_DEVICE;
    }
    sc_flash_command(ID_EXIT);
    sc_wait_vblank(); sc_wait_vblank(); sc_wait_vblank();
    sc_wait_vblank(); sc_wait_vblank(); sc_wait_vblank();
    return ret;
}


/* Erase flash when source == NULL */
#define PAGEWRITE 0xA0A0
void sc_flash_write(volatile unsigned short* source, volatile unsigned short* dest)
{
    int i;
    //volatile unsigned short* now = 0;
    sc_flash_command(PAGEWRITE);

    for(i = 0; i < 128; i++)
    {
        if(source != NULL)
        {
            *dest = *source;
            source++;
        }
        else
        {
            *dest = 0xFFFF;
        }
        dest++;
    }

    /* Wait for the write to finish... */
    sc_wait_vblank(); sc_wait_vblank(); sc_wait_vblank();
    sc_wait_vblank(); sc_wait_vblank(); sc_wait_vblank();
}





/* ------------------------------------------------------------ */
/* SBL timer library is used in order to measure time interval. */
#include "SBL/INCLUDE/SEGA_TIM.H"

void sc_tmr_start(void)
{
    TIM_FRT_INIT(8); // 8, 32 or 128
    TIM_FRT_SET_16(0);
}

unsigned short sc_tmr_lap(void)
{
    return (TIM_FRT_GET_16());
}
unsigned short sc_tmr_lap_usec(void)
{
    Float32 f = TIM_FRT_CNT_TO_MCR(TIM_FRT_GET_16());
    return ((unsigned short)(f));
}


unsigned long sc_tmr_tick2usec(unsigned long tick)
{
    Float32 f = TIM_FRT_CNT_TO_MCR(tick);
    return ((unsigned long)(f));
}

void sc_tick_sleep(unsigned short tick)
{
    sc_tmr_start();
    while(sc_tmr_lap() < tick);
}
void sc_usleep(unsigned long usec)
{
    /* Convert delay to tick value. */
    unsigned long delay_tick = 0;
    if(usec < 50*1000)
    { /* Compute tick value at good precision for delay lower than 50 msec. */
        delay_tick = (usec*60000) / (sc_tmr_tick2usec(60000));
    }
    else
    { /* Poor precision, but no overflow (up to 42 seconds) for higher values. */
        delay_tick = (usec*100) / (sc_tmr_tick2usec(100));
    }

    /* Sleep at most 60000 ticks. */
    unsigned long i;
    for(i=0; i<delay_tick; i+=60000)
    {
        unsigned long s = ((i+60000) < delay_tick ? 60000 : delay_tick - i);
        sc_tick_sleep(s);
    }
}




/* --------------------------------------------------------------- */
/* Backup memory is used in order to load/save the protocol value. */
#include "SBL/INCLUDE/SEGA_BUP.H"

/* Adapted from RB's SaveGame manager.
 * Note: why allocating memory from an arbitrary address ????
 *       Use "BackUpLibrary" global variable instead.
 */
//#define 	BUP_START_ADDR	0x6070000
unsigned long	BackUpLibrary[4096];

unsigned long	BackUpRamWork[2048];

BupConfig	CnTb[3];
BupDir		DirTb[50];
BupDate		DateTb;
BupStat		StTb;
unsigned long		device;
unsigned long		clock;
void sc_bup_init(void)
{
    BUP_Init((volatile unsigned long *)/*BUP_START_ADDR*/BackUpLibrary, BackUpRamWork, CnTb);
}

long sc_bup_read(char* name, void* data)
{
    /* Filename's format is 11 ASCII characters+null terminating character. */
    unsigned char name12[12];
    /* Fill with spaces -> Copy at most 11 chars -> Set null character to file name. */
    memset((void*)name12, ' ', 11);
    memcpy((void*)name12, name  , strnlen(name, 11)/*11+null*/);
    name12[11] = '\0';

    /* In the case reading failed, nothing is set in data. */
    return BUP_Read(0/*Build-in memory*/, name12, (unsigned char *)data);
}
long sc_bup_write(void* data, unsigned long datalen, char* name, char* comment)
{
    BupDir bupDir;

    /* Reset backup header. */
    memset(&bupDir, 0, sizeof(BupDir));
    /* Fill backup filename+comment with spaces. */
    memset(&bupDir, ' ', 12+11);
    /* Set backup filename, and its null terminating character. */
    memcpy(bupDir.filename, name  , strnlen(name, 11)/*11+null*/);
    bupDir.filename[11] = '\0';
    /* Set backup comment, and its null terminating character. */
    memcpy(bupDir.comment , comment, strnlen(comment, 10)/*10+null*/);
    bupDir.comment[10] = '\0';
    /* Set backup misc. settings. */
    bupDir.language = BUP_ENGLISH;
    bupDir.datasize = datalen;
    bupDir.date = 0; // 1996/3/26 0:00

    /* Write backup data. */
    return BUP_Write(0/*Build-in memory*/, &bupDir, (unsigned char *)data, OFF/*overwrite OK*/);
}



void _sc_file_init(void); /* File system init function (sc_init's internal use). */
void srand (unsigned int seed);
void sc_init(void)
{
    /* Init backup stuff. */
    sc_bup_init();


    /* Init RS engine. */
    rs_start();

    /* Init debugger engine. */
    scd_cbinit(&scd);
    scd_logout(" --- ~ ~ ~ ~ --- ", 0);
    /* Set debugger absolute data. */
    sc_global->scd_magic = SCD_ABS_MAGIC;
    sc_global->scd_ptr   = (unsigned long)(&scd);


    /* Init SatCom global data. */
    memset((void*)(&sc), 0, sizeof(sc_internal_t));
    sc.in_bup  = 'V';
    sc.out_bup = SC_VERSION;
    /* Get protocol from backup memory to SatCom internal buffer. */
    sc_getprot(NULL);

    /* Set polling loop count to a reasonable value */
    sc.itcnt = 1 << 4;
    /* Wait loop after processing data.
     * (Without waiting, transmission errors happen ?!)
     */
    sc.waitcount = 6;


    /* Read/Process order in the polling function.
     * 0   -> process previous read data, then read.
     * else-> read, then process read data.
     */
    sc.io_order = 0;


    /* Set buffer where to upload saturn executables. */
#ifdef SC_EXEC_IN_LRAM
    /* Execute satcom in LRAM -> use HRAM as a temporary buffer. */
    sc.exec_buffer = (unsigned char*)0x06004000;
#else
    /* Execute satcom in HRAM -> use LRAM as a temporary buffer. */
    sc.exec_buffer = (unsigned char*)0x00200000;
#endif


    /* Init state to sync. */
    sc.state = SC_STAT_SYNC;
    sc.st_len = 0;



    /* Set SatCom init flag to true */
    _sc_init_ok = 1;


    /* Init file IO (PC, CD-ROM) system. */
    _sc_file_init();


    /* Init rand seed. (in a Q&D way, but better than nothing) */
    unsigned short rand_seed = sc_tmr_lap();
    scd_logout("*** Seed: 0x%04X", rand_seed);
    srand(rand_seed);


    /* Pool memory from your ramcart (if exists). */
    _sc_pool_size    = 0;
    _sc_mpool_state  = 0;
    _sc_ramcart_size = 0;
    scm_alloc_init(SCM_RAMCART);
    scd_logout("*** RAMCART: %d MB, BGET: %d bytes (%d KB)", _sc_ramcart_size, _sc_pool_size, _sc_pool_size>>10);

    /* END: log some debug infos. */
#ifdef SC_EXEC_IN_LRAM
    scd_logout("*** Welcome to SatCom debugger (exec in LRAM)", 0);
#else
    scd_logout("*** Welcome to SatCom debugger", 0);
#endif
    scd_logout("*** Build date: " __DATE__ ", time: " __TIME__, 0);
//    scd_logout("*** TRANS BUFFER #1(P%d) = 0x%08X (%d), #2(P%d)=0x%08X (%d)", 
//                sc.prot.type[0], sc.buff_adr[0], sc.buff_len[0], sc.prot.type[1], sc.buff_adr[1], sc.buff_len[1]);
//    scd_logout("*** FAST USBDL 0x%08X (%d)", sc_global->fdl_int_buff, sc_global->fdl_int_leng);
}






/**
 * PAR Registers addresses definition.
**/
// http://segaxtreme.net/community/topic/14669-usb-downloading-device-for-sega-saturn/page__st__40
//The flash ID has some tag of correlation to cart type (Datel usually uses SST chips, EMS usually
// uses Atmel chips; I have no idea about other vendors such as Xinga), but I don't think it's a guarantee.
//my EMS cart got chips of vendorid 0xBFBF.
//
// Comms status flag (read-only)
// ===
// Datel: 0x22500001
// EMS: 0x22100001
#define PAR_DATEL_FLAG         0x22500001
#define PAR_EMS_FLAG           0x22100001

// Comms byte read (read-only)
// ===
// Datel: 0x22600001
// EMS: 0x22180001
#define PAR_DATEL_READ         0x22600001
#define PAR_EMS_READ           0x22180001

// Comms byte write (write-only)
// ===
// Datel: 0x22400001
// EMS: 0x22080001
#define PAR_DATEL_WRITE        0x22400001
#define PAR_EMS_WRITE          0x22080001


/* Internal function that sets the Read/Write registers addresses. */
void sc_initprot(void)
{
    unsigned char p0  = sc.prot.type[0];
    unsigned char p1  = sc.prot.type[1];
    unsigned char adr = sc.prot.arp_address;

    /*** Assign ARP address pattern. ***/
    if((p0 == SC_PROT_LEGACY) || (p0 == SC_PROT_EXTENDED)
    || (p1 == SC_PROT_LEGACY) || (p1 == SC_PROT_EXTENDED))
    {
        if((adr != 1) && (adr != 2))
        { /* Auto address setting. */
            /* The following is quite broken, as it has no support whatsoever
            for correlating vendor/device IDs. */
            unsigned short vendorid = sc_flash_getid(1/*vendorid*/);
            sc_wait_vblank();
            switch(vendorid)
            {
            case 0xBFBF: /* Silicon Storage Technology (SST) vendor => Datel */
                adr = 2;
                break;

            case 0x1F1F: /* Atmel vendor => EMS */
            default:
                adr = 1;
                break;
            }

            //unsigned short devid = sc_flash_getid(0/*vendorid*/));
            //sc_wait_vblank();
            //switch(devid)
            //{
            //case 0x0707: /* SST29EE010 device. */
            //    break;
            //case 0xD5D5: /* AT29C010 device */
            //default:
            //    break;
            //}
        }

        if(adr == 1)
        { /* Force address to EMS */
            sc.address[0] = PAR_EMS_FLAG;
            sc.address[1] = PAR_EMS_READ;
            sc.address[2] = PAR_EMS_WRITE;
        }
        else// if(adr == 2)
        { /* Force address to Datel */
            sc.address[0] = PAR_DATEL_FLAG;
            sc.address[1] = PAR_DATEL_READ;
            sc.address[2] = PAR_DATEL_WRITE;
        }
    }

    /*** Share transmission buffer. ***/
    if((p0 != SC_PROT_NONE) && (p1 == SC_PROT_NONE))
    { /* Only main protocol is used. */
        sc.buff_adr[0] = sc.buffer; sc.buff_len[0] = SC_BUFF_SIZE;
        sc.buff_adr[1] = NULL;      sc.buff_len[1] = 0;
    }
    else if((p0 == SC_PROT_NONE) && (p1 != SC_PROT_NONE))
    { /* Only main protocol is used. */
        sc.buff_adr[0] = NULL;      sc.buff_len[0] = 0;
        sc.buff_adr[1] = sc.buffer; sc.buff_len[1] = SC_BUFF_SIZE;
    }
    else if((p0 == SC_PROT_NONE) && (p1 == SC_PROT_NONE))
    { /* No protocol used. */
        sc.buff_adr[0] = NULL; sc.buff_len[0] = 0;
        sc.buff_adr[1] = NULL; sc.buff_len[1] = 0;
    }
    else
    { /* Both main and sub protocols are used. */
        unsigned long n = SC_BUFF_SIZE/2;
        sc.buff_adr[0] = sc.buffer  ; sc.buff_len[0] = n;
        sc.buff_adr[1] = sc.buffer+n; sc.buff_len[1] = n;
    }

    /*** Init FDL (Fast Data Link) stuff. ***/
    /* Look for the protocol ID that may use DataLink. */
    int datalink_prot_id = (p1 == SC_PROT_LEGACY ? 1 : 0);
    sc_global->fdl_int_buff = (unsigned long)(sc.buff_adr[datalink_prot_id]);
    sc_global->fdl_int_leng = sc.buff_len[datalink_prot_id];
    sc_global->fdl_magic1   = FDL_MAGIC1;
    sc_global->fdl_magic2   = FDL_MAGIC2;
    sc_global->fdl_req      = 0;
    sc_global->fdl_usr_buff = 0;
    sc_global->fdl_usr_leng = 0;

}
/**
 * Get/set link/protocol data from/to backup memory.
**/
void sc_getprot(sc_bup_data_t* dat_out)
{
    /* Set parameters default values. */
    memset((void*)(&(sc.prot)), 0, sizeof(sc_bup_data_t));
    sc.prot.struct_size    = sizeof(sc_bup_data_t);
    sc.prot.struct_version = SC_BUP_VER1;
    sc.prot.type[0] = SC_PROT_LEGACY; /* Protocol default value. */
    sc.prot.type[1] = SC_PROT_NONE;   /* Protocol default value. */
    sc.prot.arp_address = 0; /* ARP auto detect. */
    sc.prot.debug       = 0; /* Non debug mode. */

    /* Load protocol value from backup memory. */
    sc_bup_read(PROT_BUPFNAME, (void*)(&(sc.prot)));

    /* As the protocol may have changed, we need to init it. */
    sc_initprot();

    /* Copy the resulting data to the output buffer */
    if(dat_out != NULL)
    {
        memcpy((void*)dat_out, (void*)(&(sc.prot)), sizeof(sc_bup_data_t));
    }
}
void sc_setprot(sc_bup_data_t* dat_in)
{
    if(dat_in == NULL) return;

    /* Simple version checking. */
    if( (dat_in->struct_size    == sizeof(sc_bup_data_t))
    &&  (dat_in->struct_version == SC_BUP_VER1 ))
    {
        /* Set protocol values. */
        memcpy((void*)(&(sc.prot)), (void*)dat_in, sizeof(sc_bup_data_t));
    }
    else
    {
        /* Init protocol values. */
        memset((void*)(&(sc.prot)), 0, sizeof(sc_bup_data_t));
        sc.prot.struct_size    = sizeof(sc_bup_data_t);
        sc.prot.struct_version = SC_BUP_VER1;
        sc.prot.type[0] = SC_PROT_NONE; /* Protocol default value. */
        sc.prot.type[1] = SC_PROT_NONE; /* Protocol default value. */
    }

    /* Init protocol internal values. */
    sc_initprot();

    /* Save protocol value to backup memory. */
    sc_bup_write((void*)(&(sc.prot)), sizeof(sc_bup_data_t), PROT_BUPFNAME, PROT_BUPCOMMENT);    
}



#ifndef SC_RELEASE


void sc_check_legacy(void);
void sc_check_extended(void);

void _sc_check_internal(void)
{
    for(sc.prot_id=0; sc.prot_id<2; sc.prot_id++)
    {
        unsigned char p = sc.prot.type[sc.prot_id];
        if(p == SC_PROT_EXTENDED)
        {
            DBG_PRINT(0/*col*/, 0/*row*/, "Check extended", 0);
            sc_check_extended();
        }
        else if(p == SC_PROT_LEGACY)
        {
            DBG_PRINT(0/*col*/, 0/*row*/, "Check legacy  ", 0);
            sc_check_legacy();
        }
        else if(p == SC_PROT_SERIAL)
        {
            //TODO!
        }
    }
}





/* Uses code from cgfm2 stuff. */
int _use_poll = 0;
unsigned char comms_exc_byte(unsigned char value)
{
    unsigned char temp;
    volatile unsigned char *flag = (unsigned char *)sc.address[0];

    /* Wait for comms exchange flag. */
    if(_use_poll == 1)
    {
        while(((*flag) & 1) == 0);
    }
    _use_poll = 1;

    /* Receive data. */
    temp = *((unsigned char *)sc.address[1]);

    /* Send data. */
    *((unsigned char *)sc.address[2]) = value;

    /* Log exchanged bytes */
    DBG_CONSOLE("R=%02X(%c), S=%02X(%c)", temp , temp, value, value);

    return temp;
}
unsigned short comms_exc_short(unsigned short data)
{
    unsigned short rdata;
    rdata  = comms_exc_byte((data >>  8) & 0xFF) <<  8;
    rdata |= comms_exc_byte((data >>  0) & 0xFF);
    return rdata;
}
unsigned long comms_exc_long(unsigned long data)
{
    unsigned long rdata;
    rdata  = comms_exc_byte((data >> 24) & 0xFF) << 24;
    rdata |= comms_exc_byte((data >> 16) & 0xFF) << 16;
    rdata |= comms_exc_byte((data >>  8) & 0xFF) <<  8;
    rdata |= comms_exc_byte((data >>  0) & 0xFF);
    return rdata;
}


unsigned long _command_id = 0;
void sc_check_legacy(void)
{
    /* ARP Communication data. */
    volatile unsigned char *flag = (unsigned char *)sc.address[0];
    unsigned long tmp;
    unsigned char command_byte;
    unsigned long address;
    unsigned long length;
    unsigned char exec_flag;
    unsigned char checksum;

    unsigned char* ptr;
    unsigned long i;

    /* Poll comms exchange flag. */
    unsigned long loop;
    int flag_on = 0;
    for(loop = 0; loop < sc.itcnt; loop++)
    {
        /*
         * Update state and exchange data when state becomes 1
         */
        if(*flag & 1)
        {
            flag_on = 1;
            break;
        }
    }
    DBG_PRINT(0/*col*/, 1/*row*/, "flag_on=%d", flag_on);

    /* No data received -> exit function. */
    if(flag_on != 1) return;
    
    /* Dirty hack: don't check flag for the first
     * byte, because we polled it just before.
     */
    _use_poll = 1;


    /* Synchronize with PC. */
    tmp = comms_exc_byte('D');
    if (tmp != 'I')
    {
        return;
    }
    tmp = comms_exc_byte('O');
    if (tmp != 'N')
    {
        return;
    }


    /* Read the command byte from the PC. */
    command_byte = comms_exc_byte(0);
    _command_id++;
    DBG_PRINT(0/*col*/, 2/*row*/, "CMD = 0x%02X, ID=0x%08X", command_byte, _command_id);

    switch(command_byte)
    {

    /** From http://cgfm2.emuviews.com/sat/satar.txt :
     * Function $01: Download memory
     * 
     * It does the following:
     * 
     * 1. Write longword in SH-2 register R9 to the PC.
     * 2. Read two longwords, address and length, from the PC.
     * 3. If length is zero, exchange bytes 'O' and 'K', then return.
     *    It does not care what the PC sends back.
     * 4. Exchange data bytes from specified memory addresses until all memory
     *    has been read. The SH-2 can hang if it reads invalid address.
     * 5. Exchange a single byte, which is a checksum.
     * 6. Go to step 2 and repeat.
     * 
     * For a single download, you'd send a length of zero and any address the
     * second time around. The checksum is generated by adding each byte to
     * an 8-bit accumulator. (so it wraps from $FF to $00)
    **/
    case(0x01):
        DBG_PRINT(0/*col*/, 3/*row*/, "%x>Download", _command_id&0xF);

        comms_exc_long(0);

        address = comms_exc_long(0);
        DBG_PRINT(0/*col*/, 4/*row*/, "%x>ADR = 0x%04X", _command_id&0xF, address);

        length  = comms_exc_long(0);
        DBG_PRINT(0/*col*/, 5/*row*/, "%x>LEN = 0x%04X", _command_id&0xF, length);

        while(length != 0)
        {
            ptr = (unsigned char*)address;
            checksum = 0;
            for(i=0; i<length; i++)
            {
                comms_exc_byte(ptr[i]);
                checksum += ptr[i];
            }
            DBG_PRINT(0/*col*/, 6/*row*/, "%x>CSUM = 0x%02X", _command_id&0xF, checksum);
            comms_exc_byte(checksum);

            address = comms_exc_long(0);
            length  = comms_exc_long(0);
        }
        /* Finished download. */
        comms_exc_byte('O');
        comms_exc_byte('K');
        break;


    /** From http://cgfm2.emuviews.com/sat/satar.txt :
     * Function $08: Write byte to memory address
     * 
     * Reads a longword from the PC (address to write to) and exchanges zero,
     * using the value it receives to write as a byte value to the specified
     * address.
    **/
    case(0x08):
        DBG_PRINT(0/*col*/, 3/*row*/, "%x>Wrt Byte", _command_id&0xF);

        address = comms_exc_long(0);
        DBG_PRINT(0/*col*/, 4/*row*/, "%x>ADR = 0x%04X", _command_id&0xF, address);

        tmp     = comms_exc_byte(0);
        DBG_PRINT(0/*col*/, 5/*row*/, "%x>VAL = 0x%02X  ", _command_id&0xF, tmp);

        ((unsigned char*)address)[0] = tmp;
        break;


    /** From http://cgfm2.emuviews.com/sat/satar.txt :
     * Function $09: Upload (and execute) data
     * 
     * 1. Read longword from the PC (address to load data)
     * 2. Read longword from the PC(data length)
     * 3. Exchange byte (if $01, JSR to load address after loading, else do nothing)
     * 4. Read data bytes from PC until all data was transferred
     * 
     * When transferring data, the first byte exchanged is the value of SH-2
     * register R9, and all subsequent bytes are the previous byte sent to the PC.
     * 
     * Assuming the stack isn't modified, the program called by this function
     * could end with an RTS and return to the PAR software.
    **/
    case(0x09):
        DBG_PRINT(0/*col*/, 3/*row*/, "%x>Upload  ", _command_id&0xF);

        address   = comms_exc_long(0);
        DBG_PRINT(0/*col*/, 4/*row*/, "%x>ADR = 0x%04X", _command_id&0xF, address);

        length    = comms_exc_long(0);
        DBG_PRINT(0/*col*/, 5/*row*/, "%x>LEN = 0x%04X", _command_id&0xF, length);

        exec_flag = comms_exc_byte(0);
        DBG_PRINT(0/*col*/, 6/*row*/, "%x>EXEC = 0x%02X", _command_id&0xF, exec_flag);

        /* Receive data to write. */
        ptr = (unsigned char*)address;
        for(i=0; i<length; i++)
        {
            ptr[i] = comms_exc_byte((i==0 ? 0 : ptr[i-1]));
        }

        /* Extra: Fast Data Link (FDL) compression/decompression stuff. */
        if(sc_global->fdl_req == 'C')
        { /* 'C': Compress data from user area to internal area. */
            /* unsigned long comp_exec(unsigned char* ind, unsigned long inl, unsigned char* outd); */
            sc_global->fdl_return = comp_exec((unsigned char*)sc_global->fdl_usr_buff, sc_global->fdl_usr_leng, (unsigned char*)sc_global->fdl_int_buff);

            /* Reset FDL stuff + small tweak for execution. */
            sc_global->fdl_req = 0;
            address = sc_global->fdl_int_buff;
        }
        else if(sc_global->fdl_req == 'D')
        { /* 'D': Decompress data from internal area to user area. */
            /* unsigned long decomp_exec(unsigned char* ind, unsigned long inl, unsigned char* outd); */
            sc_global->fdl_return = decomp_exec((unsigned char*)sc_global->fdl_int_buff, sc_global->fdl_usr_leng, (unsigned char*)sc_global->fdl_usr_buff);

            /* Reset FDL stuff + small tweak for execution. */
            sc_global->fdl_req = 0;
            address = sc_global->fdl_usr_buff;
        }

        /* Execute ? */
        if(exec_flag == 1)
        {
            sc_execute(address/*tmp_address*/, length/*length*/, address/*exec_address*/);
        }
        break;


    /* Unknown/unimplemented function. */
    default:
        DBG_PRINT(19/*col*/, 2/*row*/, "[%x>UNK = 0x%02X]", _command_id&0xF, command_byte);
        break;
    }


} /* sc_check_legacy function. */





/* dummy sleep function. */
int dummywait(int count)
{
    int i, j = 0, k = 0;
    for(i=0; i<count; i++)
    {
        j += (  j+i*53)%4;
        k += (k+j+i*53)%1000;
        switch(j)
        {
        case(0):
            j += k*k - 2*k;
            break;
        case(1):
            j += k*k + 2*k;
            break;
        case(2):
            j += k*k - k;
            break;
        case(3):
            j += k*k + k;
            break;
        }
    }
    return j;
}



void sc_check_extended(void)
{
    /* ARP Communication data. */
    volatile unsigned char *flag = (unsigned char *)sc.address[0];
    unsigned char in = 0x00, out = 0x00;

    unsigned long loop;
    unsigned long flagcnt=0;

    /* Set to 1 when a return is requested from the PC. */
    unsigned short need_return  = 0;
    /* Set to 1 when a program execution is requested from the PC. */
    unsigned short need_execute = 0;

    unsigned short subloop=0;
    for(loop = 0; (sc.itcnt==(1 << 0x1F)) || (loop <= sc.itcnt); subloop++)
    {
        if(subloop >= 7)
        {
            subloop = 0;
            loop++;
        }
        /*
         * Update state and exchange data when state becomes 1
         */
        if(*flag & 1)
        { flagcnt++;
            /* As data is received, reset timeout loop. */
            loop = 0; subloop = 0;

            if(sc.io_order != 0)
            {
                /* Read data from PC. */
                in = *((volatile unsigned char *)sc.address[1]);
                /* Write previous state's answer. */
                *((volatile unsigned char *)sc.address[2]) = out;
            }
            else
            {
                in  = sc.in_bup;
                out = sc.out_bup;
            }

            switch(sc.state)
            {

            /* Read data. */
            case(SC_READ_DATA):
                /* Init crcs. */
                sc.crc16 = crc16_start(); sc.crc16_back = sc.crc16;
                sc.state = SC_READ_DATA_LOOP;

            case(SC_READ_DATA_LOOP):
                if(sc.st_len < (sc.rw_length))
                { /* Read data and send to the PC. */
                    out = sc.rw_address[sc.st_len];
                    sc.crc16 = crc16_update(sc.crc16, out); sc.crc16_back = sc.crc16;

                    sc.st_len++;
                }
                else if(sc.st_len == (sc.rw_length))
                { /* Send crc high byte. */
                    out = (sc.crc16) >> 8;
                    sc.st_len++;
                }
                else if(sc.st_len == (sc.rw_length+1))
                { /* Send crc low byte. */
                    out = (sc.crc16) & 0x00FF;
                    sc.st_len++;
                }
                else
                { /* Send synchronization dummy byte. */
                    out = '<';
                    sc.state = SC_STAT_SYNC;
                    sc.st_len = 0;
                }
                break;

            /* Compute, then send RS code to the PC. */
            case(SC_RSCODE_START):
                if(sc.st_len == 0)
                {
                    sc.crc16 = crc16_start();

                    /* Compute RS code. */
                    unsigned short pp;
                    rse_init(sc.rs);
                    for(pp=0; pp<sc.rw_length; pp++)
                    {
                        rse_update(sc.rs, pp, sc.rw_address[pp]);
                    }

                }

                out = sc.rs[sc.st_len];
                sc.crc16 = crc16_update(sc.crc16, out);

                sc.st_len++;
                if(sc.st_len >= SC_RSLEN)
                {
                    sc.state = SC_RSCODE_CRC;
                    sc.st_len = 0;
                }
                break;
            /* Send previous CRC to the PC. */
            case(SC_RSCODE_CRC):
                if(sc.st_len == 0)
                {
                    out = sc.crc16_back >> 8;
                    sc.st_len++;
                }
                else
                {
                    out = sc.crc16_back & 0x00FF;
                    sc.state = SC_RSCODE_END;
                    sc.st_len = 0;
                }
                sc.crc16 = crc16_update(sc.crc16, out);
                break;
            /* Send packet CRC to the PC. */
            case(SC_RSCODE_END):
                if(sc.st_len == 0)
                {
                    out = sc.crc16 >> 8;
                    sc.st_len++;
                }
                else if(sc.st_len == 1)
                {
                    out = sc.crc16 & 0x00FF;
                    sc.st_len++;
                }
                else
                { /* Send synchronization dummy byte. */
                    out = '<';
                    sc.state = SC_STAT_SYNC;
                    sc.st_len = 0;
                }
                break;


            /* Write data. */
            case(SC_WRIT_DATA):
                if(sc.st_len < (sc.rw_length))
                { /* Write received data, and send it back to the PC. */
                    if(sc.st_len == 1)
                    { /* First byte -> init CRC. */
                        sc.crc16 = crc16_start();
                        sc.crc16 = crc16_update(sc.crc16, sc.first_write_byte);
                    }
                    sc.rw_address[sc.st_len] = in;
                    sc.crc16 = crc16_update(sc.crc16, in);
                    /* Send the number of bytes written so far.
                     * The reason is that is makes possible to detect an
                     * "erasure" error even in the case of always writing
                     * the same value.
                     */
                    out = sc.st_len;
                    sc.st_len++;
                }
                else if(sc.st_len == (sc.rw_length))
                { /* Send crc high byte. */
                    out = (sc.crc16) >> 8;
                    sc.st_len++;
                }
                else if(sc.st_len == (sc.rw_length+1))
                { /* Send crc low byte. */
                    out = (sc.crc16) & 0x00FF;
                    sc.st_len++;
                }
                else
                { /* Send synchronization dummy byte. */
                    out = '<';
                    sc.state = SC_STAT_SYNC;
                    sc.st_len = 0;
                }
                break;



            /* Send (de)compression return value. */
            case(SC_COMP_RET_SEND):

                if(sc.st_len == 0)
                { /* Send return value's high byte. */
                    sc.crc16 = crc16_start();
                    out = (sc.comp_return_value) >> 8;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 1)
                { /* Send return value's low byte. */
                    out = (sc.comp_return_value) & 0x00FF;
                    sc.crc16 = crc16_update(sc.crc16, out);

                    /* All data sent -> Send CRC. */
                    sc.state = SC_CRC_SEND;
                    sc.st_len = 0;
                }
                break;

            /* Send (de)compression buffer address. */
            case(SC_COMP_ADR_SEND):

                if(sc.st_len == 0)
                { /* Send address high byte. */
                    sc.crc16 = crc16_start();
                    out = (((unsigned long)sc.buffer) >> 24) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 1)
                {
                    out = (((unsigned long)sc.buffer) >> 16) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 2)
                {
                    out = (((unsigned long)sc.buffer) >>  8) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 3)
                { /* Send address low byte. */
                    out = (((unsigned long)sc.buffer) >>  0) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 4)
                { /* Send length high byte. */
                    out = ((sc.buff_len[sc.prot_id]) >>  8) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 5)
                { /* Send address low byte. */
                    out = ((sc.buff_len[sc.prot_id]) >>  0) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);

                    /* All data sent -> Send CRC. */
                    sc.state = SC_CRC_SEND;
                    sc.st_len = 0;
                }
                break;

            /* Send exec temporary address. */
            case(SC_BUFF_ADR_SEND):

                if(sc.st_len == 0)
                { /* Send address high byte. */
                    sc.crc16 = crc16_start();
                    out = (((unsigned long)sc.exec_buffer) >> 24) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 1)
                {
                    out = (((unsigned long)sc.exec_buffer) >> 16) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 2)
                {
                    out = (((unsigned long)sc.exec_buffer) >>  8) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;
                }
                else if(sc.st_len == 3)
                { /* Send address low byte. */
                    out = (((unsigned long)sc.exec_buffer) >>  0) & 0xFF;
                    sc.crc16 = crc16_update(sc.crc16, out);
                    sc.st_len++;

                    /* All data sent -> Send CRC. */
                    sc.state = SC_CRC_SEND;
                    sc.st_len = 0;
                }
                break;


            /* Send checksum. */
            case(SC_CRC_SEND):
                if(sc.st_len == 0)
                { /* Send crc high byte. */
                    out = (sc.crc16) >> 8;
                    sc.st_len++;
                }
                else if(sc.st_len == 1)
                { /* Send crc low byte. */
                    out = (sc.crc16) & 0x00FF;
                    sc.st_len++;
                }
                else
                { /* Send synchronization dummy byte. */
                    out = '<';
                    sc.state = SC_STAT_SYNC;
                    sc.st_len = 0;
                }
                break;




            /* Receive packet, including its crc. */
            case(SC_STAT_RCVPKT):
                out = 'm' + sc.st_len;
                sc.pkt_buffer[sc.st_len] = in;
                sc.st_len++;

                if(sc.st_len == 1)
                {
                    /* Init/update crc. */
                    sc.crc16 = crc16_start();

                    /* Get command byte. */
                    sc.command = in;
                    if(sc.command == 'A')
                    {
                        /* Set base address and length.              */
                        /* command(1), address(4), length(1), CRC(2) */
                        sc.pkt_expected_len = 8;
                    }
                    else if(sc.command == 'R')
                    {
                        /* Read request from PC.                */
                        /* command(1), multiplicator(1), CRC(2) */
                        sc.pkt_expected_len = 4;
                    }
                    else if(sc.command == 'W')
                    {
                        /* Write/execute request from PC.                       */
                        /* command(1), multiplicator(1), first_value(1), CRC(2) */
                        sc.pkt_expected_len = 5;
                    }
                    else if(sc.command == 'S')
                    {
                        /* Compute, then send CRC to PC.        */
                        /* command(1), multiplicator(1), CRC(2) */
                        sc.pkt_expected_len = 4;
                    }
                    else if(sc.command == 'C')
                    {
                        /* Send RS code to PC. */
                        /* command(1), CRC(2)  */
                        sc.pkt_expected_len = 3;
                    }
                    else if(sc.command == 'D')
                    {
                        /* Receive RS code from PC.                                            */
                        /* command(1), prev_CRC(2), recpos(1), rs_data(SC_RSLEN), CRC(2) */
                        sc.pkt_expected_len = 6+SC_RSLEN;
                    }
                    else if(sc.command == 'F')
                    {
                        /* Receive simple recovery data from PC.                    */
                        /* command(1), prev_CRC(2), recpos(1), last_byte(1), CRC(2) */
                        sc.pkt_expected_len = 7;
                    }
                    else if(sc.command == 'Z')
                    {
                        //unsigned long comp_exec(unsigned char* ind, unsigned long inl, unsigned char* outd);
                        /* Compress data.                           */
                        /* Note: Automatically set parameters :     */
                        /*       1. out_buf -> sc.buffer            */
                        /* command(1), in_buf(4), in_len(2), CRC(2) */
                        sc.pkt_expected_len = 9;
                    }
                    else if(sc.command == 'U')
                    {
                        //unsigned long comp_exec(unsigned char* ind, unsigned long inl, unsigned char* outd);
                        /* Decompress data.                          */
                        /* Note: Automatically set parameters :      */
                        /*       1. in_buf -> sc.buffer              */
                        /* command(1), in_len(2), out_buf(4), CRC(2) */
                        sc.pkt_expected_len = 9;
                    }
                    else if(sc.command == 'G')
                    {
                        /* Get (de)compressed size. */
                        /* command(1), CRC(2)       */
                        sc.pkt_expected_len = 3;
                    }
                    else if(sc.command == 'K')
                    {
                        /* Get (de)compression buffer address. */
                        /* command(1), CRC(2)                  */
                        sc.pkt_expected_len = 3;
                    }
                    else if (sc.command == 'Y')
                    {
                        /* Send address to exec data temporary buffer. */
                        /* command(1), CRC(2)                          */
                        sc.pkt_expected_len = 3;
                    }
                    else if (sc.command == 'X')
                    {
                        /* Copy data from tmp_address to exec_address, then execute       */
                        /* the freshly copied code from exec_address.                     */
                        /* command(1), tmp_address(4), length(4), exec_address(4), CRC(2) */
                        sc.pkt_expected_len = 15;
                    }
                    else if(sc.command == 'L')
                    {
                        /* Set transmission polling loop parameters. */
                        /* command(1), parameters(1), CRC(2)         */
                        sc.pkt_expected_len = 4;
                    }
                    else
                    { /* Unknown -> Abort. */
                        out = 'a';
                        sc.state = SC_STAT_SYNC;
                        sc.st_len = 0;
                    }
                }
                else if(sc.st_len == sc.pkt_expected_len)
                { /* All packet data are received : check crc, and decide next state. */
                    unsigned short crc16 = ~(sc.crc16);
                    if(sc.pkt_expected_len >= 2)
                    {
                        crc16 = ((sc.pkt_buffer[sc.pkt_expected_len - 2]) << 8)
                               | (sc.pkt_buffer[sc.pkt_expected_len - 1]);
                    }
                    

                    if(crc16 == sc.crc16)
                    { /* crc16 OK : analyse packet data */

                        /* COMMON : compute start address and length. */
                        if((sc.command == 'R')
                        || (sc.command == 'W')
                        || (sc.command == 'S'))
                        {
                            unsigned char multiplicator = sc.pkt_buffer[1];
                            sc.rw_address  = sc.base_address + sc.base_length*multiplicator;
                            sc.rw_length   = sc.base_length;
                        }



                        if(sc.command == 'A')
                        {
                            sc.base_address = (unsigned char*)((sc.pkt_buffer[1] << 24)
                                            | (sc.pkt_buffer[2] << 16)
                                            | (sc.pkt_buffer[3] << 8)
                                            | (sc.pkt_buffer[4]));
                            sc.base_length  =  sc.pkt_buffer[5];

                            out = '<';
                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'R')
                        {
                            out = '<';
                            sc.state = SC_READ_DATA;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'W')
                        {
                            unsigned char first_value = sc.pkt_buffer[2];

                            sc.rw_address[0] = first_value;
                            sc.first_write_byte = first_value;
                            out = '<';
                            if(sc.rw_length != 1)
                            { /* Request next bytes. */
                                sc.state = SC_WRIT_DATA;
                                sc.st_len = 1;
                            }
                            else
                            { /* Only one byte to write -> goto sync. */
                                sc.state = SC_STAT_SYNC;
                                sc.st_len = 0;
                            }
                        }
                        else if (sc.command == 'X')
                        {
                            out = '<';
                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                            /* Execute the program after sending the acknowledge byte. */
                            need_execute = 1;
                        }
                        else if(sc.command == 'S')
                        {
                            out = '<';
                            /* Compute area's CRC. */
                            unsigned long i;
                            sc.crc16 = crc16_start();
                            for(i=0; i<sc.rw_length; i++)
                            {
                                sc.crc16 = crc16_update(sc.crc16, sc.rw_address[i]);
                            }
                            sc.state = SC_CRC_SEND;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'C')
                        {
                            out = '<';
                            sc.state = SC_RSCODE_START;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'D')
                        {
                            /* Recover data by using RS code */
                            unsigned char rs[SC_RSLEN];
                            unsigned short crcm;
                            unsigned char recpos;
                            memcpy((void*)(&crcm  ), (void*)(sc.pkt_buffer+1), sizeof(unsigned short));
                            recpos = sc.pkt_buffer[3];
                            memcpy((void*)(rs     ), (void*)(sc.pkt_buffer+4), SC_RSLEN);
                            int ret;
                            /*
                             * Note : RS recovery on Saturn seems to be slower than
                             * re-sending data (assuming ~200 bytes small chuncks).
                             * So that the following code has been disabled.
                             */
#if 1
                            ret = rsd_execute(sc.rw_address, sc.rw_length, crcm, rs, recpos, recpos+1, SC_RSD_INSRT);
#else
                            ret = 1;
#endif
                            if(ret == 0)
                            { /* OK */
                                out = '<';
                            }
                            else
                            { /* NG */
                                out = '*';
                            }

                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'F')
                        {
                            /* Recover data by using simple (single shift) recovery. */
                            unsigned short crcm;
                            unsigned char recpos;
                            unsigned char last_byte;
                            memcpy((void*)(&crcm  ), (void*)(sc.pkt_buffer+1), sizeof(unsigned short));
                            recpos    = sc.pkt_buffer[3];
                            last_byte = sc.pkt_buffer[4];

                            unsigned char* buff = sc.rw_address;
                            unsigned short len  = sc.rw_length;

                            /* Remove the inserted byte. */
                            unsigned short pp;
                            for(pp=recpos; pp<(len-1); pp++)
                            {
                                buff[pp] = buff[pp+1];
                            }
                            /* As we shifted the message, we need to set the last byte. */
                            buff[len-1] = last_byte;

                            /* Verify message integrity by using CRC. */
                            unsigned short crcv;
                            crcv = crc16_start();
                            for(pp=0; pp<len; pp++)
                            {
                                crcv = crc16_update(crcv, buff[pp]);
                            }

                            if(crcv == crcm)
                            { /* OK */
                                out = '<';
                            }
                            else
                            { /* NG */
                                out = '*';
                            }

                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'L')
                        {
                            out = '<';

                            unsigned char loop_command = sc.pkt_buffer[1];
                            /* Set loop count. */
                            sc.itcnt = 1 << (loop_command & 0x1F);

                            /* Need to return satcom function ? */
                            if((loop_command >> 7) & 0x01)
                            {
                                need_return = 1;
                            }

                            /* Reset state. */
                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'Z')
                        { /* Compress data. */
                            unsigned char* input_address;
                            unsigned short input_len;
                            /* Extract parameters from array. */
                            input_address = (unsigned char*)((sc.pkt_buffer[1] << 24)
                                          | (sc.pkt_buffer[2] << 16)
                                          | (sc.pkt_buffer[3] << 8)
                                          | (sc.pkt_buffer[4]));
                            input_len     = (unsigned short)((sc.pkt_buffer[5] << 8)
                                          | (sc.pkt_buffer[6]));
                            /* unsigned long comp_exec(unsigned char* ind, unsigned long inl, unsigned char* outd); */
                            sc.comp_return_value = comp_exec(input_address, input_len, sc.buffer);

                            out = '<';
                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'U')
                        { /* Decompress data. */
                            unsigned short input_len;
                            unsigned char* output_address;
                            /* Extract parameters from array. */
                            input_len      = (unsigned short)((sc.pkt_buffer[1] << 8)
                                           | (sc.pkt_buffer[2]));
                            output_address = (unsigned char*)((sc.pkt_buffer[3] << 24)
                                           | (sc.pkt_buffer[4] << 16)
                                           | (sc.pkt_buffer[5] << 8)
                                           | (sc.pkt_buffer[6]));
                            /* unsigned long decomp_exec(unsigned char* ind, unsigned long inl, unsigned char* outd); */
                            sc.comp_return_value = decomp_exec(sc.buffer, input_len, output_address);

                            out = '<';
                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'G')
                        {
                            out = '<';
                            sc.state = SC_COMP_RET_SEND;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'K')
                        {
                            out = '<';
                            sc.state = SC_COMP_ADR_SEND;
                            sc.st_len = 0;
                        }
                        else if(sc.command == 'Y')
                        {
                            out = '<';
                            sc.state = SC_BUFF_ADR_SEND;
                            sc.st_len = 0;
                        }
                        else
                        { /* Unknown command. */
                            out = 'b';
                            sc.state = SC_STAT_SYNC;
                            sc.st_len = 0;
                        }
                    }
                    else
                    { /* Bad CRC -> Abort */
                        out = 'a';
                        sc.state = SC_STAT_SYNC;
                        sc.st_len = 0;
                    }
                }

                if(sc.st_len <= (sc.pkt_expected_len-2))
                { /* Don't include the crc ifself when computing the crc. */
                    sc.crc16 = crc16_update(sc.crc16, in);
                }
                break;




            /* Synchronize communication with PC. */
            case(SC_STAT_SYNC):
                if(in == 'V')
                { /* Version request. */
                    out = SC_VERSION;
                }
                else
                {
                    if(in == SC_VERSION)
                    {
                        out = '>';
                        sc.state = SC_STAT_RCVPKT;
                        sc.st_len = 0;
                    }
                    else
                    {
                        out = '?';
                    }
                }
                break;



            /* Unknown/unimplemented function. */
            default:
                out = 'c';
            } /* Operations switch. */




            if(sc.io_order == 0)
            {
                /* Read data from PC. */
                in = *((volatile unsigned char *)sc.address[1]);
                /* Write previous state's answer. */
                *((volatile unsigned char *)sc.address[2]) = out;
            }

            sc.in_bup  = in;
            sc.out_bup = out;

            /* Without this stuff, a lot of transmission errors happen ... WHY ??!? */
            unsigned long l;
            for(l=0; l<1000; l++)
            {
                loop++;
                if((*flag & 1) == 0)
                {
                    break;
                }
            }
            dummywait(sc.waitcount);


            /* Execute request ? (we need to perform this after sending the output byte) */
            if(need_execute)
            {
                unsigned long exec_params[3];
                /* Extract parameters from array. */
                memcpy((void*)exec_params, (void*)(sc.pkt_buffer+1), sizeof(exec_params));
                sc_execute(exec_params[0], exec_params[1], exec_params[2]);
                need_execute = 0;
            }

            /* Return request ? (we need to perform return after sending the output byte) */
            if(need_return)
            {
                need_return = 0;
                return;
            }
        } /* flag == 1 */
    } /* Iterations */

    /* State watchdog. (automatically reset state if nothing is received for a while.) */
    if((sc.flagcntp + flagcnt) < 3)
    {
        sc.state = SC_STAT_SYNC;
        sc.st_len = 0;
        /* Set loop count to a reasonable value */
        sc.itcnt = 1 << 8;
    }
    sc.flagcntp = flagcnt;

} /* sc_check_extended function. */


#endif // !SC_RELEASE








/*----------------------------------------------------------------------------*/
/*- Debugger functions. ------------------------------------------------------*/




#ifndef SC_RELEASE
void _scd_logout_internal(unsigned char type, unsigned char id, char *string, ...)
{
    va_list argptr;
    char buff[255];

    /* Print the log message to the memory. */
    va_start(argptr, string);
    vsnprintf(buff+3, sizeof(buff)-3, string, argptr); buff[sizeof(buff)-1] = '\0';
    va_end(argptr);

    /* Set length and type to data */
    unsigned long len = strnlen(buff+3, sizeof(buff)-3);
    if(len == 0) return;
    buff[0] = 3 + len;
    buff[1] = type;
    buff[2] = id; /* 0: console, 1~: string grid */

    /* Send the log message to the crappy debugger. */
    CRAP_DBG(buff+3);

    /* Append the log message to the debug data circular buffer. */
    scd_cbwrite(&scd, (unsigned char*)buff, 3+len);
}

char* _scd_prompt_internal(char* prompt_str)
{
    /* Send prompt message. */
    _scd_logout_internal(SCD_TYPE_PROMPT, 0/*to console*/, prompt_str, 0);

    /* Wait until the PC finishes to send the prompt answer. */
    scd.proc_complete = 0;
    while(scd.proc_complete == 0)
    {
        sc_check();
    }

    /* Return the prompt answer. */
    scd.prompt_answer[SCD_PROMPT_SIZE-1] = '\0';
    return (char*)scd.prompt_answer;
}



void _scd_logflush_internal(void)
{
    while(scd.writeptr != scd.readptr)
    {
        sc_check();
    }
}

#endif // !SC_RELEASE



/*----------------------------------------------------------------------------*/
/*- File I/O functions --> PC stuff. -----------------------------------------*/

unsigned long _scd_file_request(
    unsigned char type, 
    unsigned long foffset, 
    unsigned long frw_address, 
    unsigned long frw_len, 
    char*         fname)
{
#ifndef SC_RELEASE
    char buff[255];

    /* Write the parameters. */
    unsigned char offs = 2;
    buff[offs++] = ((foffset) >> 24) & 0xFF;
    buff[offs++] = ((foffset) >> 16) & 0xFF;
    buff[offs++] = ((foffset) >>  8) & 0xFF;
    buff[offs++] = ((foffset) >>  0) & 0xFF;

    buff[offs++] = ((frw_address) >> 24) & 0xFF;
    buff[offs++] = ((frw_address) >> 16) & 0xFF;
    buff[offs++] = ((frw_address) >>  8) & 0xFF;
    buff[offs++] = ((frw_address) >>  0) & 0xFF;

    buff[offs++] = ((frw_len) >> 24) & 0xFF;
    buff[offs++] = ((frw_len) >> 16) & 0xFF;
    buff[offs++] = ((frw_len) >>  8) & 0xFF;
    buff[offs++] = ((frw_len) >>  0) & 0xFF;

    /* Print the filename to the memory. */
    snprintf(buff+offs, sizeof(buff)-offs, fname);

    /* Set length and type to data */
    unsigned long len = offs + strnlen(buff+offs, sizeof(buff)-offs);
    buff[0] = 2 + len;
    buff[1] = type;

    /* Append the file request to the debug data circular buffer. */
    scd_cbwrite(&scd, (unsigned char*)buff, 2+len);

    /* Wait until the PC process the file data request. */
    scd.proc_complete = 0;
    while(scd.proc_complete == 0)
    {
        sc_check();
    }

    /* Return size actually read/written/appended. */
    return(scd.fileio_size);

#else
    /* Don't do anything in release mode. */
    return 0;
#endif
}



#ifdef SC_USE_CDROM
/*----------------------------------------------------------------------------*/
/*- File I/O functions --> CDROM stuff (scrapped from SBL). ------------------*/
//#include "sc_cdrom.c"


#include "SBL/INCLUDE/SEGA_XPT.H"
#include "SBL/INCLUDE/SEGA_CDC.H"
#include "SBL/CDC/cd_int.h"
#include "SBL/CDC/cdc_bif.c"
#include "SBL/CDC/cdc_bio.c"
#include "SBL/CDC/cdc_cfs.c"
#include "SBL/CDC/cdc_cmn.c"
#include "SBL/CDC/cdc_dev.c"
#include "SBL/CDC/cdc_drv.c"
#include "SBL/CDC/cdc_mbu.c"
#include "SBL/CDC/cdc_mdc.c"
#include "SBL/CDC/cdc_mfb.c"
#include "SBL/CDC/cdc_mls.c"
#include "SBL/CDC/cdc_msb.c"
#include "SBL/CDC/cdc_mst.c"
#include "SBL/CDC/cdc_mwn.c"
#include "SBL/CDC/cdc_reg.c"
#include "SBL/CDC/cdc_scd.c"
#include "SBL/CDC/cdc_sel.c"
#include "SBL/CDC/cdc_sub.c"
#include "SBL/CDC/cdc_unk.c"



#include "SBL/DMA/DMA_XPT.H"
#include "SBL/DMA/DMA_CPU0.H"
#include "SBL/DMA/dma_cpu0.c"

/****************************************************************************/
/*	懳徾椞堟偺楢憐僷乕僕													*/
/****************************************************************************/
void CSH_Purge(void *adrs, Uint32 P_size)
{
#if	0	/* 儔僀儞嫬奅懳嶔 '95-12-13 */
#if	0
	/*
	**仭1995-07-26	崅嫶抭墑
	**	傢偐傝偵偔偄偐傜曄峏
	*/
	void *ea;									/*	廔椆傾僪儗僗			*/

	adrs = (void *)((Uint32)adrs
	 & 0x1ffffff0 | 0x40000000);				/*	愭摢草兽孛薨谋霓诮嶌惉	*/
	ea = (void *)((Uint32)adrs + P_size);		/*	廔椆傾僪儗僗亄侾嶼弌	*/
	while ((Uint32)adrs < (Uint32)ea) {			/*	懳徾儔僀儞暘孞傝曉偡	*/
		*(Uint32 *)adrs = 0;					/*	僀儞僶儕僨乕僩			*/
		adrs = (void *)((Uint32)adrs + 16);		/*	師偺弨旛				*/
	}											/*	end	while				*/
#else
	Uint32	( *dest )[4] = ( Uint32 (*)[] )(((Uint32)adrs & 0x1ffffff0 ) | 0x40000000);
	Uint32	i;

	for( i = 0; i < ( P_size / 16L ); i++ ){
		dest[i][0] = 0;
	}
#endif

#else	/* 儔僀儞嫬奅懳嶔 '95-12-13 */
	typedef Uint32 Line[0x10/sizeof(Uint32)];	/* 儔僀儞偼 0x10 僶僀僩扨埵 */
	Line *ptr, *end;
	Uint32 zero = 0;

	ptr = (Line *)(((Uint32)adrs & 0x1fffffff) | 0x40000000);	/* 僉儍僢僔儏僷乕僕椞堟 */
	end = (Line *)((Uint32)ptr + P_size - 0x10);	/* 廔椆億僀儞僞乮-0x10 偼億僗僩僀儞僋儕儊儞僩偺堊乯 */
	ptr = (Line *)((Uint32)ptr & -sizeof(Line));	/* 儔僀儞傾儔僀儊儞僩偵惍崌 */
	do {
		(*ptr)[0] = zero;			/* 僉儍僢僔儏僷乕僕 */
	} while (ptr++ < end);			/* 億僗僩僀儞僋儕儊儞僩偼僨傿儗僀僗儘僢僩妶梡偺堊 */
#endif	/* 儔僀儞嫬奅懳嶔 '95-12-13 */
}

#define DEBUG_LIB
#include "SBL/INCLUDE/SEGA_GFS.H"
#include "SBL/GFS/gfs_buf.c"
#include "SBL/GFS/gfs_cdb.c"
#include "SBL/GFS/gfs_cdc.c"
#include "SBL/GFS/gfs_cdf.c"
#include "SBL/GFS/gfs_dir.c"
#include "SBL/GFS/GFS_MMF.H"
#include "SBL/GFS/gfs_mmb.c"
#include "SBL/GFS/gfs_mmc.c"
#include "SBL/GFS/gfs_mmf.c"
#include "SBL/GFS/gfs_sfb.c"
#include "SBL/GFS/gfs_sff.c"
#include "SBL/GFS/gfs_trn.c"
#include "SBL/GFS/gfs.c"



/* Following stuff is adapted from Rockin'B's mini library. */

#define   OPEN_MAX  (Sint32)SC_FILE_OPENMAX
#define   DIR_MAX   (Sint32)SC_DIRMAX

// keep this info global
GfsDirTbl gfsDirTbl;
GfsDirName gfsDirName[DIR_MAX];
Uint32 gfsLibWork[GFS_WORK_SIZE(OPEN_MAX)/sizeof(Uint32)];     
// number of files in current directory
Sint32 gfsDirN;




/* for initializing the CD-ROM when testing with PAR */
#define SYS_CDINIT1(i) \
     ((**(void(**)(int))0x60002dc)(i))

#define SYS_CDINIT2() \
     ((**(void(**)(void))0x600029c)())

int sc_cdrom_unlock(void)
{
     Sint32 ret;
     CdcStat stat;
     volatile int delay;

     SYS_CDINIT1(3);

     SYS_CDINIT2();

     do {
          for(delay = 100000; delay; delay--);

          ret = CDC_GetCurStat(&stat);

     } while ((ret != 0) || (CDC_STAT_STATUS(&stat) == 0xff));

     return (int) CDC_STAT_STATUS(&stat);
}


/**
 * Split filename into folders names.
 * (CD-ROM sc_fsize and sc_fread internal use)
**/
char _filename_buff[SC_FILENAME_LEN];
char* _folders_list[SC_DIRMAX];
unsigned long _folders_count = 0;
void _parse_filename(char* filename)
{
    int i, n;
    char c;
    char* buff = _filename_buff;

    /* Get filename parameter's length. */
    n = strnlen(filename, SC_FILENAME_LEN-1);

    /*
     * Copy filename parameter a buffer (needed, because we
     * will modifiy it), and force its case to up (required
     * by GFS library).
     */
    for(i=0; i<n; i++)
    {
        c = filename[i];
        if((c >= 'a') && (c <= 'z'))
        {
            c = c - ('a' - 'A');
        }
        buff[i] = c;
    }
    /* Don't forget the last null character. */
    buff[i] = '\0';

    /* Change each folder separator to string end character. */
    for(i=0; i<n; i++)
    {
        if((buff[i] == '/') || (buff[i] == '\\'))
        {
            buff[i] = '\0';
        }
    }

    /* Gather and count folder/file names. */
    _folders_count = 0;
    _folders_list[_folders_count++] = buff;
    for(i=0; i<n-1; i++)
    {
        if(buff[i] == '\0')
        {
            if(_folders_count < SC_DIRMAX) { _folders_list[_folders_count++] = buff + i+1; }
        }
    }
}
#endif // SC_USE_CDROM





/*----------------------------------------------------------------------------*/
/*- File I/O functions --> Generic stuff -------------------------------------*/

sc_file_t _file_handles[SC_FILE_OPENMAX];
/* Init function for internal use (called in sc_init). */
void _sc_file_init(void)
{
    // scd_logout("_sc_file_init::START", 0);// scd_logflush();

    /*--- Init handles. ---*/
    memset((void*)(&_file_handles), 0, sizeof(sc_file_t) * SC_FILE_OPENMAX);

#ifdef SC_USE_CDROM
    /*--- Init CD-ROM. ---*/
    // scd_logout("_sc_file_init::STEP1", 0);// scd_logflush();
    GFS_DIRTBL_TYPE(&gfsDirTbl) = GFS_DIR_NAME;
    GFS_DIRTBL_DIRNAME(&gfsDirTbl) = gfsDirName;
    GFS_DIRTBL_NDIR(&gfsDirTbl) = DIR_MAX;
    gfsDirN = GFS_Init(OPEN_MAX, gfsLibWork, &gfsDirTbl);
    // scd_logout("_sc_file_init::STEP2", 0);// scd_logflush();

    // scd_logout("sizeof(GfsDirTbl) = %d", sizeof(GfsDirTbl));
    // scd_logout("sizeof(GfsDirName) = %d", sizeof(GfsDirName));
    // scd_logout("GFS_WORK_SIZE(OPEN_MAX) = %d", GFS_WORK_SIZE(OPEN_MAX));

    /* ON CDR's access to security ring fails. */
    /* if the CD isn't swapped (or Saturn isn't modchiped). */
    //if(gfsDirN >= 0)
    //{
    //    scd_logout("_sc_file_init::start unlock", 0);// scd_logflush();
    //    sc_cdrom_unlock();
    //}
#endif // SC_USE_CDROM
}



sc_file_t* sc_fopen(unsigned char pc_file, char* filename)
{
    int i;
    sc_file_t* fh = NULL;

    /* Look for free handle */
    for(i=0; i<SC_FILE_OPENMAX; i++)
    {
        if(_file_handles[i].is_open == 0)
        { /* Could find handle: init it. */
            fh = _file_handles + i;
            memset((void*)(fh), 0, sizeof(sc_file_t));
            strncpy(fh->filename, filename, SC_FILENAME_LEN);
            fh->filename[SC_FILENAME_LEN - 1] = '\0';
            fh->is_open = 1;
#ifndef SC_RELEASE
            fh->pc_file = pc_file;
#else
            /* In release mode, always use CD-ROM. */
            fh->pc_file = SC_FILE_CDROM;
#endif
            /* As we found what we were looking for, exit loop. */
            break;
        }
    }
    return fh;
}

void sc_fclose(sc_file_t* fh)
{
    if(fh == NULL) return;
    fh->is_open = 0;
}



unsigned long sc_fsize(sc_file_t* fh)
{
    if(fh == NULL) return 0;
    if(fh->is_open == 0) return 0;
    unsigned long size = 0;

    if(fh->pc_file == SC_FILE_PC)
    { /* Use file from PC. */
        _scd_file_request(
            /*type       */SCD_TYPE_SFILE, 
            /*foffset    */0, 
            /*frw_address*/(unsigned long)(&size), 
            /*frw_len    */sizeof(unsigned long), 
            /*fname      */fh->filename);
    }
    else
    { /* Use file from CD-ROM. */
#ifdef SC_USE_CDROM
        /* Split filename into folders+file list. */
        _parse_filename(fh->filename);
        int n = _folders_count - 1;

        int i, folder_ok = 1;
        long fid, load, set;

        /* Set folder to specified file's folder. */
        for(i=0; i<n; i++)
        {
            int j;
            for(j=0; j<10; j++)
            {
                // scd_logout("sc_fsize::GFS_DIR_FNAME(&gfsDirName[%d]) = >%s<", j, GFS_DIR_FNAME(&gfsDirName[j]));// scd_logflush();
            }
            // scd_logout("sc_fsize::Change folder: %s", _folders_list[i]);// scd_logflush();

            fid = GFS_NameToId(_folders_list[i]);
            gfsDirN = load = GFS_LoadDir(fid, &gfsDirTbl);
            set = GFS_SetDir(&gfsDirTbl);
            if(!((fid >= 0) && (load >= 0) && (set >= 0)))
            { /* folder open failure. */
                // scd_logout("sc_fsize::FAILED!", 0);// scd_logflush();
                folder_ok = 0;
                break;
            }
        }

        /* Could change folder ? */
        if(folder_ok)
        {
            char* fnam = _folders_list[n];
            fid = GFS_NameToId(fnam);
            if(fid >= 0)
            { /* Could select file -> retrieve its size. */
                size = GFS_DIR_SIZE(&gfsDirName[fid]);
            }
        }


        /* Return to root folder. */
        for(i=0; i<n; i++)
        {
            gfsDirN = load = GFS_LoadDir(GFS_NameToId(".."), &gfsDirTbl);
            set = GFS_SetDir(&gfsDirTbl);
            // scd_logout("sc_fsize::folder back", 0);// scd_logflush();
        }
#endif // SC_USE_CDROM
    }

    return size;
}


unsigned long sc_fread(sc_file_t* fh, unsigned long offset, unsigned long size, void* ptr)
{
    if(fh == NULL) return 0;
    if(fh->is_open == 0) return 0;
    unsigned long retsize = 0;

    if(fh->pc_file == SC_FILE_PC)
    { /* Use file from PC. */
        retsize = _scd_file_request(
                    /*type       */SCD_TYPE_RFILE, 
                    /*foffset    */offset, 
                    /*frw_address*/(unsigned long)(ptr), 
                    /*frw_len    */size, 
                    /*fname      */fh->filename);
    }
    else
    { /* Use file from CD-ROM. */
#ifdef SC_USE_CDROM
        /* Split filename into folders+file list. */
        _parse_filename(fh->filename);
        int n = _folders_count - 1;

        int i, folder_ok = 1;
        long fid, load, set;

        /* Set folder to specified file's folder. */
        for(i=0; i<n; i++)
        {
            int j;
            for(j=0; j<10; j++)
            {
                // scd_logout("scf_read::GFS_DIR_FNAME(&gfsDirName[%d]) = >%s<", j, GFS_DIR_FNAME(&gfsDirName[j]));// scd_logflush();
            }
            // scd_logout("scf_read::Change folder: %s", _folders_list[i]);// scd_logflush();

            fid = GFS_NameToId(_folders_list[i]);
            gfsDirN = load = GFS_LoadDir(fid, &gfsDirTbl);
            set = GFS_SetDir(&gfsDirTbl);
            if(!((fid >= 0) && (load >= 0) && (set >= 0)))
            {
                // scd_logout("scf_read::FAILED!", 0);// scd_logflush();
                folder_ok = 0;
                break;
            }
        }

        /* Could change folder ? */
        if(folder_ok)
        {
            char* fnam = _folders_list[n];
            fid = GFS_NameToId(fnam);
            if(fid >= 0)
            { /* Could select file -> retrieve its data. */
// scd_logout("scf_read::get file data -> %s (%d)", fnam, fid);// scd_logflush();
                unsigned long sector = offset/SC_SECTOR_SIZE;
                /**
                 * GFS_Load reads data by sector unit -> need to
                 * handle the case when a sector is read halfway.
                **/
                if((offset % SC_SECTOR_SIZE) == 0)
                { /* Case1: read from the begining of a sector. */
                    load = GFS_Load(fid, sector/*Sector ID*/, ptr/*Buff*/, size/*Buff size*/);
// scd_logout("scf_read::Case1::load = %d", load);// scd_logflush();
                    if(load > 0)
                    {
                        retsize = load;
                    }
                }
                else
                { /* Case2: Don't read from the begining of a sector. */
                    unsigned char sector1_data[SC_SECTOR_SIZE];
                    unsigned long sector1_offset = offset - sector*SC_SECTOR_SIZE;
                    unsigned long sector1_req; /* Bytes required in the first sector. */
// scd_logout("scf_read::Case2 offset = %d, size = %d", offset, size);// scd_logflush();
                    if((offset + size) <= SC_SECTOR_SIZE)
                    {
                        sector1_req = size;
                    }
                    else
                    {
                        sector1_req = SC_SECTOR_SIZE - sector1_offset;
                    }
                    load = GFS_Load(fid, sector/*Sector ID*/, 
                                    (void*)sector1_data/*Buff*/, 
                                    sector1_offset + sector1_req/*Buff size*/);
// scd_logout("scf_read::Case21::load = %d (sector1_offset = %d, sector1_req = %d)", load, sector1_offset, sector1_req);// scd_logflush();
                    if(load == (sector1_offset + sector1_req))
                    {
//// scd_logout("sector1_data = >%s<", sector1_data);
                        /* Read OK: Copy the required data to the output buffer. */
                        memcpy(ptr, (void*)(sector1_data + sector1_offset), sector1_req);
                        retsize = sector1_req;
// scd_logout("scf_read::Case21:: buff[0] = 0x%02X, buff = >%s<", *((unsigned char*)ptr), (char*)ptr);// scd_logflush();

                        /* If needed, read the remaining data. */
                        if(size > sector1_req)
                        {
                            load = GFS_Load(fid, sector+1/*Sector ID*/, 
                                            (void*)((unsigned char*)(ptr) + sector1_req)/*Buff*/, 
                                            size - sector1_req/*Buff size*/);
// scd_logout("scf_read::Case22::load = %d", load);// scd_logflush();
                            if(load > 0)
                            {
                                retsize = sector1_req + load;
                            }
                        }
                    }
                }
            }
        }


        /* Return to root folder. */
        for(i=0; i<n; i++)
        {
            gfsDirN = load = GFS_LoadDir(GFS_NameToId(".."), &gfsDirTbl);
            set = GFS_SetDir(&gfsDirTbl);
        }
#endif // SC_USE_CDROM
    }

    return retsize;
}



unsigned long sc_callback_fread(sc_file_t* fh, unsigned long offset, unsigned long size, void* ptr, 
                                unsigned long callback_count, 
                                Fct_sc_fread_callback callback_fct, void* callback_data)
{
    unsigned long fsize = sc_fsize(fh);
    /* Nothing to read ? */
    if(fsize == 0) return 0;
    if(offset >= fsize) return 0;
    /* Prevent from reading more than the file's contents. */
    if(size >= (fsize-offset))
    {
        size = fsize-offset;
    }

    /* Compute read chunk size, and round it to sector size. */
    if(callback_count == 0) callback_count = 1;
    unsigned long chunk_size = (size/callback_count); chunk_size -= chunk_size % SC_SECTOR_SIZE;
    /* Prevent chunk size from being null. */
    if(chunk_size < SC_SECTOR_SIZE)
    {
        chunk_size = SC_SECTOR_SIZE;
    }
int tmp=0;

    /* Read little-by-little, and call user callback. */
    unsigned long read_total = 0;
    unsigned long read_offs;
    for(read_offs = 0; read_offs < size; read_offs+=chunk_size)
    {
tmp++;
        /* Call clabback function. */
        if(callback_fct != NULL)
        {
            callback_fct(read_offs/*transfer*/, size/*total*/, callback_data/*extra*/);
        }


        unsigned long read_len = chunk_size;
        if((read_offs + read_len) >= size)
        {
            read_len = size - read_offs;
        }

        unsigned long ret;
        scd_logout("READ %d bytes from offs %d [size = %d]", read_len, read_offs, size);
        ret = sc_fread(fh, offset + read_offs, read_len, (void*)(((unsigned char*)ptr) + read_offs));
        read_total += ret;
    }
scd_logout("count = %d, chunk size = %d", tmp, chunk_size);
    return read_total;
}


unsigned long sc_fwrite(void* ptr, unsigned long size, unsigned long offset, sc_file_t* fh)
{
    if(fh == NULL) return 0;
    if(fh->is_open == 0) return 0;
    unsigned long retsize = 0;

    if(fh->pc_file == SC_FILE_PC)
    { /* Use file from PC. */
        retsize = _scd_file_request(
                    /*type       */SCD_TYPE_WFILE, 
                    /*foffset    */offset, 
                    /*frw_address*/(unsigned long)(ptr), 
                    /*frw_len    */size, 
                    /*fname      */fh->filename);
    }
    else
    { /* Can't write to CD-ROM -> do nothing. */
    }

    return retsize;
}
unsigned long sc_fappend(void* ptr, unsigned long size, sc_file_t* fh)
{
    if(fh == NULL) return 0;
    if(fh->is_open == 0) return 0;
    unsigned long retsize = 0;

    if(fh->pc_file == SC_FILE_PC)
    { /* Use file from PC. */
        retsize = _scd_file_request(
                    /*type       */SCD_TYPE_AFILE, 
                    /*foffset    */0, 
                    /*frw_address*/(unsigned long)(ptr), 
                    /*frw_len    */size, 
                    /*fname      */fh->filename);
    }
    else
    { /* Can't write to CD-ROM -> do nothing. */
    }

    return retsize;
}




