/*
 * crt.c - CRT image handling.
 *
 * Written by
 *  Andreas Boose <viceteam@t-online.de>
 *  Marco van den Heuvel <blackystardust68@yahoo.com>
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

#include "vice.h"

#include <stdio.h>
#include <string.h>

#include "archdep.h"
#include "cartridge.h"
#include "crt.h"
#include "log.h"
#include "resources.h"
#include "types.h"
#include "c64cart.h"

#define CARTRIDGE_INCLUDE_PRIVATE_API
#include "actionreplay.h"
#include "actionreplay2.h"
#include "actionreplay3.h"
#include "actionreplay4.h"
#include "atomicpower.h"
#include "bisplus.h"
#include "blackbox3.h"
#include "blackbox4.h"
#include "blackbox8.h"
#include "blackbox9.h"
#include "c64-generic.h"
#include "c64tpi.h"
#include "comal80.h"
#include "capture.h"
#include "delaep256.h"
#include "delaep64.h"
#include "delaep7x8.h"
#include "diashowmaker.h"
#include "dinamic.h"
#include "easycalc.h"
#include "easyflash.h"
#include "epyxfastload.h"
#include "exos.h"
#include "expert.h"
#include "final.h"
#include "finalplus.h"
#include "final3.h"
#include "formel64.h"
#include "freezeframe.h"
#include "freezemachine.h"
#include "funplay.h"
#include "gamekiller.h"
#include "gmod2.h"
#include "gmod3.h"
#include "gs.h"
#include "hero.h"
#include "ide64.h"
#include "isepic.h"
#include "kcs.h"
#include "kingsoft.h"
#include "ltkernal.h"
#include "mach5.h"
#include "magicdesk.h"
#include "magicformel.h"
#include "magicvoice.h"
#include "maxbasic.h"
#include "mikroass.h"
#include "mmc64.h"
#include "mmcreplay.h"
#include "multimax.h"
#include "ocean.h"
#include "pagefox.h"
#include "prophet64.h"
#include "ramlink.h"
#include "retroreplay.h"
#include "rexep256.h"
#include "rexramfloppy.h"
#include "rexutility.h"
#include "rgcd.h"
#include "rrnetmk3.h"
#include "ross.h"
#include "sdbox.h"
#include "silverrock128.h"
#include "simonsbasic.h"
#include "stardos.h"
#include "stb.h"
#include "snapshot64.h"
#include "supergames.h"
#include "supersnapshot4.h"
#include "supersnapshot.h"
#include "superexplode5.h"
#include "warpspeed.h"
#include "westermann.h"
#include "zaxxon.h"
#include "zippcode48.h"
#include "util.h"
#undef CARTRIDGE_INCLUDE_PRIVATE_API

/* #define DEBUGCRT */

#ifdef DEBUGCRT
#define DBG(x)  printf x
#else
#define DBG(x)
#endif

/*
 * CRT image "strings".
 */
const char CRT_HEADER[] = "C64 CARTRIDGE   ";
static const char CHIP_HEADER[] = "CHIP";

/*
    Open a crt file and read header, return NULL on fault, fd otherwise
*/
FILE *crt_open(const char *filename, crt_header_t *header)
{
    uint8_t crt_header[0x40];
    uint32_t skip;
    FILE *fd;

    fd = fopen(filename, MODE_READ);

    if (fd == NULL) {
        return NULL;
    }

    do {
        if (fread(crt_header, sizeof(crt_header), 1, fd) < 1) {
            log_error(LOG_DEFAULT, "could not read CRT header.");
            break;
        }

        if (memcmp(crt_header, CRT_HEADER, 16)) {
            log_error(LOG_DEFAULT, "CRT header invalid.");
            break;
        }

        skip = util_be_buf_to_dword(&crt_header[0x10]);

        if (skip < sizeof(crt_header)) {
            log_error(LOG_DEFAULT, "CRT header size is wrong (is 0x%02x, expected 0x%02x).",
                (unsigned int)skip, (unsigned int)sizeof(crt_header));
            break; /* invalid header size */
        }
        skip -= sizeof(crt_header); /* without header */

        header->version = util_be_buf_to_word(&crt_header[0x14]);
        header->type = util_be_buf_to_word(&crt_header[0x16]);
        header->subtype = crt_header[0x1a];
        header->exrom = crt_header[0x18];
        header->game = crt_header[0x19];
        memset(header->name, 0, sizeof(header->name));
        strncpy(header->name, (char *)(crt_header + 0x20),
                sizeof(header->name) - 1);

        fseek(fd, skip, SEEK_CUR); /* skip the rest */

        return fd; /* Ok, exit */
    } while (0);

    fclose(fd);
    return NULL; /* Fault */
}
/*
    returns -1 on error, else a positive CRT ID
*/
int crt_getid(const char *filename)
{
    crt_header_t header;
    FILE *fd;

    fd = crt_open(filename, &header);

    if (fd == NULL) {
        return -1;
    }

    fclose(fd);

    return header.type;
}

/*
    Read and parse chip header, return -1 on fault

XXX:    The skip member of a crt_chip_header_t will always be 0, instead of
        what the CHIP header indicates.
        So the chip member is complete bollocks,
        -- compyx
*/
int crt_read_chip_header(crt_chip_header_t *header, FILE *fd)
{
    uint8_t chipheader[0x10];

    if (fread(chipheader, sizeof(chipheader), 1, fd) < 1) {
        return -1; /* couldn't read header */
    }
    if (memcmp(chipheader, CHIP_HEADER, 4)) {
        return -1; /* invalid header signature */
    }

    /* 1: grab size of chip packet + chip header */
    header->skip = util_be_buf_to_dword(&chipheader[4]);

    if (header->skip < sizeof(chipheader)) {
        return -1; /* invalid packet size */
    }
    /* 2: subtract header size, we now have the rom size */
    header->skip -= sizeof(chipheader); /* without header */

    header->size = util_be_buf_to_word(&chipheader[14]);
    if (header->size > header->skip) {
        return -1; /* rom bigger then total size?! */
    }
    /* 3: subtract ROM size -> 0, WTF? */
    header->skip -= header->size; /* skip size after image */

    header->type = util_be_buf_to_word(&chipheader[8]);
    header->bank = util_be_buf_to_word(&chipheader[10]);
    header->start = util_be_buf_to_word(&chipheader[12]);

    if (header->start + header->size > 0x10000) {
        return -1; /* rom crossing the 64k boundary?! */
    }

    return 0;
}
/*
    Read chip data, return -1 on error
*/
int crt_read_chip(uint8_t *rawcart, int offset, crt_chip_header_t *chip, FILE *fd)
{
    if (offset + chip->size > C64CART_IMAGE_LIMIT) {
        return -1; /* overflow */
    }
    if (fread(&rawcart[offset], chip->size, 1, fd) < 1) {
        return -1; /* eof?! */
    }
    fseek(fd, chip->skip, SEEK_CUR); /* skip the rest */

    return 0;
}
/*
    Write chip header and data, return -1 on fault
*/
int crt_write_chip(uint8_t *data, crt_chip_header_t *header, FILE *fd)
{
    uint8_t chipheader[0x10];

    memcpy(chipheader, CHIP_HEADER, 4);
    util_dword_to_be_buf(&chipheader[4], header->size + sizeof(chipheader));
    util_word_to_be_buf(&chipheader[8], header->type);
    util_word_to_be_buf(&chipheader[10], header->bank);
    util_word_to_be_buf(&chipheader[12], header->start);
    util_word_to_be_buf(&chipheader[14], header->size);

    if (fwrite(chipheader, sizeof(chipheader), 1, fd) < 1) {
        return -1; /* could not write chip header */
    }

    if (fwrite(data, header->size, 1, fd) < 1) {
        return -1; /* could not write chip content */
    }

    return 0;
}
/*
    Create crt file with header, return NULL on fault, fd otherwise
*/
FILE *crt_create(const char *filename, int type, int exrom, int game, const char *name)
{
    uint8_t crt_header[0x40];
    FILE *fd;

    if (filename == NULL) {
        return NULL;
    }

    fd = fopen(filename, MODE_WRITE);

    if (fd == NULL) {
        return NULL;
    }

    memset(&crt_header, 0, sizeof(crt_header));
    memcpy(crt_header, CRT_HEADER, 16);
    util_dword_to_be_buf(&crt_header[0x10], sizeof(crt_header));
    util_word_to_be_buf(&crt_header[0x14], 0x100); /* version */
    util_word_to_be_buf(&crt_header[0x16], (uint16_t)type);
    crt_header[0x18] = exrom ? 1 : 0;
    crt_header[0x19] = game ? 1 : 0;
    strncpy((char*)(crt_header + 0x20), name, sizeof(crt_header) - 0x20 - 1);

    if (fwrite(crt_header, sizeof(crt_header), 1, fd) < 1) {
        fclose(fd);
        return NULL;
    }

    return fd;
}

/* create v1.1 header with sub type */
FILE *crt_create_v11(const char *filename, int type, int subtype, int exrom, int game, const char *name)
{
    uint8_t crt_header[0x40];
    FILE *fd;

    if (filename == NULL) {
        return NULL;
    }

    fd = fopen(filename, MODE_WRITE);

    if (fd == NULL) {
        return NULL;
    }

    memset(&crt_header, 0, sizeof(crt_header));
    memcpy(crt_header, CRT_HEADER, 16);
    util_dword_to_be_buf(&crt_header[0x10], sizeof(crt_header));
    util_word_to_be_buf(&crt_header[0x14], 0x0101); /* version */
    util_word_to_be_buf(&crt_header[0x16], (uint16_t)type);
    crt_header[0x18] = exrom ? 1 : 0;
    crt_header[0x19] = game ? 1 : 0;
    crt_header[0x1a] = subtype;
    strncpy((char*)(crt_header + 0x20), name, sizeof(crt_header) - 0x20 - 1);

    if (fwrite(crt_header, sizeof(crt_header), 1, fd) < 1) {
        fclose(fd);
        return NULL;
    }

    return fd;
}

/*
    returns -1 on error, else a positive CRT ID

    FIXME: to simplify this function a little bit, all subfunctions should
           also return the respective CRT ID on success
*/
int crt_attach(const char *filename, uint8_t *rawcart)
{
    crt_header_t header;
    int rc, new_crttype;
    FILE *fd;

    DBG(("crt_attach: %s\n", filename));

    fd = crt_open(filename, &header);

    if (fd == NULL) {
        return -1;
    }

    new_crttype = header.type;
    if (new_crttype & 0x8000) {
        /* handle our negative test IDs */
        new_crttype -= 0x10000;
    }
    DBG(("crt_attach ID: %d\n", new_crttype));

/*  cart should always be detached. there is no reason for doing fancy checks
    here, and it will cause problems incase a cart MUST be detached before
    attaching another, or even itself. (eg for initialization reasons)

    most obvious reason: attaching a different ROM (software) for the same
    cartridge (hardware) */

    cartridge_detach_image(new_crttype);

    switch (new_crttype) {
        case CARTRIDGE_CRT:
            rc = generic_crt_attach(fd, rawcart);
            if (rc != CARTRIDGE_NONE) {
                new_crttype = rc;
            }
            break;
        case CARTRIDGE_ACTION_REPLAY:
            rc = actionreplay_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_ACTION_REPLAY2:
            rc = actionreplay2_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_ACTION_REPLAY3:
            rc = actionreplay3_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_ACTION_REPLAY4:
            rc = actionreplay4_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_ATOMIC_POWER:
            rc = atomicpower_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_BISPLUS:
            rc = bisplus_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_BLACKBOX3:
            rc = blackbox3_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_BLACKBOX4:
            rc = blackbox4_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_BLACKBOX8:
            rc = blackbox8_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_BLACKBOX9:
            rc = blackbox9_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_CAPTURE:
            rc = capture_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_COMAL80:
            rc = comal80_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_DELA_EP256:
            rc = delaep256_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_DELA_EP64:
            rc = delaep64_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_DELA_EP7x8:
            rc = delaep7x8_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_DIASHOW_MAKER:
            rc = dsm_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_DINAMIC:
            rc = dinamic_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_EASYCALC:
            rc = easycalc_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_EASYFLASH:
            rc = easyflash_crt_attach(fd, rawcart, filename);
            break;
        case CARTRIDGE_EPYX_FASTLOAD:
            rc = epyxfastload_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_EXOS:
            rc = exos_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_EXPERT:
            rc = expert_crt_attach(fd, rawcart, filename);
            break;
        case CARTRIDGE_FINAL_I:
            rc = final_v1_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_FINAL_III:
            rc = final_v3_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_FINAL_PLUS:
            rc = final_plus_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_FORMEL64:
            rc = formel64_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_FREEZE_FRAME:
            rc = freezeframe_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_FREEZE_MACHINE:
            rc = freezemachine_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_FUNPLAY:
            rc = funplay_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_GAME_KILLER:
            rc = gamekiller_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_GMOD2:
            rc = gmod2_crt_attach(fd, rawcart, filename);
            break;
        case CARTRIDGE_GMOD3:
            rc = gmod3_crt_attach(fd, rawcart, filename);
            break;
        case CARTRIDGE_GS:
            rc = gs_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_HERO:
            rc = hero_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_IDE64:
            rc = ide64_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_IEEE488:
            rc = tpi_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_ISEPIC:
            rc = isepic_crt_attach(fd, rawcart, filename);
            break;
        case CARTRIDGE_KCS_POWER:
            rc = kcs_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_KINGSOFT:
            rc = kingsoft_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_LT_KERNAL:
            rc = ltkernal_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MACH5:
            rc = mach5_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MAGIC_DESK:
            rc = magicdesk_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MAGIC_FORMEL:
            rc = magicformel_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MAGIC_VOICE:
            rc = magicvoice_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MAX_BASIC:
            rc = maxbasic_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MIKRO_ASSEMBLER:
            rc = mikroass_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MMC64:
            rc = mmc64_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_MMC_REPLAY:
            rc = mmcreplay_crt_attach(fd, rawcart, filename);
            break;
        case CARTRIDGE_MULTIMAX:
            rc = multimax_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_OCEAN:
            rc = ocean_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_P64:
            rc = p64_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_PAGEFOX:
            rc = pagefox_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_RAMLINK:
            rc = ramlink_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_RETRO_REPLAY:
            rc = retroreplay_crt_attach(fd, rawcart, filename, header.subtype);
            break;
        case CARTRIDGE_REX_EP256:
            rc = rexep256_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_REX:
            rc = rex_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_REX_RAMFLOPPY:
            rc = rexramfloppy_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_RGCD:
            rc = rgcd_crt_attach(fd, rawcart);
            break;
#ifdef HAVE_RAWNET
        case CARTRIDGE_RRNETMK3:
            rc = rrnetmk3_crt_attach(fd, rawcart, filename);
            break;
#endif
        case CARTRIDGE_ROSS:
            rc = ross_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SDBOX:
            rc = sdbox_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SILVERROCK_128:
            rc = silverrock128_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SIMONS_BASIC:
            rc = simon_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_STARDOS:
            rc = stardos_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SNAPSHOT64:
            rc = snapshot64_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_STRUCTURED_BASIC:
            rc = stb_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SUPER_GAMES:
            rc = supergames_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SUPER_SNAPSHOT:
            rc = supersnapshot_v4_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SUPER_SNAPSHOT_V5:
            rc = supersnapshot_v5_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_SUPER_EXPLODE_V5:
            rc = se5_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_WARPSPEED:
            rc = warpspeed_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_WESTERMANN:
            rc = westermann_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_ZAXXON:
            rc = zaxxon_crt_attach(fd, rawcart);
            break;
        case CARTRIDGE_ZIPPCODE48:
            rc = zippcode48_crt_attach(fd, rawcart);
            break;
        default:
            archdep_startup_log_error("unknown CRT ID: %d\n", new_crttype);
            rc = -1;
            break;
    }

    fclose(fd);

    if (rc == -1) {
        DBG(("crt_attach error (%d)\n", rc));
        return -1;
    }
    DBG(("crt_attach return ID: %d\n", new_crttype));
    return new_crttype;
}
