#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>

#ifdef VMS
#include <unixio.h>
#include <file.h>
#else
#include <fcntl.h>
#endif

#ifdef DJGPP
#include "djgpp.h"
#endif

#ifdef AMIGA_PPC
#include <time.h>
#include <clib/powerpc_protos.h>
#endif

static char *rcsid = "$Id: atari.c,v 1.48 1998/02/21 15:22:04 david Exp $";

#define FALSE   0
#define TRUE    1

#include "atari.h"
#include "cpu.h"
#include "antic.h"
#include "gtia.h"
#include "pia.h"
#include "pokey.h"
#include "supercart.h"
#include "devices.h"
#include "sio.h"
#include "monitor.h"
#include "platform.h"
#include "prompts.h"
#include "rt-config.h"

TVmode tv_mode = PAL;
Machine machine = Atari;
int verbose = FALSE;
double fps;
int nframes;
ULONG lastspeed;                                /* measure time between two Antic runs */

static int os = 2;
static int pil_on = FALSE;

long firsttime;

extern UBYTE NMIEN;
extern UBYTE NMIST;
extern UBYTE PORTA;
extern UBYTE PORTB;

extern int xe_bank;
extern int rom_inserted;
extern UBYTE atari_basic[8192];
extern UBYTE atarixl_os[16384];

UBYTE *cart_image = NULL;               /* For cartridge memory */
int cart_type = NO_CART;
int DELAYED_SERIN_IRQ;
int DELAYED_SEROUT_IRQ;
int DELAYED_XMTDONE_IRQ;
int countdown_rate = 4000;
double deltatime;

#ifdef BACKUP_MSG
char backup_msg_buf[512];
#endif

void add_esc(UWORD address, UBYTE esc_code);
int Atari800_Exit(int run_monitor);
void Atari800_Hardware(void);

int load_cart(char *filename, int type);

static char *rom_filename = NULL;

void sigint_handler(int num)
{
        int restart;
        int diskno;

        restart = Atari800_Exit(TRUE);
        if (restart) {
                signal(SIGINT, sigint_handler);
                return;
        }
        for (diskno = 1; diskno < 8; diskno++)
                SIO_Dismount(diskno);

        exit(0);
}


int load_image(char *filename, int addr, int nbytes)
{
        int status = FALSE;
        int fd;

        fd = open(filename, O_RDONLY, 0777);
        if (fd != -1) {
                status = read(fd, &memory[addr], nbytes);
                if (status != nbytes) {
#ifdef BACKUP_MSG
                        sprintf(backup_msg_buf, "Error reading %s\n", filename);
#else
                        printf("Error reading %s\n", filename);
#endif
                        Atari800_Exit(FALSE);
                        exit(1);
                }
                close(fd);

                status = TRUE;
        }
        else if (verbose) {
                perror(filename);
        }
        return status;
}

void EnablePILL(void)
{
        if (os != 3) {                          /* Disable PIL when running Montezumma's Revenge */
                SetROM(0x8000, 0xbfff);
                pil_on = TRUE;
        }
}

void DisablePILL(void)
{
        if (os != 3) {                          /* Disable PIL when running Montezumma's Revenge */
                SetRAM(0x8000, 0xbfff);
                pil_on = FALSE;
        }
}

/*
 * Load a standard 8K ROM from the specified file
 */

int Insert_8K_ROM(char *filename)
{
        int status = FALSE;
        int fd;

        fd = open(filename, O_RDONLY, 0777);
        if (fd != -1) {
                read(fd, &memory[0xa000], 0x2000);
                close(fd);
                SetRAM(0x8000, 0x9fff);
                SetROM(0xa000, 0xbfff);
                cart_type = NORMAL8_CART;
                rom_inserted = TRUE;
                status = TRUE;
        }
        return status;
}

/*
 * Load a standard 16K ROM from the specified file
 */

int Insert_16K_ROM(char *filename)
{
        int status = FALSE;
        int fd;

        fd = open(filename, O_RDONLY, 0777);
        if (fd != -1) {
                read(fd, &memory[0x8000], 0x4000);
                close(fd);
                SetROM(0x8000, 0xbfff);
                cart_type = NORMAL16_CART;
                rom_inserted = TRUE;
                status = TRUE;
        }
        return status;
}

/*
 * Load an OSS Supercartridge from the specified file
 * The OSS cartridge is a 16K bank switched cartridge
 * that occupies 8K of address space between $a000
 * and $bfff
 */

int Insert_OSS_ROM(char *filename)
{
        int status = FALSE;
        int fd;

        fd = open(filename, O_RDONLY, 0777);
        if (fd != -1) {
                cart_image = (UBYTE *) malloc(0x4000);
                if (cart_image) {
                        read(fd, cart_image, 0x4000);
                        memcpy(&memory[0xa000], cart_image, 0x1000);
                        memcpy(&memory[0xb000], cart_image + 0x3000, 0x1000);
                        SetRAM(0x8000, 0x9fff);
                        SetROM(0xa000, 0xbfff);
                        cart_type = OSS_SUPERCART;
                        rom_inserted = TRUE;
                        status = TRUE;
                }
                close(fd);
        }
        return status;
}

/*
 * Load a DB Supercartridge from the specified file
 * The DB cartridge is a 32K bank switched cartridge
 * that occupies 16K of address space between $8000
 * and $bfff
 */

int Insert_DB_ROM(char *filename)
{
        int status = FALSE;
        int fd;

        fd = open(filename, O_RDONLY, 0777);
        if (fd != -1) {
                cart_image = (UBYTE *) malloc(0x8000);
                if (cart_image) {
                        read(fd, cart_image, 0x8000);
                        memcpy(&memory[0x8000], cart_image, 0x2000);
                        memcpy(&memory[0xa000], cart_image + 0x6000, 0x2000);
                        SetROM(0x8000, 0xbfff);
                        cart_type = DB_SUPERCART;
                        rom_inserted = TRUE;
                        status = TRUE;
                }
                close(fd);
        }
        return status;
}

/*
 * Load a 32K 5200 ROM from the specified file
 */

int Insert_32K_5200ROM(char *filename)
{
        int status = FALSE;
        int fd;

        fd = open(filename, O_RDONLY, 0777);
        if (fd != -1) {
                read(fd, &memory[0x4000], 0x8000);
                close(fd);
                SetROM(0x4000, 0xbfff);
                cart_type = AGS32_CART;
                rom_inserted = TRUE;
                status = TRUE;
        }
        return status;
}

/*
 * This removes any loaded cartridge ROM files from the emulator
 * It doesn't remove either the OS, FFP or character set ROMS.
 */

int Remove_ROM(void)
{
        if (cart_image) {                       /* Release memory allocated for Super Cartridges */
                free(cart_image);
                cart_image = NULL;
        }
        SetRAM(0x8000, 0xbfff);         /* Ensure cartridge area is RAM */
        cart_type = NO_CART;
        rom_inserted = FALSE;

        return TRUE;
}

int Insert_Cartridge(char *filename)
{
        int status = FALSE;
        int fd;

        fd = open(filename, O_RDONLY, 0777);
        if (fd != -1) {
                UBYTE header[16];

                read(fd, header, sizeof(header));
                if ((header[0] == 'C') &&
                        (header[1] == 'A') &&
                        (header[2] == 'R') &&
                        (header[3] == 'T')) {
                        int type;
                        int checksum;

                        type = (header[4] << 24) |
                                (header[5] << 16) |
                                (header[6] << 8) |
                                header[7];

                        checksum = (header[4] << 24) |
                                (header[5] << 16) |
                                (header[6] << 8) |
                                header[7];

                        switch (type) {
#define STD_8K 1
#define STD_16K 2
#define OSS 3
#define AGS 4
                        case STD_8K:
                                read(fd, &memory[0xa000], 0x2000);
                                SetRAM(0x8000, 0x9fff);
                                SetROM(0xa000, 0xbfff);
                                cart_type = NORMAL8_CART;
                                rom_inserted = TRUE;
                                status = TRUE;
                                break;
                        case STD_16K:
                                read(fd, &memory[0x8000], 0x4000);
                                SetROM(0x8000, 0xbfff);
                                cart_type = NORMAL16_CART;
                                rom_inserted = TRUE;
                                status = TRUE;
                                break;
                        case OSS:
                                cart_image = (UBYTE *) malloc(0x4000);
                                if (cart_image) {
                                        read(fd, cart_image, 0x4000);
                                        memcpy(&memory[0xa000], cart_image, 0x1000);
                                        memcpy(&memory[0xb000], cart_image + 0x3000, 0x1000);
                                        SetRAM(0x8000, 0x9fff);
                                        SetROM(0xa000, 0xbfff);
                                        cart_type = OSS_SUPERCART;
                                        rom_inserted = TRUE;
                                        status = TRUE;
                                }
                                break;
                        case AGS:
                                read(fd, &memory[0x4000], 0x8000);
                                close(fd);
                                SetROM(0x4000, 0xbfff);
                                cart_type = AGS32_CART;
                                rom_inserted = TRUE;
                                status = TRUE;
                                break;
                        default:
                                printf("%s is in unsupported cartridge format %d\n",
                                           filename, type);
                                break;
                        }
                }
                else {
                        printf("%s is not a cartridge\n", filename);
                }
                close(fd);
        }
        return status;
}

void PatchOS(void)
{
        const unsigned short o_open = 0;
        const unsigned short o_close = 2;
        const unsigned short o_read = 4;
        const unsigned short o_write = 6;
        const unsigned short o_status = 8;
        /* const unsigned short   o_special = 10; */
        const unsigned short o_init = 12;

        unsigned short addr;
        unsigned short entry;
        unsigned short devtab;
        int i;
/*
   =====================
   Disable Checksum Test
   =====================
 */
        if (enable_sio_patch)
                add_esc(0xe459, ESC_SIOV);

        switch (machine) {
        case Atari:
                break;
        case AtariXL:
        case AtariXE:
                memory[0xc314] = 0x8e;
                memory[0xc315] = 0xff;
                memory[0xc319] = 0x8e;
                memory[0xc31a] = 0xff;
                break;
        default:
                printf("Fatal Error in atari.c: PatchOS(): Unknown machine\n");
                Atari800_Exit(FALSE);
                exit(1);
                break;
        }
/*
   ==========================================
   Patch O.S. - Modify Handler Table (HATABS)
   ==========================================
 */
        switch (machine) {
        case Atari:
                addr = 0xf0e3;
                break;
        case AtariXL:
        case AtariXE:
                addr = 0xc42e;
                break;
        default:
                printf("Fatal Error in atari.c: PatchOS(): Unknown machine\n");
                Atari800_Exit(FALSE);
                exit(1);
                break;
        }

        for (i = 0; i < 5; i++) {
                devtab = (memory[addr + 2] << 8) | memory[addr + 1];

                switch (memory[addr]) {
                case 'P':
                        entry = (memory[devtab + o_open + 1] << 8) | memory[devtab + o_open];
                        add_esc(entry + 1, ESC_PHOPEN);
                        entry = (memory[devtab + o_close + 1] << 8) | memory[devtab + o_close];
                        add_esc(entry + 1, ESC_PHCLOS);
/*
   entry = (memory[devtab+o_read+1] << 8) | memory[devtab+o_read];
   add_esc (entry+1, ESC_PHREAD);
 */
                        entry = (memory[devtab + o_write + 1] << 8) | memory[devtab + o_write];
                        add_esc(entry + 1, ESC_PHWRIT);
                        entry = (memory[devtab + o_status + 1] << 8) | memory[devtab + o_status];
                        add_esc(entry + 1, ESC_PHSTAT);
/*
   entry = (memory[devtab+o_special+1] << 8) | memory[devtab+o_special];
   add_esc (entry+1, ESC_PHSPEC);
 */
                        memory[devtab + o_init] = 0xd2;
                        memory[devtab + o_init + 1] = ESC_PHINIT;
                        break;
                case 'C':
                        memory[addr] = 'H';
                        entry = (memory[devtab + o_open + 1] << 8) | memory[devtab + o_open];
                        add_esc(entry + 1, ESC_HHOPEN);
                        entry = (memory[devtab + o_close + 1] << 8) | memory[devtab + o_close];
                        add_esc(entry + 1, ESC_HHCLOS);
                        entry = (memory[devtab + o_read + 1] << 8) | memory[devtab + o_read];
                        add_esc(entry + 1, ESC_HHREAD);
                        entry = (memory[devtab + o_write + 1] << 8) | memory[devtab + o_write];
                        add_esc(entry + 1, ESC_HHWRIT);
                        entry = (memory[devtab + o_status + 1] << 8) | memory[devtab + o_status];
                        add_esc(entry + 1, ESC_HHSTAT);
                        break;
                case 'E':
#ifdef BASIC
                        printf("Editor Device\n");
                        entry = (memory[devtab + o_open + 1] << 8) | memory[devtab + o_open];
                        add_esc(entry + 1, ESC_E_OPEN);
                        entry = (memory[devtab + o_read + 1] << 8) | memory[devtab + o_read];
                        add_esc(entry + 1, ESC_E_READ);
                        entry = (memory[devtab + o_write + 1] << 8) | memory[devtab + o_write];
                        add_esc(entry + 1, ESC_E_WRITE);
#endif
                        break;
                case 'S':
                        break;
                case 'K':
#ifdef BASIC
                        printf("Keyboard Device\n");
                        entry = (memory[devtab + o_read + 1] << 8) | memory[devtab + o_read];
                        add_esc(entry + 1, ESC_K_READ);
#endif
                        break;
                default:
                        break;
                }

                addr += 3;                              /* Next Device in HATABS */
        }
}

static int Reset_Disabled = FALSE;

void Coldstart(void)
{
        if (os == 3) {
                os = 2;                                 /* Fiddle OS to prevent infinite recursion */
                Initialise_Monty();
                os = 3;
        }
        NMIEN = 0x00;
        NMIST = 0x00;
        PORTA = 0xff;
        if (machine == AtariXL || machine == AtariXE)
                PIA_PutByte(_PORTB, 0xff);      /* turn on operating system */
        else
                PORTB = 0xff;
        memory[0x244] = 1;
        CPU_Reset();

        if (hold_option)
                next_console_value = 0x03;      /* Hold Option During Reboot */
}

void Warmstart(void)
{
        if (os == 3)
                Initialise_Monty();

        NMIEN = 0x00;
        NMIST = 0x00;
        PORTA = 0xff;
        PORTB = 0xff;
        CPU_Reset();
}

int Initialise_AtariOSA(void)
{
        int status;

        status = load_image(atari_osa_filename, 0xd800, 0x2800);
        if (status) {
                machine = Atari;
                PatchOS();
                SetRAM(0x0000, 0xbfff);
                if (enable_c000_ram)
                        SetRAM(0xc000, 0xcfff);
                else
                        SetROM(0xc000, 0xcfff);
                SetROM(0xd800, 0xffff);
                SetHARDWARE(0xd000, 0xd7ff);
                Coldstart();
        }
        return status;
}

int Initialise_AtariOSB(void)
{
        int status;

        status = load_image(atari_osb_filename, 0xd800, 0x2800);
        if (status) {
                machine = Atari;
                PatchOS();
                SetRAM(0x0000, 0xbfff);
                if (enable_c000_ram)
                        SetRAM(0xc000, 0xcfff);
                else
                        SetROM(0xc000, 0xcfff);
                SetROM(0xd800, 0xffff);
                SetHARDWARE(0xd000, 0xd7ff);
                Coldstart();
        }
        return status;
}

int Initialise_AtariXL(void)
{
        int status;

        status = load_image(atari_xlxe_filename, 0xc000, 0x4000);
        if (status) {
                machine = AtariXL;
                PatchOS();
                memcpy(atarixl_os, memory + 0xc000, 0x4000);

                if (cart_type == NO_CART) {
                        status = Insert_8K_ROM(atari_basic_filename);
                        if (status) {
                                memcpy(atari_basic, memory + 0xa000, 0x2000);
                                SetRAM(0x0000, 0x9fff);
                                SetROM(0xc000, 0xffff);
                                SetHARDWARE(0xd000, 0xd7ff);
                                rom_inserted = FALSE;
                                Coldstart();
                        }
                        else {
#ifdef BACKUP_MSG
                                sprintf(backup_msg_buf, "Unable to load %s\n", atari_basic_filename);
#else
                                printf("Unable to load %s\n", atari_basic_filename);
#endif
                                Atari800_Exit(FALSE);
                                exit(1);
                        }
                }
                else {
                        SetRAM(0x0000, 0xbfff);
                        SetROM(0xc000, 0xffff);
                        SetHARDWARE(0xd000, 0xd7ff);
                        rom_inserted = FALSE;
                        Coldstart();
                }
        }
        return status;
}

int Initialise_AtariXE(void)
{
        int status;

        status = Initialise_AtariXL();
        machine = AtariXE;
        xe_bank = -1;

        return status;
}

int Initialise_Atari5200(void)
{
        int status;

        int i;

        for (i = 0; i < 0xf800; i++)
                memory[i] = 0x00;

        status = load_image(atari_5200_filename, 0xf800, 0x800);
        if (status) {
                machine = Atari5200;
                SetRAM(0x0000, 0x3fff);
                SetROM(0xf800, 0xffff);
                SetROM(0x4000, 0xffff);
                SetHARDWARE(0xc000, 0xc0ff);    /* 5200 GTIA Chip */
                SetHARDWARE(0xd400, 0xd4ff);    /* 5200 ANTIC Chip */
                SetHARDWARE(0xe800, 0xe8ff);    /* 5200 POKEY Chip */
                SetHARDWARE(0xeb00, 0xebff);    /* 5200 POKEY Chip */
                Coldstart();
        }
        return status;
}

/*
 * Initialise System with Montezuma's Revenge
 * image. Standard Atari OS is not required.
 */

#include "emuos.h"

#ifdef __BUILT_IN_MONTY__
#include "monty.h"
#endif

int Initialise_Monty(void)
{
        int status;

        status = load_image("monty-emuos.img", 0x0000, 0x10000);
#ifdef __BUILT_IN_MONTY__
        if (!status) {
                memcpy(&memory[0x0000], monty_h, 0xc000);
                memcpy(&memory[0xc000], emuos_h, 0x4000);
                status = TRUE;
        }
#endif

        if (status) {
                machine = Atari;
//      PatchOS ();
                SetRAM(0x0000, 0xbfff);
                if (enable_c000_ram)
                        SetRAM(0xc000, 0xcfff);
                else
                        SetROM(0xc000, 0xcfff);
                SetROM(0xd800, 0xffff);
                SetHARDWARE(0xd000, 0xd7ff);
                Coldstart();
        }
        return status;
}

/*
 * Initialise System with an replacement OS. It has just
 * enough functionality to run Defender and Star Raider.
 */

int Initialise_EmuOS(void)
{
        int status;

        status = load_image("emuos.img", 0xc000, 0x4000);
        if (!status)
                memcpy(&memory[0xc000], emuos_h, 0x4000);
        else
                printf("EmuOS: Using external emulated OS\n");

        machine = Atari;
        PatchOS();
        SetRAM(0x0000, 0xbfff);
        if (enable_c000_ram)
                SetRAM(0xc000, 0xcfff);
        else
                SetROM(0xc000, 0xcfff);
        SetROM(0xd800, 0xffff);
        SetHARDWARE(0xd000, 0xd7ff);
        Coldstart();

        status = TRUE;

        return status;
}

int main(int argc, char **argv)
{
        FILE *fp;

        int status;
        int error;
        int diskno = 1;
        int i;
        int j;

        char *rtconfig_filename = NULL;
        int config = FALSE;

        error = FALSE;

        for (i = j = 1; i < argc; i++) {
                if (strcmp(argv[i], "-configure") == 0)
                        config = TRUE;
                else if (strcmp(argv[i], "-config") == 0)
                        rtconfig_filename = argv[++i];
                else if (strcmp(argv[i], "-v") == 0) {
                        printf("%s\n", ATARI_TITLE);
                        exit(1);
                }
                else if (strcmp(argv[i], "-verbose") == 0)
                        verbose = TRUE;
                else
                        argv[j++] = argv[i];
        }

        argc = j;

        if (!RtConfigLoad(rtconfig_filename))
                config = TRUE;

        if (config) {
                RtConfigUpdate();
                RtConfigSave();
        }
        switch (default_system) {
        case 1:
                machine = Atari;
                os = 1;
                break;
        case 2:
                machine = Atari;
                os = 2;
                break;
        case 3:
                machine = AtariXL;
                break;
        case 4:
                machine = AtariXE;
                break;
        case 5:
                machine = Atari5200;
                break;
        default:
                machine = AtariXL;
                break;
        }

        switch (default_tv_mode) {
        case 1:
                tv_mode = PAL;
                break;
        case 2:
                tv_mode = NTSC;
                break;
        default:
                tv_mode = PAL;
                break;
        }

        for (i = j = 1; i < argc; i++) {
                if (strcmp(argv[i], "-atari") == 0)
                        machine = Atari;
                else if (strcmp(argv[i], "-xl") == 0)
                        machine = AtariXL;
                else if (strcmp(argv[i], "-xe") == 0)
                        machine = AtariXE;
                else if (strcmp(argv[i], "-5200") == 0)
                        machine = Atari5200;
                else if (strcmp(argv[i], "-nobasic") == 0)
                        hold_option = TRUE;
                else if (strcmp(argv[i], "-nopatch") == 0)
                        enable_sio_patch = FALSE;
                else if (strcmp(argv[i], "-pal") == 0)
                        tv_mode = PAL;
                else if (strcmp(argv[i], "-ntsc") == 0)
                        tv_mode = NTSC;
                else if (strcmp(argv[i], "-osa_rom") == 0)
                        strcpy(atari_osa_filename, argv[++i]);
                else if (strcmp(argv[i], "-osb_rom") == 0)
                        strcpy(atari_osb_filename, argv[++i]);
                else if (strcmp(argv[i], "-xlxe_rom") == 0)
                        strcpy(atari_xlxe_filename, argv[++i]);
                else if (strcmp(argv[i], "-5200_rom") == 0)
                        strcpy(atari_5200_filename, argv[++i]);
                else if (strcmp(argv[i], "-basic_rom") == 0)
                        strcpy(atari_basic_filename, argv[++i]);
                else if (strcmp(argv[i], "-cart") == 0) {
                        rom_filename = argv[++i];
                        cart_type = CARTRIDGE;
                }
                else if (strcmp(argv[i], "-rom") == 0) {
                        rom_filename = argv[++i];
                        cart_type = NORMAL8_CART;
                }
                else if (strcmp(argv[i], "-rom16") == 0) {
                        rom_filename = argv[++i];
                        cart_type = NORMAL16_CART;
                }
                else if (strcmp(argv[i], "-ags32") == 0) {
                        rom_filename = argv[++i];
                        cart_type = AGS32_CART;
                }
                else if (strcmp(argv[i], "-oss") == 0) {
                        rom_filename = argv[++i];
                        cart_type = OSS_SUPERCART;
                }
                else if (strcmp(argv[i], "-db") == 0) {         /* db 16/32 superduper cart */
                        rom_filename = argv[++i];
                        cart_type = DB_SUPERCART;
                }
                else if (strcmp(argv[i], "-refresh") == 0) {
                        sscanf(argv[++i], "%d", &refresh_rate);
                        if (refresh_rate < 1)
                                refresh_rate = 1;
                }
                else if (strcmp(argv[i], "-help") == 0) {
                        printf("\t-configure    Update Configuration File\n");
                        printf("\t-config fnm   Specify Alternate Configuration File\n");
                        printf("\t-atari        Standard Atari 800 mode\n");
                        printf("\t-xl           Atari XL mode\n");
                        printf("\t-xe           Atari XE mode (Currently same as -xl)\n");
                        printf("\t-5200         Atari 5200 Games System\n");
                        printf("\t-pal          Enable PAL TV mode\n");
                        printf("\t-ntsc         Enable NTSC TV mode\n");
                        printf("\t-rom fnm      Install standard 8K Cartridge\n");
                        printf("\t-rom16 fnm    Install standard 16K Cartridge\n");
                        printf("\t-oss fnm      Install OSS Super Cartridge\n");
                        printf("\t-db fnm       Install DB's 16/32K Cartridge (not for normal use)\n");
                        printf("\t-refresh num  Specify screen refresh rate\n");
                        printf("\t-nopatch      Don't patch SIO routine in OS\n");
                        printf("\t-a            Use A OS\n");
                        printf("\t-b            Use B OS\n");
                        printf("\t-c            Enable RAM between 0xc000 and 0xd000\n");
                        printf("\t-v            Show version/release number\n");
                        argv[j++] = argv[i];
                }
                else if (strcmp(argv[i], "-a") == 0)
                        os = 1;
                else if (strcmp(argv[i], "-b") == 0)
                        os = 2;
                else if (strcmp(argv[i], "-monty") == 0) {
                        machine = Atari;
                        os = 3;
                }
                else if (strcmp(argv[i], "-emuos") == 0) {
                        machine = Atari;
                        os = 4;
                }
                else if (strcmp(argv[i], "-c") == 0)
                        enable_c000_ram = TRUE;
                else
                        argv[j++] = argv[i];
        }

        argc = j;

        if (tv_mode == PAL)
                deltatime = (1.0 / 50.0);
        else
                deltatime = (1.0 / 60.0);

        Device_Initialise(&argc, argv);
        SIO_Initialise(&argc, argv);

        Atari_Initialise(&argc, argv);  /* Platform Specific Initialisation */

        if (!atari_screen) {
                atari_screen = (ULONG *) malloc((ATARI_HEIGHT + 16) * ATARI_WIDTH);
                for (i = 0; i < 256; i++)
                        colour_translation_table[i] = i;
        }
        /*
         * Initialise basic 64K memory to zero.
         */

        for (i = 0; i < 65536; i++)
                memory[i] = 0;

        /*
         * Initialise Custom Chips
         */

        ANTIC_Initialise(&argc, argv);
        GTIA_Initialise(&argc, argv);
        PIA_Initialise(&argc, argv);

        /*
         * Initialise Serial Port Interrupts
         */

        DELAYED_SERIN_IRQ = 0;
        DELAYED_SEROUT_IRQ = 0;
        DELAYED_XMTDONE_IRQ = 0;

        /*
         * Any parameters left on the command line must be disk images.
         */

        for (i = 1; i < argc; i++) {
                if (!SIO_Mount(diskno++, argv[i])) {
                        printf("Disk File %s not found\n", argv[i]);
                        error = TRUE;
                }
        }

        if (error) {
#ifdef BACKUP_MSG
                sprintf(backup_msg_buf, "Usage: %s [-rom filename] [-oss filename] [diskfile1...diskfile8]\n", argv[0]);
                sprintf(backup_msg_buf + strlen(backup_msg_buf), "\t-help         Extended Help\n");
#else
                printf("Usage: %s [-rom filename] [-oss filename] [diskfile1...diskfile8]\n", argv[0]);
                printf("\t-help         Extended Help\n");
#endif
                Atari800_Exit(FALSE);
                exit(1);
        }
        /*
         * Install CTRL-C Handler
         */

        signal(SIGINT, sigint_handler);

        /*
         * Configure Atari System
         */

        switch (machine) {
        case Atari:
                if (os == 1)
                        status = Initialise_AtariOSA();
                else if (os == 2)
                        status = Initialise_AtariOSB();
                else if (os == 3)
                        status = Initialise_Monty();
                else
                        status = Initialise_EmuOS();
                break;
        case AtariXL:
                status = Initialise_AtariXL();
                break;
        case AtariXE:
                status = Initialise_AtariXE();
                break;
        case Atari5200:
                status = Initialise_Atari5200();
                break;
        default:
                printf("Fatal Error in atari.c\n");
                Atari800_Exit(FALSE);
                exit(1);
        }

        if (!status) {
                printf("Operating System not available - using Emulated OS\n");
                status = Initialise_EmuOS();
        }
/*
 * ================================
 * Install requested ROM cartridges
 * ================================
 */
        if (rom_filename) {
                switch (cart_type) {
                case CARTRIDGE:
                        status = Insert_Cartridge(rom_filename);
                        break;
                case OSS_SUPERCART:
                        status = Insert_OSS_ROM(rom_filename);
                        break;
                case DB_SUPERCART:
                        status = Insert_DB_ROM(rom_filename);
                        break;
                case NORMAL8_CART:
                        status = Insert_8K_ROM(rom_filename);
                        break;
                case NORMAL16_CART:
                        status = Insert_16K_ROM(rom_filename);
                        break;
                case AGS32_CART:
                        status = Insert_32K_5200ROM(rom_filename);
                        break;
                }

                if (status) {
                        rom_inserted = TRUE;
                }
                else {
                        rom_inserted = FALSE;
                }
        }
        else {
                rom_inserted = FALSE;
        }
/*
 * ======================================
 * Reset CPU and start hardware emulation
 * ======================================
 */
        Atari800_Hardware();
        printf("Fatal error: Atari800_Hardware() returned\n");
        Atari800_Exit(FALSE);
        exit(1);
        return 1;
}

void add_esc(UWORD address, UBYTE esc_code)
{
        memory[address++] = 0xf2;       /* ESC */
        memory[address++] = esc_code;   /* ESC CODE */
        memory[address] = 0x60;         /* RTS */
}

/*
   ================================
   N = 0 : I/O Successful and Y = 1
   N = 1 : I/O Error and Y = error#
   ================================
 */

void K_Device(UBYTE esc_code)
{
        char ch;

        switch (esc_code) {
        case ESC_K_OPEN:
        case ESC_K_CLOSE:
                regY = 1;
                ClrN;
                break;
        case ESC_K_WRITE:
        case ESC_K_STATUS:
        case ESC_K_SPECIAL:
                regY = 146;
                SetN;
                break;
        case ESC_K_READ:
                ch = getchar();
                switch (ch) {
                case '\n':
                        ch = 0x9b;
                        break;
                default:
                        break;
                }
                regA = ch;
                regY = 1;
                ClrN;
                break;
        }
}

void E_Device(UBYTE esc_code)
{
        UBYTE ch;

        switch (esc_code) {
        case ESC_E_OPEN:
                printf("Editor Open\n");
                regY = 1;
                ClrN;
                break;
        case ESC_E_READ:
                ch = getchar();
                switch (ch) {
                case '\n':
                        ch = 0x9b;
                        break;
                default:
                        break;
                }
                regA = ch;
                regY = 1;
                ClrN;
                break;
        case ESC_E_WRITE:
                ch = regA;
                switch (ch) {
                case 0x7d:
                        putchar('*');
                        break;
                case 0x9b:
                        putchar('\n');
                        break;
                default:
                        if ((ch >= 0x20) && (ch <= 0x7e))       /* for DJGPP */
                                putchar(ch & 0x7f);
                        break;
                }
                regY = 1;
                ClrN;
                break;
        }
}

#define CDTMV1 0x0218

void Escape(UBYTE esc_code)
{
        int addr;

        switch (esc_code) {
#ifdef MONITOR_BREAK
        case ESC_BREAK:
                Atari800_Exit(TRUE);
                break;
#endif
        case ESC_BREAK:
        case ESC_SIOV:
                SIO();
                break;
        case ESC_K_OPEN:
        case ESC_K_CLOSE:
        case ESC_K_READ:
        case ESC_K_WRITE:
        case ESC_K_STATUS:
        case ESC_K_SPECIAL:
                K_Device(esc_code);
                break;
        case ESC_E_OPEN:
        case ESC_E_READ:
        case ESC_E_WRITE:
                E_Device(esc_code);
                break;
        case ESC_PHOPEN:
                Device_PHOPEN();
                break;
        case ESC_PHCLOS:
                Device_PHCLOS();
                break;
        case ESC_PHREAD:
                Device_PHREAD();
                break;
        case ESC_PHWRIT:
                Device_PHWRIT();
                break;
        case ESC_PHSTAT:
                Device_PHSTAT();
                break;
        case ESC_PHSPEC:
                Device_PHSPEC();
                break;
        case ESC_PHINIT:
                Device_PHINIT();
                break;
        case ESC_HHOPEN:
                Device_HHOPEN();
                break;
        case ESC_HHCLOS:
                Device_HHCLOS();
                break;
        case ESC_HHREAD:
                Device_HHREAD();
                break;
        case ESC_HHWRIT:
                Device_HHWRIT();
                break;
        case ESC_HHSTAT:
                Device_HHSTAT();
                break;
        case ESC_HHSPEC:
                Device_HHSPEC();
                break;
        case ESC_HHINIT:
                Device_HHINIT();
                break;
        default:
                Atari800_Exit(FALSE);
                printf("Invalid ESC Code %x at Address %x\n",
                           esc_code, regPC - 2);
                monitor();
                exit(1);
        }
}

int Atari800_Exit(int run_monitor)
{
        if (verbose) {
                printf("Current Frames per Secound = %f\n", (double)((double)nframes/(double)firsttime));
        }
        return Atari_Exit(run_monitor);
}

UBYTE Atari800_GetByte(UWORD addr)
{
        UBYTE byte;
/*
   ============================================================
   GTIA, POKEY, PIA and ANTIC do not fully decode their address
   ------------------------------------------------------------
   PIA (At least) is fully decoded when emulating the XL/XE
   ============================================================
 */
        switch (addr & 0xff00) {
        case 0xd000:                            /* GTIA */
                byte = GTIA_GetByte(addr - 0xd000);
                break;
        case 0xd200:                            /* POKEY */
                byte = POKEY_GetByte(addr - 0xd200);
                break;
        case 0xd300:                            /* PIA */
                byte = PIA_GetByte(addr - 0xd300);
                break;
        case 0xd400:                            /* ANTIC */
                byte = ANTIC_GetByte(addr - 0xd400);
                break;
        case 0xc000:                            /* GTIA - 5200 */
                byte = GTIA_GetByte(addr - 0xc000);
                break;
        case 0xe800:                            /* POKEY - 5200 */
                byte = POKEY_GetByte(addr - 0xe800);
                break;
        case 0xeb00:                            /* POKEY - 5200 */
                byte = POKEY_GetByte(addr - 0xeb00);
                break;
        default:
                break;
        }

        return byte;
}

int Atari800_PutByte(UWORD addr, UBYTE byte)
{
        int abort = FALSE;
/*
   ============================================================
   GTIA, POKEY, PIA and ANTIC do not fully decode their address
   ------------------------------------------------------------
   PIA (At least) is fully decoded when emulating the XL/XE
   ============================================================
 */
        switch (addr & 0xff00) {
        case 0xd000:                            /* GTIA */
                abort = GTIA_PutByte(addr - 0xd000, byte);
                break;
        case 0xd200:                            /* POKEY */
                abort = POKEY_PutByte(addr - 0xd200, byte);
                break;
        case 0xd300:                            /* PIA */
                abort = PIA_PutByte(addr - 0xd300, byte);
                break;
        case 0xd400:                            /* ANTIC */
                abort = ANTIC_PutByte(addr - 0xd400, byte);
                break;
        case 0xd500:                            /* Super Cartridges */
                abort = SuperCart_PutByte(addr, byte);
                break;
        case 0xc000:                            /* GTIA - 5200 */
                abort = GTIA_PutByte(addr - 0xc000, byte);
                break;
        case 0xeb00:                            /* POKEY - 5200 */
                abort = POKEY_PutByte(addr - 0xeb00, byte);
                break;
        default:
                break;
        }

        return abort;
}

#ifdef AMIGA_PPC
#define CLK_TCK  50
#endif

void ShowRealSpeed(ULONG * atari_screen, int refresh_rate)
{
        UWORD *ptr, *ptr2;
        double j;
        int i = (clock() - lastspeed) * (tv_mode == PAL ? 50 : 60) / CLK_TCK / refresh_rate;
        j=(double)((double)nframes/(double)firsttime);
        i=((int)j)*2;
        lastspeed = clock();
        if (i > ATARI_WIDTH / 4)
                return;

        ptr = (UWORD *) atari_screen;
        ptr += (ATARI_WIDTH / 2) * (ATARI_HEIGHT - 2) + ATARI_WIDTH / 4;        /* begin in middle of line */
        ptr2 = ptr + ATARI_WIDTH / 2;

        while (--i > 0) {                       /* number of blocks = times slower */
                int j = i << 1;
                ptr[j] = ptr2[j] = 0x0707;
                j++;
                ptr[j] = ptr2[j] = 0;
        }
}

void Atari800_Hardware(void)
{
        static struct timeval tp;
        static struct timezone tzp;
        static double lasttime;
#ifdef USE_CLOCK
        static ULONG nextclock = 0;     /* put here a non-zero value to enable speed regulator */
#endif
        static Key_Held = FALSE;

        nframes = 0;
#ifndef AMIGA_PPC
        gettimeofday(&tp, &tzp);
        lasttime = tp.tv_sec + (tp.tv_usec / 1000000.0);
#else
        //lasttime=clock()/CLOCKS_PER_SEC;
        GetSysTimePPC(&tp);
        lasttime=tp.mtv_sec.s_sec+(tp.mtv_usec.s_usec/1000000);
        firsttime=0;
#endif
        fps = 0.0;

        while (TRUE) {
                static int test_val = 0;
                int keycode;

#ifndef BASIC
/*
   colour_lookup[8] = colour_translation_table[COLBK];
 */

                keycode = Atari_Keyboard();

                switch (keycode) {
                case AKEY_COLDSTART:
                        Coldstart();
                        break;
                case AKEY_WARMSTART:
                        Warmstart();
                        break;
                case AKEY_EXIT:
                        Atari800_Exit(FALSE);
                        exit(1);
                case AKEY_BREAK:
                        if (IRQEN & 0x80) {
                                IRQST &= ~0x80;
                                IRQ = 1;
                        }
                        break;
                case AKEY_UI:
                        ui(atari_screen);
                        break;
                case AKEY_PIL:
                        if (pil_on)
                                DisablePILL();
                        else
                                EnablePILL();
                        break;
                case AKEY_NONE:
                        Key_Held = FALSE;
                        break;
                default:
                        if (Key_Held)
                                break;
                        KBCODE = keycode;
                        Key_Held = TRUE;
                        if (IRQEN & 0x40) {
                                IRQST &= ~0x40;
                                IRQ = 1;
                        }
                        break;
                }
#endif

                /*
                 * Generate Screen
                 */

#ifndef BASIC
                if (++test_val == refresh_rate) {
                        ANTIC_RunDisplayList();
                        ShowRealSpeed(atari_screen, refresh_rate);
                        Atari_DisplayScreen((UBYTE *) atari_screen);
                        test_val = 0;
                }
                else {
                        for (ypos = 0; ypos < ATARI_HEIGHT; ypos++) {
                                GO(114);
                        }
                }
#else
                for (ypos = 0; ypos < ATARI_HEIGHT; ypos++) {
                        GO(114);
                }
#endif

                nframes++;

/* if too fast host computer, slow the emulation down to 100% of original speed */

#ifdef USE_CLOCK
                /* on Atari Falcon CLK_TCK = 200 (i.e. 5 ms granularity) */
                /* on DOS (DJGPP) CLK_TCK = 91 */
                if (nextclock) {
                        ULONG curclock;
                        do {
                                curclock = clock();
                        } while (curclock < nextclock);

                        nextclock = curclock + (CLK_TCK / (tv_mode == PAL ? 50 : 60));
                }
#else
#ifndef DJGPP
                if (deltatime > 0.0) {
                        double curtime;

                        do {
#ifndef AMIGA_PPC
                                gettimeofday(&tp, &tzp);
                                curtime = tp.tv_sec + (tp.tv_usec / 1000000.0);
#else
                                GetSysTimePPC(&tp);
                                curtime=tp.mtv_sec.s_sec+(tp.mtv_usec.s_usec/1000000);
                                //curtime=clock()/CLOCKS_PER_SEC;
#endif
                        } while (0);//(curtime < (lasttime + deltatime));

                        firsttime=firsttime+(curtime-lasttime);
                        fps = 1.0 / (curtime - lasttime);
                        lasttime = curtime;
                }
#else                                                   // for dos, count ticks and use the ticks_per_second global variable
                // Use the high speed clock tick function uclock()
                if (deltatime > 0.0) {
                        static uclock_t lasttime = 0;
                        uclock_t curtime, uclockd;
                        unsigned long uclockd_hi, uclockd_lo;
                        unsigned long uclocks_per_int;

                        uclocks_per_int = (deltatime * (double) UCLOCKS_PER_SEC) + 0.5;
                        // printf( "ticks_per_int %d, %.8X\n", uclocks_per_int, (unsigned long) lasttime );

                        do {
                                curtime = uclock();
                                if (curtime > lasttime) {
                                        uclockd = curtime - lasttime;
                                }
                                else {
                                        uclockd = ((uclock_t) ~ 0 - lasttime) + curtime + (uclock_t) 1;
                                }
                                uclockd_lo = (unsigned long) uclockd;
                                uclockd_hi = (unsigned long) (uclockd >> 32);
                        } while ((uclockd_hi == 0) && (uclocks_per_int > uclockd_lo));

                        lasttime = curtime;
                }
#endif                                                  /* DJGPP */
#endif                                                  /* FALCON */
        }
}
