/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name:            sharpmz.cpp
// Created:         July 2018
// Author(s):       Philip Smart
// Description:     Sharp MZ series MiSTer Menu Add-On.
//                  This module is an extension to the MiSTer control program. It adds extensions to
//                  the menu system and additional I/O control specific to the Sharp MZ series
//                  emulation.
//
// Credits:
// Copyright:       (c) 2018 Philip Smart <philip.smart@net2net.org>
//
// History:         July 2018      - Initial module written.
//                  Sept 2018      - Synchronised with main MiSTer codebase.
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// This source file 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 source file 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, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "stdio.h"
#include "string.h"
#include "malloc.h"
#include <fcntl.h>
#include <sys/stat.h>
#include "../../hardware.h"
#include "../../fpga_io.h"
#include "sharpmz.h"
#include "../../osd.h"
#include "../../menu.h"
#include "../../debug.h"
#include "../../user_io.h"

// Names of the supported machines.
//
static const char *MZMACHINES[MAX_MZMACHINES] = { "MZ80K", "MZ80C", "MZ1200", "MZ80A", "MZ700", "MZ800", "MZ80B", "MZ2000" };

#define sharpmz_debugf(a, ...)
//#define sharpmz_debugf(a, ...) printf("\033[1;31mSHARPMZ: " a "\033[0m\n", ##__VA_ARGS__)
#define sharpmz__x_debugf(a, ...)
//#define sharpmz_x_debugf(a, ...) printf("\033[1;32mSHARPMZ: " a "\033[0m\n", ##__VA_ARGS__)

static sharpmz_config_t      config;
static uint8_t               sector_buffer[1024];
static sharpmz_tape_header_t tapeHeader;
static tape_queue_t          tapeQueue;

// Method to open a file for writing.
//
int sharpmz_file_write(fileTYPE *file, const char *fileName)
{
    int           ret;
    char          fullPath[1024];

    sprintf(fullPath, "%s/%s/%s", getRootDir(), SHARPMZ_CORE_NAME, fileName);

    const int mode = O_WRONLY | O_CREAT | O_TRUNC | O_SYNC | S_IRWXU | S_IRWXG | S_IRWXO;
    ret = FileOpenEx(file, fullPath, mode);
    if (!ret)
    {
        sharpmz_debugf("sharpmz_file_write (FileOpenEx) - File:%s, error: %d.\n", fullPath, ret);
    }

    // Success.
    return 1;
}

// Method to load a rom into the emulator.
//
void sharpmz_set_rom(romData_t &image)
{
    if(image.romEnabled)
    {
        sharpmz_debugf("Rom enabled: sharpmz_set_rom(%04x, %04x, %s)\n", image.loadAddr, image.loadSize, image.romFileName);
        sharpmz_send_file(image, 0);
    } else
    {
        sharpmz_debugf("Rom not enabled: sharpmz_set_rom(%04x, %04x, %s)\n", image.loadAddr, image.loadSize, image.romFileName);
    }
}

// Save current configuration to SD Card.
//
int sharpmz_save_config(void)
{
    FileSaveConfig(SHARPMZ_CONFIG_FILENAME, &config, sizeof(config));

    // For calls from the UI, return a state to progress on to.
    //
    return(MENU_SHARPMZ_MAIN1);
}

// Method to reset the emulator.
//
void sharpmz_reset(unsigned long preResetSleep, unsigned long postResetSleep)
{
    // Set the reset bit.
    //
    config.system_ctrl |= 1;
    user_io_8bit_set_status(config.system_ctrl, (1));

    // Sleep and hold device in reset for given period.
    //
    if(preResetSleep > 0)
        usleep(preResetSleep);

    // Remove reset.
    //
    config.system_ctrl &= ~(1);
    user_io_8bit_set_status(config.system_ctrl, (1));

    // Sleep and hold device in reset for given period.
    //
    if(postResetSleep > 0)
        usleep(postResetSleep);
}

// Reset the configuration to inbuilt defaults.
//
int sharpmz_reset_config(short setStatus)
{
    int i;
    char buf[1024];

    // Setup config defaults.
    //
    config.system_ctrl = 0;
    for(i=0; i < MAX_REGISTERS; i++)
    {
        config.system_reg[i] = 0x00; // sharpmz_read_config_register(i);
    }
    for(i=0; i < MAX_MZMACHINES; i++)
    {
        sprintf(buf, "%s_mrom.ROM",       MZMACHINES[i]);
        strcpy(config.romMonitor[i][MONITOR].romFileName, buf);
        config.romMonitor[i][MONITOR].romEnabled     = 0;
        config.romMonitor[i][MONITOR].loadAddr       = MZLOADADDR[MROM_IDX][i];
        config.romMonitor[i][MONITOR].loadSize       = MZLOADSIZE[MROM_IDX][i];
        sprintf(buf, "%s_80c_mrom.ROM",   MZMACHINES[i]);
        strcpy(config.romMonitor[i][MONITOR_80C].romFileName, buf);
        config.romMonitor[i][MONITOR_80C].romEnabled = 0;
        config.romMonitor[i][MONITOR_80C].loadAddr   = MZLOADADDR[MROM_80C_IDX][i];
        config.romMonitor[i][MONITOR_80C].loadSize   = MZLOADSIZE[MROM_80C_IDX][i];
        sprintf(buf, "%s_cgrom.ROM",      MZMACHINES[i]);
        strcpy(config.romCG[i].romFileName, buf);
        config.romCG[i].romEnabled                   = 0;
        config.romCG[i].loadAddr                     = MZLOADADDR[CGROM_IDX][i];
        config.romCG[i].loadSize                     = MZLOADSIZE[CGROM_IDX][i];
        sprintf(buf, "%s_keymap.ROM",     MZMACHINES[i]);
        strcpy(config.romKeyMap[i].romFileName, buf);
        config.romKeyMap[i].romEnabled               = 0;
        config.romKeyMap[i].loadAddr                 = MZLOADADDR[KEYMAP_IDX][i];
        config.romKeyMap[i].loadSize                 = MZLOADSIZE[KEYMAP_IDX][i];
        sprintf(buf, "%s_user.ROM",       MZMACHINES[i]);
        strcpy(config.romUser[i].romFileName, buf);
        config.romUser[i].romEnabled                 = 0;
        config.romUser[i].loadAddr                   = MZLOADADDR[USERROM_IDX][i];
        config.romUser[i].loadSize                   = MZLOADSIZE[USERROM_IDX][i];
        sprintf(buf, "%s_fdc.ROM",        MZMACHINES[i]);
        strcpy(config.romFDC[i].romFileName, buf);
        config.romFDC[i].romEnabled                  = 0;
        config.romFDC[i].loadAddr                    = MZLOADADDR[FDCROM_IDX][i];
        config.romFDC[i].loadSize                    = MZLOADSIZE[FDCROM_IDX][i];
    }

    // Set the status values.
    //
    if(setStatus)
    {
        user_io_8bit_set_status(config.system_ctrl, 0xffffffff);

        // Set the registers.
        //
        for(int i=0; i < MAX_REGISTERS; i++)
        {
            sharpmz_set_config_register(i, config.system_reg[i]);
        }
    }

    // For calls from the UI, return a state to progress on to.
    //
    return(MENU_SHARPMZ_MAIN1);
}

// Load the configuration from the HPS.
//
int sharpmz_reload_config(short setStatus)
{
    short success = 0;

    // Try to load config from the filesystem.
    //
    int size = FileLoadConfig(SHARPMZ_CONFIG_FILENAME, 0, 0);
    if (size > 0 && size == sizeof(sharpmz_config_t))
    {
        FileLoadConfig(SHARPMZ_CONFIG_FILENAME, &config, sizeof(sharpmz_config_t));
        success = 1;
        sharpmz_debugf("Config loaded successfully.");
    }
    else
    {
        sharpmz_debugf("No %s config found, creating using defaults", SHARPMZ_CONFIG_FILENAME);
        sharpmz_reset_config(0);
        sharpmz_save_config();
    }

    // Set the status values and control registers as requested..
    //
    if(setStatus && success)
    {
        user_io_8bit_set_status(config.system_ctrl, 0xffffffff);

        // Set the registers.
        //
        for(int i=0; i < MAX_REGISTERS; i++)
        {
            sharpmz_set_config_register(i, config.system_reg[i]);
        }
    }

    // Let caller know if we succeeded to load config.
    //
    return(success);
}

// Initialisation of the machine, upload roms etc.
//
void sharpmz_init(void)
{
    int i;

    sharpmz_debugf("Sharp MZ Series Initialisation");

    // Necessary sleep to allow the reset to complete and the registers to become available.
    //
    usleep(50000);

    // Try and load the SD config.
    //
    if(sharpmz_reload_config(0) == 0)
    {
        // Set configuration to default in case we cannot load the file based configuration.
        //
        sharpmz_reset_config(0);
    }

    // Setup the status values based on the config.
    //
    user_io_8bit_set_status(config.system_ctrl, 0xffffffff);

    // Set the control registers according to config.
    //
    for(int i=0; i < MAX_REGISTERS; i++)
    {
        sharpmz_set_config_register(i, config.system_reg[i]);
    }

    // Upload defined rom files.
    //
    for(i=0; i < MAX_MZMACHINES; i++)
    {
        // Load given machine rom images at defined addresses.
        //
        sharpmz_set_rom(config.romMonitor[i][0]);
        sharpmz_set_rom(config.romMonitor[i][1]);
        sharpmz_set_rom(config.romCG[i]);
        sharpmz_set_rom(config.romKeyMap[i]);
    }

    // Debug, show registers.
    for(i=0; i < MAX_REGISTERS; i++)
    {
        sharpmz_debugf("Register (%02x) = %02x", i, sharpmz_read_config_register(i));
    }

    // Initialise tape queue.
    //
    tapeQueue.top=tapeQueue.bottom = NULL;
    tapeQueue.elements = 0;

    sharpmz_debugf("Initialisation complete.");
}

// Poll handler, perform any periodic tasks via this hook.
//
void sharpmz_poll(void)
{
    // Locals.
    static unsigned long  time = GetTimer(0);
    unsigned long         timeElapsed;
    char                 *fileName;

    // Get elapsed time since last poll.
    //
    timeElapsed = GetTimer(0) - time;

    // Every 2 seconds (READY signal takes 1 second to become active after previous load) to see if the tape buffer is empty.
    //
    if(timeElapsed > 2000)
    {
        // Check to see if the Tape READY signal is inactive, if it is and we have items in the queue, load up the next
        // tape file and send it.
        //
        if( !((sharpmz_read_config_register(REGISTER_CMT)) & 0x01) )
        {
            // Check the tape queue, if items available, pop oldest and upload.
            //
            if(tapeQueue.elements > 0)
            {
                fileName = sharpmz_pop_filename();
                if(fileName != 0)
                {
                    sharpmz_debugf("Loading tape: %s\n", fileName);
                    sharpmz_load_tape_to_ram(fileName, 1);
                }
            }
        }

        // Check to see if the RECORD_READY flag is set.
        if( !((sharpmz_read_config_register(REGISTER_CMT)) & 0x04) )
        {
            // Ok, so now see if the auto save feature is enabled.
            //
            if(config.autoSave)
            {
                // Save the CMT cache ram to file. The action of reading will reset the READY flag.
                //
                sharpmz_save_tape_from_cmt((const char *)0);
            }
        }

        // Reset the timer.
        time = GetTimer(0);
    }
}

// Method to push a tape filename onto the queue.
//
void sharpmz_push_filename(char *fileName)
{
    // Locals.
    tape_queue_node_t *ptr=(tape_queue_node_t *)malloc(sizeof(tape_queue_node_t));

    // Copy filename into queue.
    strcpy(ptr->fileName, fileName);

    // Tag onto end of queue.
    ptr->next=NULL;
    if (tapeQueue.top == NULL && tapeQueue.bottom == NULL)
    {
        tapeQueue.top=tapeQueue.bottom = ptr;
    }
    else
    {
        tapeQueue.top->next = ptr;
        tapeQueue.top = ptr;
    }
    tapeQueue.elements++;
}

// Method to read the oldest tape filename entered and return it.
//
char *sharpmz_pop_filename(void)
{
    // Locals.
    tape_queue_node_t *ptr;
    static char        fileName[MAX_FILENAME_SIZE];

    // Queue empty, just return.
    if(tapeQueue.bottom == NULL)
    {
        return 0;
    }

    // Get item in a FIFO order.
    ptr=tapeQueue.bottom;
    if(tapeQueue.top == tapeQueue.bottom)
    {
        tapeQueue.top = NULL;
    }
    tapeQueue.bottom = tapeQueue.bottom->next;

    // Store in static buffer and free node.
    strcpy(fileName, ptr->fileName);
    tapeQueue.elements--;
    free(ptr);

    // Return filename.
    return(fileName);
}

// Method to iterate through the list of filenames.
//
char *sharpmz_get_next_filename(char reset)
{
    static tape_queue_node_t *ptr = NULL;

    // Queue empty, just return.
    if(tapeQueue.bottom == NULL)
    {
        return 0;
    }

    // Reset is active, start at beginning of list.
    //
    if(reset || ptr == NULL)
    {
        ptr = tapeQueue.bottom;
    } else
    {
        // At end of list, stop.
        if(ptr != NULL)
        {
            ptr = ptr->next;
        }
    }

    // Return filename if available.
    //
    return(ptr == NULL ? 0 : index(ptr->fileName, '/')+1);
}

// Method to clear the queued tape list.
//
void sharpmz_clear_filelist(void)
{
    // Locals.
    tape_queue_node_t *ptr;

    // Queue empty, just return.
    if(tapeQueue.bottom == NULL)
    {
        return;
    }

    // Go from bottom to top, freeing the bottom item in each iteration.
    for(ptr=tapeQueue.bottom; ptr != NULL; ptr=tapeQueue.bottom)
    {
        tapeQueue.bottom = ptr->next;
        free(ptr);
    }
    tapeQueue.top = NULL;
    tapeQueue.elements = 0;
}

// Return fast tape status bits. Bit 0,1 of system_reg, 0 = Off, 1 = 2x, 2 = 4x, 3 = 16x
//
int sharpmz_get_fasttape(void)
{
    return (config.system_reg[REGISTER_CMT] & 0x00000007);
}

// Get the group to which the current machine belongs:
// 0 - MZ80K/C/A type
// 1 - MZ700 type
// 2 - MZ80B/2000 type
//
short sharpmz_get_machine_group(void)
{
    short machineModel = config.system_reg[REGISTER_MODEL] & 0x07;
    short machineGroup = 0;

    // Set value according to machine model.
    //
    switch(machineModel)
    {
        // These machines currently underdevelopment, so fall through to MZ80K
        case MZ80B_IDX:  // MZ80B
        case MZ2000_IDX: // MZ2000
            machineGroup = 2;

        case MZ80K_IDX:  // MZ80K
        case MZ80C_IDX:  // MZ80C
        case MZ1200_IDX: // MZ1200
        case MZ80A_IDX:  // MZ80A
            machineGroup = 0;
            break;

        case MZ700_IDX:  // MZ700
        case MZ800_IDX:  // MZ800
            machineGroup = 1;
            break;

        default:
            machineGroup = 0;
            break;
    }

    return(machineGroup);
}

// Return string showing Fast Tape mode.
//
const char *sharpmz_get_fasttape_string(void)
{
    short fastTape = (config.system_reg[REGISTER_CMT]) & 0x00000007;

    return(SHARPMZ_FAST_TAPE[ (sharpmz_get_machine_group() * 8)  + fastTape ]);
}

// Set fast tape status bits to given value.
//
void sharpmz_set_fasttape(short mode, short setStatus)
{
    short machineGroup = sharpmz_get_machine_group();

    // Clear out current setting.
    config.system_reg[REGISTER_CMT] &= ~0x07;

    if((machineGroup == 0 && mode > 5) || (machineGroup == 1 && mode > 5) || (machineGroup == 2 && mode > 4)) mode = 0;
    config.system_reg[REGISTER_CMT] |= (mode & 0x07);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_CMT, config.system_reg[REGISTER_CMT]);
}

// Return tape buttons status bits. Bit 2, 3 of system_reg, 0 = Off, 1 = Play, 2 = Record, 3 = Auto.
//
int sharpmz_get_tape_buttons(void)
{
    return ((config.system_reg[REGISTER_CMT] >> 3) & 0x00000003 );
}

// Return string showing tape buttons setting.
//
const char *sharpmz_get_tape_buttons_string(void)
{
    short tapeSW = (config.system_reg[REGISTER_CMT] >> 3) & 0x00000003;

    return(SHARPMZ_TAPE_BUTTONS[ tapeSW ]);
}

// Set tape buttons status bits to given value.
//
void sharpmz_set_tape_buttons(short mode, short setStatus)
{
    config.system_reg[REGISTER_CMT] &= ~(3 << 3);
    config.system_reg[REGISTER_CMT] |= (mode & 0x03) << 3;

    if(setStatus)
        sharpmz_set_config_register(REGISTER_CMT, config.system_reg[REGISTER_CMT]);
}

// Method to return the next memory bank name string in a loop sequence.
//
short sharpmz_get_next_memory_bank(void)
{
    static short memoryBank = SHARPMZ_MEMBANK_SYSROM;

    if(memoryBank == 1) memoryBank = SHARPMZ_MEMBANK_SYSRAM;                          // SYSROM spans 2 physical banks.
    if(memoryBank >= SHARPMZ_MEMBANK_MAXBANKS) memoryBank = SHARPMZ_MEMBANK_SYSROM;   // Loop round when we get to the end.
    return (memoryBank++);
}

// Return string showing memory bank name.
//
const char *sharpmz_get_memory_bank_string(short memoryBank)
{
    return(SHARPMZ_MEMORY_BANK[ memoryBank ]);
}

// Return string showing memory bank dump file name.
//
const char *sharpmz_get_memory_bank_file(short memoryBank)
{
    return(SHARPMZ_MEMORY_BANK_FILE[ memoryBank ]);
}

// Return display type status bits. Bit 0, 1, 2 of system_reg.
//
int sharpmz_get_display_type(void)
{
    return (config.system_reg[REGISTER_DISPLAY] & 0x00000007 );
}

// Return string showing Display Type.
//
const char *sharpmz_get_display_type_string(void)
{
    short displayType = (config.system_reg[REGISTER_DISPLAY]) & 0x00000007;

    return(SHARPMZ_DISPLAY_TYPE[ displayType ]);
}

// Set display type status bits to given value.
//
void sharpmz_set_display_type(short displayType, short setStatus)
{
    // Sanity check.
    if(displayType > 3)  displayType = 0;
    //if(displayType == 1) displayType = 3; // Skip unassigned hardware.
    //if(displayType == 2) displayType = 3;

    config.system_reg[REGISTER_DISPLAY] &= ~(0x07);
    config.system_reg[REGISTER_DISPLAY] |= (displayType & 0x07);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DISPLAY, config.system_reg[REGISTER_DISPLAY]);
}

// Return aspect ratio status bits. Bit 1 of systemctrl, 0 = Off, 1 = On
//
int sharpmz_get_aspect_ratio(void)
{
    return ((config.system_ctrl & 0x00000002) != 0);
}

// Return string showing Aspect Ratio.
//
const char *sharpmz_get_aspect_ratio_string(void)
{
    short aspectRatio = (config.system_ctrl & 0x00000002);

    return(SHARPMZ_ASPECT_RATIO[ aspectRatio ]);
}

// Set aspect ratio status bit to given value.
//
void sharpmz_set_aspect_ratio(short on, short setStatus)
{
    config.system_ctrl &= ~(1 << 1);
    config.system_ctrl |= (on == 1 ? 1 << 1 : 0);

    if(setStatus)
        user_io_8bit_set_status(config.system_ctrl, (1 << 1));
}

// Return Scan doubler fx status bits. Bits 2,3,4 of systemctrl.
//
int sharpmz_get_scandoubler_fx(void)
{
    return ((config.system_ctrl >> 2) & 0x00000007 );
}

// Return string showing Scandoubler Fx mode.
//
const char *sharpmz_get_scandoubler_fx_string(void)
{
    short scandoublerFx = ((config.system_ctrl >> 2) & 0x00000007 );

    return(SHARPMZ_SCANDOUBLER_FX[ scandoublerFx ]);
}

// Set scan doubler status bits to given value.
//
void sharpmz_set_scandoubler_fx(short doubler, short setStatus)
{
    // Sanity check.
    if(doubler > 4) doubler = 0;

    config.system_ctrl &= ~(0x07 << 2);
    config.system_ctrl |= (doubler & 0x07) << 2;

    if(setStatus)
        user_io_8bit_set_status(config.system_ctrl, (0x07 << 2));
}

// Return VRAM wait state mode status bit. Bit 6 of system_reg, 0 = Off, 1 = On
//
int sharpmz_get_vram_wait_mode(void)
{
    return ((config.system_reg[REGISTER_DISPLAY] >> 6) & 0x00000001 );
}

// Return string showing VRAM wait state mode.
//
const char *sharpmz_get_vram_wait_mode_string(void)
{
    short vramWaitMode = ((config.system_reg[REGISTER_DISPLAY] >> 6) & 0x00000001 );

    return(SHARPMZ_VRAMWAIT_MODE[ vramWaitMode ]);
}

// Set VRAM wait state mode status bit to given value.
//
void sharpmz_set_vram_wait_mode(short on, short setStatus)
{
    config.system_reg[REGISTER_DISPLAY] &= ~(1 << 6);
    config.system_reg[REGISTER_DISPLAY] |= (on == 1 ? 1 << 6 : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DISPLAY, config.system_reg[REGISTER_DISPLAY]);
}

// Return VRAM Output Disable bit. Bit 4 of system_reg, 0 = Output Enabled, 1 = Output Disabled
//
int sharpmz_get_vram_disable_mode(void)
{
    return ((config.system_reg[REGISTER_DISPLAY] >> 4) & 0x00000001 );
}

// Return string showing VRAM Output Disable mode.
//
const char *sharpmz_get_vram_disable_mode_string(void)
{
    short vramDisableMode = ((config.system_reg[REGISTER_DISPLAY] >> 4) & 0x00000001 );

    return(SHARPMZ_VRAMDISABLE_MODE[ vramDisableMode ]);
}

// Set VRAM Output Disable mode bit to given value.
//
void sharpmz_set_vram_disable_mode(short on, short setStatus)
{
    config.system_reg[REGISTER_DISPLAY] &= ~(1 << 4);
    config.system_reg[REGISTER_DISPLAY] |= (on == 1 ? 1 << 4 : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DISPLAY, config.system_reg[REGISTER_DISPLAY]);
}

// Return GRAM Output Disable bit. Bit 5 of system_reg, 0 = Output Enabled, 1 = Output Disabled
//
int sharpmz_get_gram_disable_mode(void)
{
    return ((config.system_reg[REGISTER_DISPLAY] >> 5) & 0x00000001 );
}

// Return string showing GRAM Output Disable mode.
//
const char *sharpmz_get_gram_disable_mode_string(void)
{
    short gramDisableMode = ((config.system_reg[REGISTER_DISPLAY] >> 5) & 0x00000001 );

    return(SHARPMZ_GRAMDISABLE_MODE[ gramDisableMode ]);
}

// Set GRAM Output Disable mode bit to given value.
//
void sharpmz_set_gram_disable_mode(short on, short setStatus)
{
    config.system_reg[REGISTER_DISPLAY] &= ~(1 << 5);
    config.system_reg[REGISTER_DISPLAY] |= (on == 1 ? 1 << 5 : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DISPLAY, config.system_reg[REGISTER_DISPLAY]);
}

// Return PCG mode status bit. Bit 7 of system_reg, 0 = Off, 1 = On
//
int sharpmz_get_pcg_mode(void)
{
    return ((config.system_reg[REGISTER_DISPLAY] >> 7) & 0x00000001 );
}

// Return string showing PCG mode.
//
const char *sharpmz_get_pcg_mode_string(void)
{
    short pcgMode = ((config.system_reg[REGISTER_DISPLAY] >> 7) & 0x00000001 );

    return(SHARPMZ_PCG_MODE[ pcgMode ]);
}

// Set PCG mode status bit to given value.
//
void sharpmz_set_pcg_mode(short on, short setStatus)
{
    config.system_reg[REGISTER_DISPLAY] &= ~(1 << 7);
    config.system_reg[REGISTER_DISPLAY] |= (on == 1 ? 1 << 7 : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DISPLAY, config.system_reg[REGISTER_DISPLAY]);
}

// Return machine model status bits. Bits 0,1,2 of system_reg.
//
int sharpmz_get_machine_model(void)
{
    return (config.system_reg[REGISTER_MODEL] & 0x07);
}

// Return string showing Machine model.
//
const char *sharpmz_get_machine_model_string(short machineModel )
{
    return(SHARPMZ_MACHINE_MODEL[ machineModel & 0x07 ]);
}

// Return string showing Machine model.
//
const char *sharpmz_get_machine_model_string(void)
{
    short machineModel = (config.system_reg[REGISTER_MODEL]) & 0x07;

    return(SHARPMZ_MACHINE_MODEL[ machineModel ]);
}

// Method to return the next machine model index in a loop sequence.
//
short sharpmz_get_next_machine_model(void)
{
    static short machineModel = (config.system_reg[REGISTER_MODEL]) & 0x07;

    // Certain models not yet active.
    if(machineModel == MZ800_IDX || machineModel == MZ80B_IDX || machineModel == MZ2000_IDX) machineModel = MZ80K_IDX;
    return (machineModel++);
}

// Set machine model status bits to given value.
//
void sharpmz_set_machine_model(short machineModel, short setStatus)
{
    // Sanity.
    //
    machineModel &= 0x07;

    // When setting the model, default other settings to sensible values.
    //
    switch(machineModel)
    {
        // These machines currently underdevelopment, so fall through to MZ80K
        case MZ800_IDX:  // MZ800
        case MZ80B_IDX:  // MZ80B
        case MZ2000_IDX: // MZ2000
            machineModel = 0;

        case MZ80K_IDX:  // MZ80K
        case MZ80C_IDX:  // MZ80C
        case MZ1200_IDX: // MZ1200
        case MZ80A_IDX:  // MZ80A
            //sharpmz_set_fasttape(0x00, 1);      // No.
            sharpmz_set_display_type(0x00, 0);  // Normal display
            //sharpmz_set_pcg_mode(0x00, 1);      // ROM
            //sharpmz_set_scandoubler_fx(0x00, 1);// None.
            sharpmz_set_cpu_speed(0x00, 1);     // 2MHz MZ80C, 3.5MHz MZ700
            break;

        case MZ700_IDX:  // MZ700
            //sharpmz_set_fasttape(0x00, 1);      // No.
            sharpmz_set_display_type(0x02, 0);  // Colour display
            //sharpmz_set_pcg_mode(0x00, 1);      // ROM sharpmz_set_scandoubler_fx(0x00, 0);// None.
            //sharpmz_set_scandoubler_fx(0x00, 1);// None.
            sharpmz_set_cpu_speed(0x00, 0);     // 2MHz MZ80C, 3.5MHz MZ700
            break;
    }

    // Set the machine model.
    config.system_reg[REGISTER_MODEL] &= ~(0x07);
    config.system_reg[REGISTER_MODEL] |= (machineModel);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_MODEL, config.system_reg[REGISTER_MODEL]);
}

// Return string showing CPU speed for display.
//
const char *sharpmz_get_cpu_speed_string(void)
{
    short cpuSet = (config.system_reg[REGISTER_CPU]) & 0x00000007;

    return(SHARPMZ_CPU_SPEED[ (sharpmz_get_machine_group() * 8)  + cpuSet ]);
}

// Return cpu speed status bits. Bits 0, 1 of system_reg.
//
int sharpmz_get_cpu_speed(void)
{
    return ((config.system_reg[REGISTER_CPU]) & 0x00000007);
}

// Set cpu speed status bits to given value.
//
void sharpmz_set_cpu_speed(short cpuSpeed, short setStatus)
{
    short machineGroup = sharpmz_get_machine_group();

    // Clear current setting.
    config.system_reg[REGISTER_CPU] &= ~(0x07);

    if((machineGroup == 0 && cpuSpeed > 5) || (machineGroup == 1 && cpuSpeed > 5) || (machineGroup == 2 && cpuSpeed > 4)) cpuSpeed = 0;
    config.system_reg[REGISTER_CPU] |= (cpuSpeed & 0x07);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_CPU, config.system_reg[REGISTER_CPU]);
}

// Return audio source status bit. Bit 0 of system_reg, 0 = sound, 1 = tape
//
int sharpmz_get_audio_source(void)
{
    return ((config.system_reg[REGISTER_AUDIO]) & 0x00000001);
}

// Return string showing Audio source.
//
const char *sharpmz_get_audio_source_string(void)
{
    short audioSource = ((config.system_reg[REGISTER_AUDIO]) & 0x00000001);

    return(SHARPMZ_AUDIO_SOURCE[ audioSource ]);
}

// Set audio source status bit to given value.
//
void sharpmz_set_audio_source(short on, short setStatus)
{
    config.system_reg[REGISTER_AUDIO] &= ~(1);
    config.system_reg[REGISTER_AUDIO] |= (on == 1 ? 1 : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_AUDIO, config.system_reg[REGISTER_AUDIO]);
}

// Return audio volume setting. The value is inverse, ie. it is an attenuation, 15 is highest, 0 is off.
//
int sharpmz_get_audio_volume(void)
{
    return ((config.volume) & 0x0f);
}

// Return string showing Audio volume setting - this is inversed.
//
const char *sharpmz_get_audio_volume_string(void)
{
    return(SHARPMZ_AUDIO_VOLUME[ config.volume & 0x0f ]);
}

// Set audio volume level. 15 = high attenuation, 0 = no attenuation.
//
void sharpmz_set_audio_volume(short volume, short setStatus)
{
    config.volume &= 0x10;
    config.volume |= volume & 0x0f;

    if(setStatus)
        spi_uio_cmd8(UIO_AUDVOL, config.volume);
}

// Return audio mute setting.
//
int sharpmz_get_audio_mute(void)
{
    return ((config.volume & 0x10) >> 4);
}

// Return string showing Audio mute setting.
//
const char *sharpmz_get_audio_mute_string(void)
{
    return(SHARPMZ_AUDIO_MUTE[ (config.volume & 0x10) >> 4 ]);
}

// Set audio mute level.
//
void sharpmz_set_audio_mute(short mute, short setStatus)
{
    config.volume &= 0x0f;
    config.volume |= mute << 4;

    if(setStatus)
        spi_uio_cmd8(UIO_AUDVOL, config.volume);
}

// Return debug enable status bit. Bit 7 of system_reg, 0 = Off, 1 = On
//
int sharpmz_get_debug_enable(void)
{
    return ((config.system_reg[REGISTER_DEBUG] >> 7) & 0x00000001);
}

// Return string showing Debug Enable mode.
//
const char *sharpmz_get_debug_enable_string(void)
{
    short debugEnable = ((config.system_reg[REGISTER_DEBUG] >> 7) & 0x00000001);

    return(SHARPMZ_DEBUG_ENABLE[ debugEnable ]);
}

// Set debug enable status bit to given value.
//
void sharpmz_set_debug_enable(short on, short setStatus)
{
    config.system_reg[REGISTER_DEBUG] &= ~(1 << 7);
    config.system_reg[REGISTER_DEBUG] |= (on == 1 ? 1 << 7 : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DEBUG, config.system_reg[REGISTER_DEBUG]);
}

// Return debug leds status bit. Bit 6 of system_reg, 0 = Off, 1 = On
//
int sharpmz_get_debug_leds(void)
{
    return ((config.system_reg[REGISTER_DEBUG] >> 6) & 0x00000001);
}

// Return string showing Debug Leds.
//
const char *sharpmz_get_debug_leds_string(void)
{
    short debugLeds = ((config.system_reg[REGISTER_DEBUG] >> 6) & 0x00000001);

    return(SHARPMZ_DEBUG_LEDS[ debugLeds ]);
}

// Set debug leds status bit to given value.
//
void sharpmz_set_debug_leds(short on, short setStatus)
{
    config.system_reg[REGISTER_DEBUG] &= ~(1 << 6);
    config.system_reg[REGISTER_DEBUG] |= (on == 1 ? 1 << 6 : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DEBUG, config.system_reg[REGISTER_DEBUG]);
}

// Method to return the next debug bank index in a loop sequence.
//
short sharpmz_get_next_debug_leds_bank(void)
{
    static short debugLedsBank = (config.system_reg[REGISTER_DEBUG] & 0x00000007);

    // Certain models not yet active.
    if(strcmp(SHARPMZ_DEBUG_LEDS_BANK[ debugLedsBank ], "") == 0) debugLedsBank = 0;

    return (debugLedsBank++);
}

// Return string showing debug led bank.
//
const char *sharpmz_get_debug_leds_bank_string(void)
{
    short debugLedsBank = (config.system_reg[REGISTER_DEBUG] & 0x00000007);

    return(SHARPMZ_DEBUG_LEDS_BANK[ debugLedsBank ]);
}

// Set debug LEDS bank index.
//
void sharpmz_set_debug_leds_bank(short debugLedsBank, short setStatus)
{
    // Sanity check.
    if(debugLedsBank >  7) debugLedsBank = 0;

    config.system_reg[REGISTER_DEBUG] &= ~(0x07);
    config.system_reg[REGISTER_DEBUG] |= (debugLedsBank & 0x07);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DEBUG, config.system_reg[REGISTER_DEBUG]);
}

// Method to return the next debug sub-bank index in a loop sequence.
//
short sharpmz_get_next_debug_leds_subbank(unsigned char reset)
{
    static short debugLedsSubBank = ((config.system_reg[REGISTER_DEBUG] >> 3) & 0x00000007);
    short        debugLedsBank = (config.system_reg[REGISTER_DEBUG] & 0x00000007);

    // Limit choice according to number of subbanks and subbanks setup.
    if(reset || debugLedsSubBank > 7) debugLedsSubBank = 0;
    if(strcmp(SHARPMZ_DEBUG_LEDS_SUBBANK[ (debugLedsBank * 8) + debugLedsSubBank ], "") == 0) debugLedsSubBank = 0;
    return (debugLedsSubBank++);
}

// Return string showing debug led sub-bank.
//
const char *sharpmz_get_debug_leds_subbank_string(void)
{
    short debugLedsBank    = (config.system_reg[REGISTER_DEBUG] & 0x00000007);
    short debugLedsSubBank = ((config.system_reg[REGISTER_DEBUG] >> 3) & 0x00000007);

    return(SHARPMZ_DEBUG_LEDS_SUBBANK[ (debugLedsBank * 8) + debugLedsSubBank ]);
}

// Set debug LEDS sub-bank index.
//
void sharpmz_set_debug_leds_subbank(short debugLedsSubBank, short setStatus)
{
    // Sanity check.
    if(debugLedsSubBank > 7) debugLedsSubBank = 0;

    config.system_reg[REGISTER_DEBUG] &= ~(0x07 << 3);
    config.system_reg[REGISTER_DEBUG] |= ((debugLedsSubBank & 0x07) << 3);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DEBUG, config.system_reg[REGISTER_DEBUG]);
}

// Method to return the next debug cpu frequency index in a loop sequence.
//
short sharpmz_get_next_debug_cpufreq(void)
{
    static short debugCPUFrequency = ((config.system_reg[REGISTER_DEBUG2] >> 4) & 0x0000000f);

    // Sanity check.
    if(debugCPUFrequency > 15) debugCPUFrequency = 0;

    return (debugCPUFrequency++);
}

// Return string showing debug cpu frequency.
//
const char *sharpmz_get_debug_cpufreq_string(void)
{
    short debugCPUFrequency = ((config.system_reg[REGISTER_DEBUG2] >> 4) & 0x0000000f);

    return(SHARPMZ_DEBUG_CPUFREQ[ debugCPUFrequency ]);
}

// Set debug cpu frequency index.
//
void sharpmz_set_debug_cpufreq(short debugCPUFrequency, short setStatus)
{
    // Sanity check.
    if(debugCPUFrequency > 15) debugCPUFrequency = 0;

    config.system_reg[REGISTER_DEBUG2] &= ~(0x0f << 4);
    config.system_reg[REGISTER_DEBUG2] |= ((debugCPUFrequency & 0x0f) << 4);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DEBUG2, config.system_reg[REGISTER_DEBUG2]);
}

// Method to return the next debug sample frequency index in a loop sequence.
//
short sharpmz_get_next_debug_leds_smpfreq(void)
{
    static short debugLedsSampleFrequency = (config.system_reg[REGISTER_DEBUG2] & 0x0000000f);

    // Sanity check.
    if(debugLedsSampleFrequency > 15) debugLedsSampleFrequency = 0;

    return (debugLedsSampleFrequency++);
}

// Return string showing debug led sample frequency.
//
const char *sharpmz_get_debug_leds_smpfreq_string(void)
{
    short debugLedsSampleFrequency = (config.system_reg[REGISTER_DEBUG2] & 0x0000000f);

    return(SHARPMZ_DEBUG_LEDS_SMPFREQ[ debugLedsSampleFrequency ]);
}

// Set debug LEDS sample frequency index.
//
void sharpmz_set_debug_leds_smpfreq(short debugLedsSampleFrequency, short setStatus)
{
    // Sanity check.
    if(debugLedsSampleFrequency > 15) debugLedsSampleFrequency = 0;

    config.system_reg[REGISTER_DEBUG2] &= ~(0x0f);
    config.system_reg[REGISTER_DEBUG2] |= (debugLedsSampleFrequency & 0x0f);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_DEBUG2, config.system_reg[REGISTER_DEBUG2]);
}

// Return string showing auto save enabled mode.
//
const char *sharpmz_get_auto_save_enabled_string(void)
{
    return(SHARPMZ_AUTO_SAVE_ENABLED[ config.autoSave ]);
}

// Method to return the enabled state of auto save.
//
unsigned char sharpmz_get_auto_save_enabled(void)
{
    return(config.autoSave);
}

// Method to set the auto save flag to determine if automatic saving of incoming tape data is enabled.
//
void sharpmz_set_auto_save_enabled(unsigned char on)
{
    config.autoSave = on;
}

// Method to return the enabled state of a user rom for given machine.
//
short sharpmz_get_user_rom_enabled(short machineModel)
{
    return ((config.system_reg[REGISTER_USERROM] >> machineModel) & 0x00000001);
}

// Return string showing user rom enabled status.
//
const char *sharpmz_get_user_rom_enabled_string(short machineModel)
{
    short userRomEnabled = ((config.system_reg[REGISTER_USERROM] >> machineModel) & 0x00000001);

    return(SHARPMZ_USERROM_ENABLED[ userRomEnabled ]);
}

// Method to set the enabled state of a user rom for a given machine.
//
void sharpmz_set_user_rom_enabled(short machineModel, short on, short setStatus)
{
    config.system_reg[REGISTER_USERROM] &= ~(1 << machineModel);
    config.system_reg[REGISTER_USERROM] |= (on == 1 ? 1 << machineModel : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_USERROM, config.system_reg[REGISTER_USERROM]);
}

// Method to return the enabled state of a fdc rom for given machine.
//
short sharpmz_get_fdc_rom_enabled(short machineModel)
{
    return ((config.system_reg[REGISTER_FDCROM] >> machineModel) & 0x00000001);
}

// Return string showing fdc rom enabled status.
//
const char *sharpmz_get_fdc_rom_enabled_string(short machineModel)
{
    short fdcRomEnabled = ((config.system_reg[REGISTER_FDCROM] >> machineModel) & 0x00000001);

    return(SHARPMZ_FDCROM_ENABLED[ fdcRomEnabled ]);
}

// Method to set the enabled state of a fdc rom for a given machine.
//
void sharpmz_set_fdc_rom_enabled(short machineModel, short on, short setStatus)
{
    config.system_reg[REGISTER_FDCROM] &= ~(1 << machineModel);
    config.system_reg[REGISTER_FDCROM] |= (on == 1 ? 1 << machineModel : 0);

    if(setStatus)
        sharpmz_set_config_register(REGISTER_FDCROM, config.system_reg[REGISTER_FDCROM]);
}

// Method to return the enabled state of a rom for loading.
//
short sharpmz_get_custom_rom_enabled(short machineModel, short romType)
{
    short romEnabled = 0;

    machineModel &= 0x07;
    romType      &= 0x07;
    switch(romType)
    {
        case MROM_IDX:
            romEnabled = config.romMonitor[machineModel][MONITOR].romEnabled;
            break;

        case MROM_80C_IDX:
            romEnabled = config.romMonitor[machineModel][MONITOR_80C].romEnabled;
            break;

        case CGROM_IDX:
            romEnabled = config.romCG[machineModel].romEnabled;
            break;

        case KEYMAP_IDX:
            romEnabled = config.romKeyMap[machineModel].romEnabled;
            break;

        case USERROM_IDX:
            romEnabled = config.romUser[machineModel].romEnabled;
            break;

        case FDCROM_IDX:
            romEnabled = config.romFDC[machineModel].romEnabled;
            break;

        default:
            sharpmz_debugf("Unknown get enabled romType: %d\n", romType);
            break;
    }
    return(romEnabled);
}

// Return string showing rom enabled mode.
//
const char *sharpmz_get_custom_rom_enabled_string(short romEnabled)
{
    return(SHARPMZ_ROM_ENABLED[ romEnabled ]);
}

// Method to set the enabled state of a rom for loading.
//
void sharpmz_set_custom_rom_enabled(short machineModel, short romType, short on)
{
    machineModel &= 0x07;
    romType      &= 0x07;
    switch(romType)
    {
        case MROM_IDX:
            config.romMonitor[machineModel][MONITOR].romEnabled = (on == 1);
            break;

        case MROM_80C_IDX:
            config.romMonitor[machineModel][MONITOR_80C].romEnabled = (on == 1);
            break;

        case CGROM_IDX:
            config.romCG[machineModel].romEnabled = (on == 1);
            break;

        case KEYMAP_IDX:
            config.romKeyMap[machineModel].romEnabled = (on == 1);
            break;

        case USERROM_IDX:
            config.romUser[machineModel].romEnabled = (on == 1);
            break;

        case FDCROM_IDX:
            config.romFDC[machineModel].romEnabled = (on == 1);
            break;

        default:
            sharpmz_debugf("Unknown set enabled romType: %d\n", romType);
            break;
    }
}

// Method to return the filename of a rom.
//
char *sharpmz_get_rom_file(short machineModel, short romType)
{
    machineModel &= 0x07;
    romType      &= 0x07;
    switch(romType)
    {
        case MROM_IDX:
           return(config.romMonitor[machineModel][MONITOR].romFileName);

        case MROM_80C_IDX:
           return(config.romMonitor[machineModel][MONITOR_80C].romFileName);

        case CGROM_IDX:
            return(config.romCG[machineModel].romFileName);

        case KEYMAP_IDX:
            return(config.romKeyMap[machineModel].romFileName);

        case USERROM_IDX:
            return(config.romUser[machineModel].romFileName);

        case FDCROM_IDX:
            return(config.romFDC[machineModel].romFileName);

        default:
            sharpmz_debugf("Unknown get filename romType: %d\n", romType);
            break;
    }
    return(NULL);
}

// Method to set the filename of a rom.
//
void sharpmz_set_rom_file(short machineModel, short romType, char *filename)
{
    machineModel &= 0x07;
    romType      &= 0x03;
    switch(romType)
    {
        case MROM_IDX:
            strcpy(config.romMonitor[machineModel][MONITOR].romFileName, filename);
            sharpmz_debugf("Copy MROM:%s, %s\n", filename,config.romMonitor[machineModel][MONITOR].romFileName );
            break;

        case MROM_80C_IDX:
            strcpy(config.romMonitor[machineModel][MONITOR_80C].romFileName, filename);
            sharpmz_debugf("Copy MROM (80x25):%s, %s\n", filename,config.romMonitor[machineModel][MONITOR_80C].romFileName );
            break;

        case CGROM_IDX:
            strcpy(config.romCG[machineModel].romFileName, filename);
            sharpmz_debugf("Copy CGROM:%s, %s\n", filename,config.romCG[machineModel].romFileName );
            break;

        case KEYMAP_IDX:
            strcpy(config.romKeyMap[machineModel].romFileName, filename);
            sharpmz_debugf("Copy KEYROM:%s, %s\n", filename,config.romKeyMap[machineModel].romFileName );
            break;

        case USERROM_IDX:
            strcpy(config.romUser[machineModel].romFileName, filename);
            sharpmz_debugf("Copy USERROM:%s, %s\n", filename,config.romUser[machineModel].romFileName );
            break;

        case FDCROM_IDX:
            strcpy(config.romFDC[machineModel].romFileName, filename);
            sharpmz_debugf("Copy FDCROM:%s, %s\n", filename,config.romFDC[machineModel].romFileName );
            break;

        default:
            sharpmz_debugf("Unknown set filename romType: %d, filename:%s\n", romType, filename);
            break;
    }
}

// Return string showing last Tape type read.
//
const char *sharpmz_get_tape_type_string(void)
{
    return(SHARPMZ_TAPE_TYPE[ tapeHeader.dataType ]);
}

// Method to get the header of the last tape loaded.
//
sharpmz_tape_header_t *sharpmz_get_tape_header(void)
{
    return(&tapeHeader);
}

// Local version of File Read so that we can read exact number of bytes required
// and return parameter specifies number of bytes actually read.
//
int sharpmz_file_read(fileTYPE *file, void *pBuffer, int nSize)
{
    if (!FileSeek(file, file->offset, SEEK_SET))
    {
        sharpmz_debugf("file_read error(seek).\n");
        return 0;
    }

    int ret = FileReadAdv(file, pBuffer, nSize);
    if (ret < 0)
    {
        sharpmz_debugf("file_read error(%d).\n", ret);
        return 0;
    }

    return ret;
}

// Method to program a config register in the emulator.
//
void sharpmz_set_config_register(short addr, unsigned char value)
{
    EnableFpga();
    spi8(SHARPMZ_CONFIG_TX);
    spi8(addr);                                   // Address of register to change.
    spi8(value);                                  // Value to set in the register.
    sharpmz_debugf("Register Set(%02x) -> %02x\n", addr, value);
    DisableFpga();

    for(int i=0; i < MAX_REGISTERS; i++)
    {
        sharpmz_debugf("%02x => %02x, ", i, sharpmz_read_config_register(i));
    }
    sharpmz_debugf("\n");
    //sharpmz_read_config_register(addr);
}

// Method to read a config register in the emulator.
//
unsigned char sharpmz_read_config_register(short addr)
{
    unsigned char value;

    EnableFpga();
    spi8(SHARPMZ_CONFIG_RX);
    spi8(addr);                                   // Address of register to change.
    spi8(0x00);
    value = spi_b(0x00);                          // Padding to give a cycle to allow data to be read.
    //sharpmz_debugf("Register Read(%02x)=%02x\n", addr, value);
    DisableFpga();

    // Return the value stored in the config register.
    return(value);
}

// Method to send a file to the emulator.
//
void sharpmz_send_file(romData_t &image, char *dirPrefix)
{
    char           sendFile[1024];
    unsigned int   actualReadSize;
    unsigned long  time = GetTimer(0);
    unsigned short i;
    fileTYPE       file = {};

    // If a prefix is given (ie. core directory), prepend it before use.
    //
    if(dirPrefix)
    {
        strcpy(sendFile, dirPrefix);
        strcat(sendFile, "/");
        strcat(sendFile, image.romFileName);
    } else
    {
        strcpy(sendFile, SHARPMZ_CORE_NAME);
        strcat(sendFile, "/");
        strcat(sendFile, image.romFileName);
    }

    sharpmz_debugf("Sending file:%s", sendFile);

    // Try and open the given file, exit on failure.
    if (!FileOpen(&file, sendFile)) return;

    // Prepare transmission of new file.
    EnableFpga();
    spi8(SHARPMZ_FILE_ADDR_TX);
    spi8(0x00);
    spi8(image.loadAddr >> 16);                   // Indicate we are uploading rom images into the correct ROM section of the memory.
    spi8((image.loadAddr >> 8) & 0xff);           // MSB of the rom image.
    spi8(image.loadAddr&0xff);                    // LSB of the rom image.
    sharpmz_debugf("Load Address:%04x, %04x, %04x\n", image.loadAddr, image.loadAddr >> 8, image.loadSize);

    // Limit size to that governed in the config. If the rom is larger than the defined size, waste additional data,
    // if less then finish early. Prevents large images from corrupting other machine roms.
    //
    sharpmz_debugf("[");
    for (i = 0; i < image.loadSize; i += actualReadSize)
    {
		if (!(i & 127)) { sharpmz_debugf("*"); }

        // Work out size of data block to read.
        int readSize = (image.loadSize - i >= 512 ? 512 : image.loadSize - i);

        // Perform the read.
        DISKLED_ON;
        actualReadSize = sharpmz_file_read(&file, sector_buffer, readSize);
        DISKLED_OFF;

        // If we encountered an error, then bad file or eof, so exit.
        //
        if(actualReadSize != 0)
        {
            spi_write(sector_buffer, actualReadSize, 0);

        } else
        {
            // End of file, short file, so just move onto end.
            actualReadSize = image.loadSize -i;
        }
    }
    DisableFpga();
    sharpmz_debugf("]\n");

    // Tidy up.
    FileClose(&file);

    // For debug, display time it took to upload.
    time = GetTimer(0) - time;
    sharpmz_debugf("Uploaded in %lu ms", time >> 20);

    // signal end of transmission
    EnableFpga();
    spi8(SHARPMZ_FILE_TX);
    spi8(SHARPMZ_EOF);
    DisableFpga();
}

// Method to read the emulator memory. Either a specific bank is given or all banks via the parameter bank.
//
short sharpmz_read_ram(const char *memDumpFile, short bank)
{
    unsigned int  actualWriteSize;
    fileTYPE      file = {};

    // Open the memory image debug file for writing.
    if (!sharpmz_file_write(&file, memDumpFile))
    {
        sharpmz_debugf("Failed to open ram dump file:%s\n", memDumpFile);
        return(3);
    }

    // Depending on the bank, or all banks, loop until request is complete.
    //
    for(unsigned int mb=(bank == SHARPMZ_MEMBANK_ALL ? 0 : bank); mb <= (unsigned int)(bank == SHARPMZ_MEMBANK_ALL ? SHARPMZ_MEMBANK_MAXBANKS-1 : bank); mb++)
    {
        // Skip bank 1, as SYSROM spans two physical banks 0 and 1.
        if(mb == 1) mb = SHARPMZ_MEMBANK_SYSRAM;

        EnableFpga();                                     // Setup the load address.
        spi8(SHARPMZ_FILE_ADDR_RX);
        spi8(0x00);
        spi8(mb);                                         // Memory bank to read.
        spi8(0x00);                                       // A15-A8
        spi8(0x00);                                       // A7-A0
        //spi8(0x00);                                       // Setup to read addr first byte.

        // The bank size is stored in the config.
        //
        sharpmz_debugf("[");
        for (unsigned long j = 0; j < MZBANKSIZE[mb] or actualWriteSize == 0; j += actualWriteSize)
        {
			if (!(j & 127)) { sharpmz_debugf("*"); }

            spi_read(sector_buffer, 512, 0);

            DISKLED_ON;
            actualWriteSize=FileWriteAdv(&file, sector_buffer, 512);
            DISKLED_OFF;
        }
        sharpmz_debugf("]\n");

        // Indicate end of upload.
        spi8(SHARPMZ_EOF);
        DisableFpga();
    }

    // Close file to complete dump.
    FileClose(&file);

    // Success.
    //
    return(0);
}

// Method to read the header of a tape.
// Useful for decision making prior to load.
//
short sharpmz_read_tape_header(const char *tapeFile)
{
    unsigned int  actualReadSize;

    //sharpmz_debugf("Sending tape file:%s to emulator ram", tapeFile);

    // Handle for the MZF file to be processed.
    //
    fileTYPE file = {};

    // Try and open the tape file, exit if it cannot be opened.
    //
    if (!FileOpen(&file, tapeFile)) return(1);

    // Read in the tape header, this indicates crucial data such as data type, size, exec address, load address etc.
    //
    actualReadSize = sharpmz_file_read(&file, &tapeHeader, 128);
    if(actualReadSize != 128)
    {
        sharpmz_debugf("Only read:%d bytes of header, aborting.\n", actualReadSize);
        return(1);
    }

    // Some sanity checks.
    //
    if(tapeHeader.dataType == 0 || tapeHeader.dataType > 5) return(4);

    // Success.
    return(0);
}

// Method to load a tape (MZF) file directly into RAM.
// This involves reading the tape header, extracting the size and destination and loading
// the header and program into emulator ram.
//
short sharpmz_load_tape_to_ram(const char *tapeFile, unsigned char dstCMT)
{
    unsigned int  actualReadSize;
    unsigned long time = GetTimer(0);
    //char          fileName[17];

    //sharpmz_debugf("Sending tape file:%s to emulator ram", tapeFile);

    // Handle for the MZF file to be processed.
    //
    fileTYPE file = {};

    // Try and open the tape file, exit if it cannot be opened.
    //
    if (!FileOpen(&file, tapeFile)) return(1);

    // Read in the tape header, this indicates crucial data such as data type, size, exec address, load address etc.
    //
    actualReadSize = sharpmz_file_read(&file, &tapeHeader, 128);
    if(actualReadSize != 128)
    {
        sharpmz_debugf("Only read:%d bytes of header, aborting.\n", actualReadSize);
        return(2);
    }

    // Some sanity checks.
    //
    if(tapeHeader.dataType == 0 || tapeHeader.dataType > 5) return(4);
	/*
    for(int i=0; i < 17; i++)
    {
        fileName[i] = tapeHeader.fileName[i] == 0x0d ? 0x00 : tapeHeader.fileName[i];
    }
	*/

    // Debug output to indicate the file loaded and information about the tape image.
    //
    switch(tapeHeader.dataType)
    {
        case 0x01:
            sharpmz_debugf("Binary File(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s, StackLow=%04x, StackHigh=%04x)\n", tapeHeader.loadAddress, tapeHeader.fileSize, tapeHeader.execAddress, fileName, MZ_TAPE_HEADER_STACK_ADDR & 0xff, (MZ_TAPE_HEADER_STACK_ADDR >> 8) & 0xff);
            break;

        case 0x02:
            sharpmz_debugf("MZ-80 Basic Program(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", tapeHeader.loadAddress, tapeHeader.fileSize, tapeHeader.execAddress, fileName);
            break;

        case 0x03:
            sharpmz_debugf("MZ-80 Data File(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", tapeHeader.loadAddress, tapeHeader.fileSize, tapeHeader.execAddress, fileName);
            break;

        case 0x04:
            sharpmz_debugf("MZ-700 Data File(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", tapeHeader.loadAddress, tapeHeader.fileSize, tapeHeader.execAddress, fileName);
            break;

        case 0x05:
            sharpmz_debugf("MZ-700 Basic Program(Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", tapeHeader.loadAddress, tapeHeader.fileSize, tapeHeader.execAddress, fileName);
            break;

        default:
            sharpmz_debugf("Unknown tape type(Type=%02x, Load Addr=%04x, Size=%04x, Exec Addr=%04x, FileName=%s)\n", tapeHeader.dataType, tapeHeader.loadAddress, tapeHeader.fileSize, tapeHeader.execAddress, fileName);
            break;
    }

    // Check the data type, only load machine code.
    //
    if(dstCMT == 0 && tapeHeader.dataType != SHARPMZ_CMT_MC)
        return(3);

    // Reset Emulator if loading direct to RAM. This clears out memory, resets monitor and places it in a known state.
    //
    if(dstCMT == 0)
        sharpmz_reset(10, 50000);

    // Load the data from tape to RAM.
    //
    EnableFpga();                                         // Start the transmission session, clear address and set ioctl_download.
    spi8(SHARPMZ_FILE_ADDR_TX);
    spi8(0x00);
    if(dstCMT == 0)                                       // Load to emulators RAM
    {
        spi8(SHARPMZ_MEMBANK_SYSRAM);
        spi8((tapeHeader.loadAddress >> 8)&0xff);
        spi8(tapeHeader.loadAddress & 0xff);              // Location set inside tape header structure.
    } else
    {
        spi8(SHARPMZ_MEMBANK_CMT_DATA);
        spi8(0x00);
        spi8(0x00);
    }

    sharpmz_debugf("[");
    for (unsigned short i = 0; i < tapeHeader.fileSize; i += actualReadSize)
    {
		if (!(i & 127)) { sharpmz_debugf("*"); }

        DISKLED_ON;
        actualReadSize = sharpmz_file_read(&file, sector_buffer, 512);
        DISKLED_OFF;

        // First sector contains the header which has already been written, so just write the remainder.
        //
        if(i == 0)
        {
            actualReadSize -= MZ_TAPE_HEADER_SIZE;
            memmove(sector_buffer, sector_buffer+MZ_TAPE_HEADER_SIZE, actualReadSize);
        }
        //sharpmz_debugf("Bytes to read, actual:%d, index:%d, sizeHeader:%d", actualReadSize, i, tapeHeader.fileSize);
        if(actualReadSize > 0)
        {
            // Write the sector (or part) to the fpga memory.
            spi_write(sector_buffer, actualReadSize, 0);
        }
    }
    sharpmz_debugf("]\n");
    DisableFpga();

    // signal end of transmission
    EnableFpga();
    spi8(SHARPMZ_FILE_TX);
    spi8(SHARPMZ_EOF);
    DisableFpga();

    // Now load header - this is done last because the emulator monitor wipes the stack area on reset.
    //
    EnableFpga();                                         // Start the transmission session, clear address and set ioctl_download.
    spi8(SHARPMZ_FILE_ADDR_TX);
    spi8(0x00);                                           // Tape header position address: Bit 24
    if(dstCMT == 0)                                       // Load to emulators RAM
    {
        spi8(SHARPMZ_MEMBANK_SYSRAM);                     // Bits 23:16
        spi8((MZ_TAPE_HEADER_STACK_ADDR >> 8) & 0xff);    // Bits 15:8
        spi8(MZ_TAPE_HEADER_STACK_ADDR & 0xff);           // Bits 7:0
    } else
    {
        spi8(SHARPMZ_MEMBANK_CMT_HDR);                    // Bits 23:16
        spi8(0x00);                                       // Bits 15:8
        spi8(0x00);                                       // Bits 7:0
    }
    //
    spi_write((unsigned char *)&tapeHeader, MZ_TAPE_HEADER_SIZE, 0);
    //
    DisableFpga();
    EnableFpga();                                         // Finally indicate end of transmission.
    spi8(SHARPMZ_FILE_TX);
    spi8(SHARPMZ_EOF);
    DisableFpga();

    time = GetTimer(0) - time;
    sharpmz_debugf("Uploaded in %lu ms", time >> 20);

    // Debug, show registers.
    for(unsigned int i=0; i < MAX_REGISTERS; i++)
    {
        sharpmz_debugf("Register (%02x) = %02x", i, sharpmz_read_config_register(i));
    }

    // Tidy up.
    FileClose(&file);

    // Dump out the memory if needed (generally for debug purposes).
    if(dstCMT == 0)                                       // Load to emulators RAM
    {
        sharpmz_read_ram((const char *)"memory.dump", SHARPMZ_MEMBANK_SYSRAM);
    } else
    {
        sharpmz_read_ram((const char *)"cmt_header.dump", SHARPMZ_MEMBANK_CMT_HDR);
        sharpmz_read_ram((const char *)"cmt_data.dump", SHARPMZ_MEMBANK_CMT_DATA);
    }

    // Remove the LF from the header filename, not needed.
    //
    for(int i=0; i < 17; i++)
    {
        if(tapeHeader.fileName[i] == 0x0d) tapeHeader.fileName[i] = 0x00;
    }

    // Debug, show registers.
    for(unsigned int i=0; i < MAX_REGISTERS; i++)
    {
        sharpmz_debugf("Register (%02x) = %02x", i, sharpmz_read_config_register(i));
    }

    // Success.
    //
    return(0);
}

// Method to save the contents of the CMT buffer onto a disk based MZF file.
// The method reads the header, writes it and then reads the data (upto size specified in header) and write it.
//
short sharpmz_save_tape_from_cmt(const char *tapeFile)
{
    short          dataSize = 0;
    unsigned short actualWriteSize;
    unsigned short writeSize;
    char           fileName[17];

    // Handle for the MZF file to be written.
    //
    fileTYPE file = {};

    // Read the header, then data, but limit data size to the 'file size' stored in the header.
    //
    for(unsigned int mb=SHARPMZ_MEMBANK_CMT_HDR; mb <= SHARPMZ_MEMBANK_CMT_DATA; mb++)
    {
        EnableFpga();                                     // Setup the load address.
        spi8(SHARPMZ_FILE_ADDR_RX);
        spi8(0x00);
        spi8(mb);                                         // Memory bank to read.
        spi8(0x00);                                       // A15-A8
        spi8(0x00);                                       // A7-A0
        spi8(0x00);                                       // Setup to read addr first byte.

        // The bank size is stored in the config.
        //
        if(mb == SHARPMZ_MEMBANK_CMT_HDR)
        {
            dataSize = MZ_TAPE_HEADER_SIZE;
        } else
        {
            dataSize = tapeHeader.fileSize;
        }
        sharpmz_debugf("mb=%d, tapesize=%04x\n", mb, tapeHeader.fileSize);
        for (; dataSize > 0; dataSize -= actualWriteSize)
        {
            sharpmz_debugf("mb=%d, dataSize=%04x, writeSize=%04x\n", mb, dataSize, writeSize);
            if(mb == SHARPMZ_MEMBANK_CMT_HDR)
            {
                writeSize = MZ_TAPE_HEADER_SIZE;
            } else
            {
                writeSize = dataSize > 512 ? 512 : dataSize;
            }
            spi_read(sector_buffer, writeSize, 0);
            if(mb == SHARPMZ_MEMBANK_CMT_HDR)
            {
                memcpy(&tapeHeader, &sector_buffer, MZ_TAPE_HEADER_SIZE);

                // Now open the file for writing. If no name provided, use the one stored in the header.
                //
                if(tapeFile == 0)
                {
                    for(int i=0; i < 17; i++)
                    {
                        fileName[i] = tapeHeader.fileName[i] == 0x0d ? 0x00 : fileName[i];
                    }
                } else
                {
                    memcpy(fileName, tapeHeader.fileName, 17);
                }

                // Open the memory image debug file for writing.
                if (!sharpmz_file_write(&file, fileName))
                {
                    sharpmz_debugf("Failed to open tape file:%s\n", fileName);
                    return(3);
                }
            }
            DISKLED_ON;
            actualWriteSize=FileWriteAdv(&file, sector_buffer, writeSize);
            DISKLED_OFF;
        }

        // Indicate end of upload.
        spi8(SHARPMZ_EOF);
        DisableFpga();
    }

    // Close file to complete dump.
    FileClose(&file);

    return(0);
}

// Function from menu.cpp located here due to its static definition. Unneeded parts stripped out but otherwise it performs same
// actions.
//
void sharpmz_select_file(const char* pFileExt, unsigned char Options, char *fs_pFileExt, char chdir, char *SelectedPath)
{
    sharpmz_debugf("pFileExt = %s\n", pFileExt);
	(void)chdir;

    if (strncasecmp(SHARPMZ_CORE_NAME, SelectedPath, strlen(SHARPMZ_CORE_NAME))) strcpy(SelectedPath, SHARPMZ_CORE_NAME);

    ScanDirectory(SelectedPath, SCANF_INIT, pFileExt, Options);
    if (!flist_nDirEntries())
    {
            SelectedPath[0] = 0;
            ScanDirectory(SelectedPath, SCANF_INIT, pFileExt, Options);
    }

    AdjustDirectory(SelectedPath);
    strcpy(fs_pFileExt, pFileExt);
}

// Method to return the default UI state.
//
int sharpmz_default_ui_state(void)
{
    return(MENU_SHARPMZ_MAIN1);
}

// User interface for the SharpMZ Series emulator. This functionality has been located here to minimise changes on common
// code base and to limit common code case size.
// The same general programming structure is maintained to enable easier changes.
//
void sharpmz_ui(int      idleState,    int      idle2State,    int        systemState,    int      selectFile,
                uint32_t *parentstate, uint32_t *menustate,    uint32_t   *menusub,       uint32_t *menusub_last,
				uint64_t *menumask,    char     *selectedPath, const char **helptext,     char     *helptext_custom,
				uint32_t *fs_ExtLen,   uint32_t *fs_Options,   uint32_t   *fs_MenuSelect, uint32_t *fs_MenuCancel,
                char     *fs_pFileExt,
                unsigned char menu,    unsigned char select,   unsigned char up,          unsigned char down,
                unsigned char left,    unsigned char right,    unsigned char plus,        unsigned char minus)
{
    // Locals - original variables are generally all lower case, variables specific to this method are capitalised on change of word.
    //
    static short romType;
    static short machineModel             = sharpmz_get_next_machine_model();
    static short debugCPUFrequency        = sharpmz_get_next_debug_cpufreq();
    static short debugLedsBank            = sharpmz_get_next_debug_leds_bank();
    static short debugLedsSampleFrequency = sharpmz_get_next_debug_leds_smpfreq();
    static short memoryBank               = sharpmz_get_next_memory_bank();
    static short fileDumped               = 0;
    static short scrollPos                = 0;
    static short volumeDir                = 0;
    int          menuItem;
	uint32_t     subItem;
    short        romEnabled;
    short        itemCount;
    char         sBuf[40];
    char        *fileName;

	(void)plus;
	(void)minus;

    // Idle2 state (MENU_NONE2) is our main hook, when the HandleUI state machine reaches this state, if the menu key is pressed,
    // we takeover control in this method.
    //
    if(*menustate == (uint32_t)idle2State && menu)
    {
        OsdSetSize(16);
        *menusub = 0;
        OsdClear();
        OsdEnable(DISABLE_KEYBOARD);
        *menustate = MENU_SHARPMZ_MAIN1;
    }

    // The menustate originates in the HandleUI method, if a value is not recognised in the main switch statement in HandleUI then
    // it is ignored. This method utilises this fact and operates on a set of states outside those used in HandleUI.
    //
    switch(*menustate)
    {
        /******************************************************************/
        /* SharpMZ core menu                                              */
        /******************************************************************/

        case MENU_SHARPMZ_MAIN1:

            menuItem  = 0;
            subItem   = 0;
            *menumask = 0;

            OsdSetTitle(user_io_get_core_name(), 0);

            OsdWrite(menuItem++, "          Main Menu", 0, 0);
            OsdWrite(menuItem++, "",                    0, 0);

            OsdWrite(menuItem++, " Tape Storage              \x16", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            // Not yet implemented.
            //OsdWrite(menuItem++, " Floppy Storage            \x16", *menusub == subItem++, 0);
            //*menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, " Machine                   \x16", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, " Display                   \x16", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, " Debug                     \x16", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, " System                    \x16", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            for (; menuItem < 10; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }

            OsdWrite(menuItem++, " Reset",  *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;
            OsdWrite(menuItem++, " Reload config", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;
            OsdWrite(menuItem++, " Save   config", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;
            OsdWrite(menuItem++, " Reset  config", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            for (; menuItem < 15; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }

            OsdWrite(menuItem++, "",                    0, 0);
            OsdWrite(menuItem, "            exit", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            // Set menu states, parent is the root menu, next is the processing menu 2.
            //
            *parentstate = MENU_SHARPMZ_MAIN1;
            *menustate = MENU_SHARPMZ_MAIN2;

            // set helptext with core display on top of basic info
            sprintf(helptext_custom, "                                ");
            strcat(helptext_custom, OsdCoreNameGet());
            strcat(helptext_custom, "                                ");
            strcat(helptext_custom, SHARPMZ_HELPTEXT[0]);
            *helptext = helptext_custom;
            break;

        case MENU_SHARPMZ_MAIN2:
            // menu key closes menu
            if (menu)
                *menustate = idleState;

            if (select || right)
            {
                switch (*menusub)
                {
                case 0:  // Tape Storage
                    *menustate    = MENU_SHARPMZ_TAPE_STORAGE1;
                    *menusub_last = *menusub;
                    *menusub      = 0;
                    if(right) select = true;
                    break;

                //case 1:  // Floppy Storage
                //    *menustate    = MENU_SHARPMZ_FLOPPY_STORAGE1;
                //    *menusub_last = *menusub;
                //    *menusub      = 0;
                //    if(right) select = true;
                //    break;

                case 1:  // Machine
                    *menustate    = MENU_SHARPMZ_MACHINE1;
                    *menusub_last = *menusub;
                    *menusub      = 0;
                    if(right) select = true;
                    break;

                case 2:  // Display
                    *menustate    = MENU_SHARPMZ_DISPLAY1;
                    *menusub_last = *menusub;
                    *menusub      = 0;
                    if(right) select = true;
                    break;

                case 3:  // Debug
                    *menustate    = MENU_SHARPMZ_DEBUG1;
                    *menusub_last = *menusub;
                    *menusub      = 0;
                    if(right) select = true;
                    break;

                case 4:  // System
                    *menustate    = systemState;
                    *menusub_last = *menusub;
                    *menusub      = 0;
                    if(right) select = true;
                    break;

                case 5: // Reset Machine
                    if(select)
                    {
                        sharpmz_init();
                        *menustate = idleState;
                    }
                    break;

                case 6:  // Reload Settings
                    if(select)
                    {
                        sharpmz_reload_config(1);
                        *menustate = MENU_SHARPMZ_MAIN1;
                    }
                    break;

                case 7:  // Save Settings
                    if(select)
                    {
                        sharpmz_save_config();
                        *menustate = MENU_SHARPMZ_MAIN1;
                    }
                    break;

                case 8:  // Reset Settings
                    if(select)
                    {
                        sharpmz_reset_config(1);
                        *menustate = MENU_SHARPMZ_MAIN1;
                    }
                    break;

                case 9:  // Exit
                    *menustate = idleState;
                    if(right) select = true;
                    break;
                }
            }
            break;

        case MENU_SHARPMZ_TAPE_STORAGE1:
            menuItem  = 0;
            subItem   = 0;
            *menumask = 0;

            OsdSetTitle("Tape Storage", OSD_ARROW_LEFT);

            OsdWrite(menuItem++, "", 0, 0);

            OsdWrite(menuItem++, " Load direct to RAM:   *.MZF", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, " Queue Tape:           *.MZF", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, " Clear Queue                ", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            // List out the current tape queue.
            itemCount = 0;
            while((fileName = sharpmz_get_next_filename(0)) != 0)
            {
                sprintf(sBuf, "  %d> %s", itemCount++, fileName);
                OsdWrite(menuItem++, sBuf, 0, 0);
            }

            OsdWrite(menuItem++, "", 0, 0);

            OsdWrite(menuItem++, " Save Tape:            *.MZF", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Auto Save Tape:       ");
            strcat(sBuf, sharpmz_get_auto_save_enabled_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, "", 0, 0);

            strcpy(sBuf, " Tape Buttons:         ");
            strcat(sBuf, sharpmz_get_tape_buttons_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Fast Tape Load:       ");
            strcat(sBuf, sharpmz_get_fasttape_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            for (; menuItem < 15; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }

            OsdWrite(menuItem, "            exit", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            *parentstate = *menustate;
            *menustate = MENU_SHARPMZ_TAPE_STORAGE2;
            break;


        case MENU_SHARPMZ_TAPE_STORAGE2:
            if (menu) {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub = *menusub_last;
            }
            if (select)
            {
                *menusub_last = *menusub;
                switch(*menusub)
                {
                // Load direct to RAM? This involves parsing the MZF header, copying the header (128 bytes) to 0x10F0 in the Z80 memory range
                // then loading the remainder into the location specified (position 20) of size (position 18).
                //
                // Load from Tape? This involves sending the whole file to the emulator CMT buffer, it then plays the file into the
                // emulator as pseudo PWM tape.
                //
                case 0:
                case 1:
                    *fs_Options    = SCANO_DIR;
                    sharpmz_select_file("MZFmzfMZTmzt", *fs_Options, fs_pFileExt, 1, selectedPath);
                    //sharpmz_select_file("MZFmzf", *fs_Options, fs_pFileExt, 1, selectedPath);
                    *fs_ExtLen     = strlen(fs_pFileExt);
                    *fs_MenuSelect = (*menusub == 0 ? MENU_SHARPMZ_TAPE_STORAGE_LOAD_TAPE_TO_RAM : MENU_SHARPMZ_TAPE_STORAGE_QUEUE_TAPE_TO_CMT);
                    *fs_MenuCancel = MENU_SHARPMZ_MAIN1;
                    *menustate     = selectFile;
                    break;

                case 2:
                    sharpmz_clear_filelist();
                    *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
                    break;

                case 3:
                    *menustate = MENU_SHARPMZ_TAPE_STORAGE_SAVE_TAPE_FROM_CMT;
                    break;

                case 4:
                    sharpmz_set_auto_save_enabled(sharpmz_get_auto_save_enabled() == 0 ? 1 : 0);
                    *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
                    break;

                case 5:
                    sharpmz_set_tape_buttons(sharpmz_get_tape_buttons()+1, 1);
                    *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
                    break;

                case 6:
                    sharpmz_set_fasttape(sharpmz_get_fasttape()+1, 1);
                    *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
                    break;

                case 7:
                    *menustate = MENU_SHARPMZ_MAIN1;
                    *menusub   = *menusub_last;
                    break;
                }
            }
            if (left)
            {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub   = *menusub_last;
            }
            break;

        case MENU_SHARPMZ_TAPE_STORAGE_LOAD_TAPE_TO_RAM:
            sharpmz_debugf("File selected to send to RAM: %s, for menu option:%04x\n", selectedPath, *menumask);
            *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
            if(selectedPath)
            {
                int fail=sharpmz_load_tape_to_ram(selectedPath, 0);
                sharpmz_tape_header_t *tapeHeader = sharpmz_get_tape_header();

                if(!fail)
                    OsdSetTitle("Tape Details", OSD_ARROW_LEFT);
                else
                    OsdSetTitle(" Tape Error", OSD_ARROW_LEFT);

                *menumask = 0x01;    // Exit.
                *parentstate = *menustate;
                menuItem = 0;

                if(!fail)
                    OsdWrite(menuItem++, "       Tape Details", 0, 0);
                else
                    OsdWrite(menuItem++, "        Tape Error", 0, 0);
                OsdWrite(menuItem++, "", 0, 0);
                sprintf(sBuf, " File Size: %04x", tapeHeader->fileSize);
                OsdWrite(menuItem++, sBuf,              0, 0);
                strcpy(sBuf, " File Type: ");
                strcat(sBuf, sharpmz_get_tape_type_string());
                OsdWrite(menuItem++, sBuf,              0, 0);
                strcpy(sBuf, " File Name: ");
                strcat(sBuf, tapeHeader->fileName);
                OsdWrite(menuItem++, sBuf,              0, 0);
                sprintf(sBuf, " Load Addr: %04x", tapeHeader->loadAddress);
                OsdWrite(menuItem++, sBuf,              0, 0);
                sprintf(sBuf, " Exec Addr: %04x", tapeHeader->execAddress);
                OsdWrite(menuItem++, sBuf,              0, 0);
                OsdWrite(menuItem++, "", 0, 0);

                if(!fail)
                    OsdWrite(menuItem++, "", 0, 0);
                else
                {
                    switch(fail)
                    {
                        case 1:
                            strcpy(sBuf, " Unable to open file!");
                            break;
                        case 2:
                            strcpy(sBuf, " Unable to read header!");
                            break;
                        case 3:
                            strcpy(sBuf, " Not a m/code Program!");
                            break;
                        case 4:
                            strcpy(sBuf, " Not a valid tape!");
                            break;
                    }
                    OsdWrite(menuItem++, sBuf, 0, 0);
                }
                for (; menuItem < OsdGetSize()-1; menuItem++) OsdWrite(menuItem, "", 0, 0);
                OsdWrite(menuItem, "            exit", true, 0);
                *menustate = MENU_SHARPMZ_TAPE_STORAGE_LOAD_TAPE_TO_RAM2;
            }
            break;

        case MENU_SHARPMZ_TAPE_STORAGE_LOAD_TAPE_TO_RAM2:
            if (menu || select || left || up)
            {
                *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
                *menusub = *menusub_last;
            }
            break;

        case MENU_SHARPMZ_TAPE_STORAGE_QUEUE_TAPE_TO_CMT:
            sharpmz_debugf("File added to Queue: %s, for menu option:%04x\n", selectedPath, *menumask);
            *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
            if(selectedPath)
            {
                // Read in the header of the queued tape file.
                //
                int fail=sharpmz_read_tape_header(selectedPath);
                sharpmz_tape_header_t *tapeHeader = sharpmz_get_tape_header();

                // Limit number of items in queue, makes no sense to have too many and we run out of display space.
                //
                if(tapeQueue.elements < 5 && !fail)
                {
                    sharpmz_push_filename(selectedPath);
                    OsdSetTitle("Tape Queued", OSD_ARROW_LEFT);
                } else
                {
                    OsdSetTitle("Queue Error", OSD_ARROW_LEFT);
                }

                *menumask = 0x01;    // Exit.
                *parentstate = *menustate;
                menuItem = 0;

                if(!fail)
                {
                    OsdWrite(menuItem++, "       Tape Details", 0, 0);
                    OsdWrite(menuItem++, "", 0, 0);
                    sprintf(sBuf, " File Size: %04x", tapeHeader->fileSize);
                    OsdWrite(menuItem++, sBuf,              0, 0);
                    strcpy(sBuf, " File Type: ");
                    strcat(sBuf, sharpmz_get_tape_type_string());
                    OsdWrite(menuItem++, sBuf,              0, 0);
                    strcpy(sBuf, " File Name: ");
                    strcat(sBuf, tapeHeader->fileName);
                    OsdWrite(menuItem++, sBuf,              0, 0);
                    sprintf(sBuf, " Load Addr: %04x", tapeHeader->loadAddress);
                    OsdWrite(menuItem++, sBuf,              0, 0);
                    sprintf(sBuf, " Exec Addr: %04x", tapeHeader->execAddress);
                    OsdWrite(menuItem++, sBuf,              0, 0);
                    OsdWrite(menuItem++, "", 0, 0);
                    OsdWrite(menuItem++, "", 0, 0);
                } else
                {
                    OsdWrite(menuItem++, "        Queue Error", 0, 0);
                    OsdWrite(menuItem++, "", 0, 0);

                    if(!fail)
                        strcpy(sBuf, " Queue limit reached!");
                    else
                    {
                        switch(fail)
                        {
                            case 1:
                                strcpy(sBuf, " Unable to open file!");
                                break;
                            case 2:
                                strcpy(sBuf, " Unable to read header!");
                                break;
                            case 3:
                                strcpy(sBuf, " Not a m/code Program!");
                                break;
                            case 4:
                                strcpy(sBuf, " Not a valid tape!");
                                break;
                        }
                    }

                    OsdWrite(menuItem++, sBuf, 0, 0);
                }
                for (; menuItem < OsdGetSize()-1; menuItem++) OsdWrite(menuItem, "", 0, 0);
                OsdWrite(menuItem, "            exit", true, 0);
                *menustate = MENU_SHARPMZ_TAPE_STORAGE_QUEUE_TAPE_TO_CMT2;
            }
            break;

        case MENU_SHARPMZ_TAPE_STORAGE_QUEUE_TAPE_TO_CMT2:
            if (menu || select || left || up)
            {
                *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
                *menusub = *menusub_last;
            }
            break;

        case MENU_SHARPMZ_TAPE_STORAGE_SAVE_TAPE_FROM_CMT:
            sharpmz_save_tape_from_cmt((const char *)0);
            *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
            break;

        case MENU_SHARPMZ_TAPE_STORAGE_SAVE_TAPE_FROM_CMT2:
            *menustate = MENU_SHARPMZ_TAPE_STORAGE1;
            break;

        // Floppy Storage
        case MENU_SHARPMZ_FLOPPY_STORAGE1:
            menuItem  = 0;
            subItem   = 0;
            *menumask = 0;

            OsdSetTitle("Floppy Storage", OSD_ARROW_LEFT);

            OsdWrite(menuItem++, "", 0, 0);

            for (; menuItem < 15; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }

            OsdWrite(menuItem, "            exit", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            *parentstate = *menustate;
            *menustate = MENU_SHARPMZ_FLOPPY_STORAGE2;
            break;

        case MENU_SHARPMZ_FLOPPY_STORAGE2:
            if (menu) {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub = *menusub_last;
            }
            if (select)
            {
                *menusub_last = *menusub;
                switch(*menusub)
                {
                case 0:
                    break;

                case 1:
                    *menustate = MENU_SHARPMZ_MAIN1;
                    *menusub   = *menusub_last;
                    break;
                }
            }
            if (left)
            {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub   = *menusub_last;
            }
            break;

        // Machine configuration.
        case MENU_SHARPMZ_MACHINE1:
            menuItem  = 0;
            subItem   = 0;
            *menumask = 0;

            OsdSetTitle("Machine", OSD_ARROW_LEFT);

            OsdWrite(menuItem++, "", 0, 0);

            strcpy(sBuf, " Machine Model:      ");
            strcat(sBuf, sharpmz_get_machine_model_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " CPU Speed:          ");
            strcat(sBuf, sharpmz_get_cpu_speed_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, "", 0, 0);

            strcpy(sBuf, " Audio Source:       ");
            strcat(sBuf, sharpmz_get_audio_source_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Audio Volume:       ");
            strcat(sBuf, sharpmz_get_audio_volume_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Audio Mute:         ");
            strcat(sBuf, sharpmz_get_audio_mute_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, "", 0, 0);
            OsdWrite(menuItem++, " Rom Management            \x16", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            for (; menuItem < 15; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }

            OsdWrite(menuItem, "            exit", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            *parentstate = *menustate;
            *menustate = MENU_SHARPMZ_MACHINE2;
            break;


        case MENU_SHARPMZ_MACHINE2:
            if (menu) {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub = *menusub_last;
            }
            if (select)
            {
                switch(*menusub)
                {
                case 0:  // Machine Model
                    //sharpmz_set_machine_model(sharpmz_get_machine_model() + 1, 1);
                    sharpmz_set_machine_model(sharpmz_get_machine_model() + 1, 1);
                    *menustate = MENU_SHARPMZ_MACHINE1;
                    break;

                case 1:  // CPU Speed
                    sharpmz_set_cpu_speed((sharpmz_get_cpu_speed() + 1) & 0x07, 1);
                    *menustate = MENU_SHARPMZ_MACHINE1;
                    break;

                case 2:  // Audio Source
                    sharpmz_set_audio_source(sharpmz_get_audio_source() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_MACHINE1;
                    break;

                case 3:  // Audio Volume
                    sharpmz_set_audio_volume((sharpmz_get_audio_volume() + (volumeDir ? -1 : +1)) & 0x0f, 1);
                    if(sharpmz_get_audio_volume() == 15) volumeDir = 1;
                    if(sharpmz_get_audio_volume() == 0)  volumeDir = 0;
                    *menustate = MENU_SHARPMZ_MACHINE1;
                    break;

                case 4:  // Audio Mute
                    sharpmz_set_audio_mute(sharpmz_get_audio_mute() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_MACHINE1;
                    break;

                case 5:  // Roms
                    *menustate    = MENU_SHARPMZ_ROMS1;
                    *menusub_last = *menusub;
                    *menusub      = 0;
                    if(right) select = true;
                    break;

                case 6:  // Exit
                    *menustate = MENU_SHARPMZ_MAIN1;
                    *menusub   = 1; //*menusub_last;
                    break;
                }
            }
            if (left)
            {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub = 1; //*menusub_last;
            }
            break;

        case MENU_SHARPMZ_ROMS1:

            if(*menusub == 0)
            {
                scrollPos = 0;
            }
            if(down && *menusub  > 9  && scrollPos < 3) { scrollPos++; (*menusub)--; }
            if(up   && *menusub  < 10 && scrollPos > 0) { scrollPos--; (*menusub)++; }

            menuItem  = 0;
            subItem   = 0;
            *menumask = 0;

            OsdSetTitle("Rom Management", OSD_ARROW_LEFT);

            OsdWrite(menuItem++, "", 0, 0);

            if(scrollPos == 0)
            {
                strcpy(sBuf, " Machine Model:      ");
                strcat(sBuf, sharpmz_get_machine_model_string(machineModel));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            if(scrollPos <= 1)
            {
                strcpy(sBuf, " User ROM:           ");
                strcat(sBuf, sharpmz_get_user_rom_enabled_string(machineModel));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            if(scrollPos <= 2)
            {
                strcpy(sBuf, " Floppy Disk ROM:    ");
                strcat(sBuf, sharpmz_get_fdc_rom_enabled_string(machineModel));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
                OsdWrite(menuItem++, "", 0, 0);
            }

            OsdWrite(menuItem++, " Enable Custom Rom:  ", 0, 0);
            romEnabled = sharpmz_get_custom_rom_enabled(machineModel, MROM_IDX);
            strcpy(sBuf, "   Monitor (40x25)   ");
            strcat(sBuf, sharpmz_get_custom_rom_enabled_string(romEnabled));
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            if(romEnabled)
            {
                strcpy(sBuf, "    \x16 ");
                strcat(sBuf, sharpmz_get_rom_file(machineModel, MROM_IDX));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            romEnabled = sharpmz_get_custom_rom_enabled(machineModel, MROM_80C_IDX);
            strcpy(sBuf, "   Monitor (80x25)   ");
            strcat(sBuf, sharpmz_get_custom_rom_enabled_string(romEnabled));
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            if(romEnabled)
            {
                strcpy(sBuf, "    \x16 ");
                strcat(sBuf, sharpmz_get_rom_file(machineModel, MROM_80C_IDX));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            romEnabled = sharpmz_get_custom_rom_enabled(machineModel, CGROM_IDX);
            strcpy(sBuf, "   Char Generator    ");
            strcat(sBuf, sharpmz_get_custom_rom_enabled_string(romEnabled));
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            if(romEnabled)
            {
                strcpy(sBuf, "    \x16 ");
                strcat(sBuf, sharpmz_get_rom_file(machineModel, CGROM_IDX));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            romEnabled = sharpmz_get_custom_rom_enabled(machineModel, KEYMAP_IDX);
            strcpy(sBuf, "   Key Mapping       ");
            strcat(sBuf, sharpmz_get_custom_rom_enabled_string(romEnabled));
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            if(romEnabled)
            {
                strcpy(sBuf, "    \x16 ");
                strcat(sBuf, sharpmz_get_rom_file(machineModel, KEYMAP_IDX));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            romEnabled = sharpmz_get_custom_rom_enabled(machineModel, USERROM_IDX);
            strcpy(sBuf, "   User ROM          ");
            strcat(sBuf, sharpmz_get_custom_rom_enabled_string(romEnabled));
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            if(romEnabled)
            {
                strcpy(sBuf, "    \x16 ");
                strcat(sBuf, sharpmz_get_rom_file(machineModel, USERROM_IDX));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            romEnabled = sharpmz_get_custom_rom_enabled(machineModel, FDCROM_IDX);
            strcpy(sBuf, "   Floppy Disk       ");
            strcat(sBuf, sharpmz_get_custom_rom_enabled_string(romEnabled));
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            if(romEnabled)
            {
                strcpy(sBuf, "    \x16 ");
                strcat(sBuf, sharpmz_get_rom_file(machineModel, FDCROM_IDX));
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;
            }

            for (; menuItem < 15; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }
            OsdWrite(menuItem, "            exit", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            *parentstate = *menustate;
            *menustate = MENU_SHARPMZ_ROMS2;
            break;


        case MENU_SHARPMZ_ROMS2:
            if (menu) {
                *menustate = MENU_SHARPMZ_MACHINE1;
                *menusub = *menusub_last;
            }
            if (select)
            {
                int selectItem = *menusub + scrollPos;

                if(selectItem > 3)  selectItem += sharpmz_get_custom_rom_enabled(machineModel, MROM_IDX)     ? 0 : 1;
                if(selectItem > 5)  selectItem += sharpmz_get_custom_rom_enabled(machineModel, MROM_80C_IDX) ? 0 : 1;
                if(selectItem > 7)  selectItem += sharpmz_get_custom_rom_enabled(machineModel, CGROM_IDX)    ? 0 : 1;
                if(selectItem > 9)  selectItem += sharpmz_get_custom_rom_enabled(machineModel, KEYMAP_IDX)   ? 0 : 1;
                if(selectItem > 11) selectItem += sharpmz_get_custom_rom_enabled(machineModel, USERROM_IDX)  ? 0 : 1;
                if(selectItem > 13) selectItem += sharpmz_get_custom_rom_enabled(machineModel, FDCROM_IDX)   ? 0 : 1;

                switch(selectItem)
                {
                    case 0:
                        machineModel = sharpmz_get_next_machine_model();
                        *menustate = MENU_SHARPMZ_ROMS1;
                        break;

                    case 1:
                        sharpmz_set_user_rom_enabled(machineModel, sharpmz_get_user_rom_enabled(machineModel) ? 0 : 1, 1);
                        *menustate = MENU_SHARPMZ_ROMS1;
                        break;

                    case 2:
                        sharpmz_set_fdc_rom_enabled(machineModel, sharpmz_get_fdc_rom_enabled(machineModel) ? 0 : 1, 1);
                        *menustate = MENU_SHARPMZ_ROMS1;
                        break;

                    case 3:
                    case 5:
                    case 7:
                    case 9:
                    case 11:
                    case 13:
                        switch(selectItem)
                        {
                            case 3:
                                romType = MROM_IDX;
                                break;

                            case 5:
                                romType = MROM_80C_IDX;
                                break;

                            case 7:
                                romType = CGROM_IDX;
                                break;

                            case 9:
                                romType = KEYMAP_IDX;
                                break;

                            case 11:
                                romType = USERROM_IDX;
                                break;

                            default:
                                romType = FDCROM_IDX;
                                break;
                        }
                        sharpmz_set_custom_rom_enabled(machineModel, romType, sharpmz_get_custom_rom_enabled(machineModel, romType) == 0);
                        *menustate = MENU_SHARPMZ_ROMS1;
                        break;

                    case 4:
                    case 6:
                    case 8:
                    case 10:
                    case 12:
                    case 14:
                        *fs_Options    = SCANO_DIR;
                        sharpmz_select_file("ROMBINrombin", *fs_Options, fs_pFileExt, 1, selectedPath);
                        *fs_ExtLen     = strlen(fs_pFileExt);
                        *fs_MenuSelect = MENU_SHARPMZ_ROM_FILE_SELECTED;
                        *fs_MenuCancel = MENU_SHARPMZ_ROMS1;
                        *menustate     = selectFile;
                        break;

                    case 15:
                        *menustate = MENU_SHARPMZ_MACHINE1;
                        *menusub   = *menusub_last;
                        break;
                }
            }
            if (left)
            {
                *menustate = MENU_SHARPMZ_MACHINE1;
                *menusub   = *menusub_last;
            }
            break;

        case MENU_SHARPMZ_ROM_FILE_SELECTED:
            if(!strncasecmp(selectedPath, SHARPMZ_CORE_NAME, 7)) strcpy(selectedPath, (char *) & selectedPath[8]);
            sharpmz_debugf("File selected: %s, model:%d, for option:%04x\n", selectedPath, machineModel, romType);
            sharpmz_set_rom_file(machineModel, romType, selectedPath);
            *menustate = MENU_SHARPMZ_ROMS1;
            break;

        // DISPLAY Menu
        case MENU_SHARPMZ_DISPLAY1:
            menuItem  = 0;
            subItem   = 0;
            *menumask = 0;

            OsdSetTitle("Display", OSD_ARROW_LEFT);

            OsdWrite(menuItem++, "", 0, 0);

            strcpy(sBuf, " Display Type:  ");
            strcat(sBuf, sharpmz_get_display_type_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Video:         ");
            strcat(sBuf, sharpmz_get_vram_disable_mode_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Graphics:      ");
            strcat(sBuf, sharpmz_get_gram_disable_mode_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " VRAM CPU Wait: ");
            strcat(sBuf, sharpmz_get_vram_wait_mode_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " PCG Mode:      ");
            strcat(sBuf, sharpmz_get_pcg_mode_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Aspect Ratio:  ");
            strcat(sBuf, sharpmz_get_aspect_ratio_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            strcpy(sBuf, " Scandoubler:   ");
            strcat(sBuf, sharpmz_get_scandoubler_fx_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            for (; menuItem < 15; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }

            OsdWrite(menuItem, "            exit", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            *parentstate = *menustate;
            *menustate = MENU_SHARPMZ_DISPLAY2;
            break;

        case MENU_SHARPMZ_DISPLAY2:
            if (menu) {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub = *menusub_last;
            }
            if (select)
            {
                switch(*menusub)
                {
                case 0:
                    sharpmz_set_display_type((sharpmz_get_display_type() + 1) & 0x07, 1);
                    *menustate = MENU_SHARPMZ_DISPLAY1;
                        break;

                case 1:
                    sharpmz_set_vram_disable_mode(sharpmz_get_vram_disable_mode() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_DISPLAY1;
                    break;

                case 2:
                    sharpmz_set_gram_disable_mode(sharpmz_get_gram_disable_mode() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_DISPLAY1;
                    break;

                case 3:
                    sharpmz_set_vram_wait_mode(sharpmz_get_vram_wait_mode() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_DISPLAY1;
                    break;

                case 4:
                    sharpmz_set_pcg_mode(sharpmz_get_pcg_mode() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_DISPLAY1;
                    break;

                case 5:
                    sharpmz_set_aspect_ratio(sharpmz_get_aspect_ratio() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_DISPLAY1;
                    break;

                case 6:
                    sharpmz_set_scandoubler_fx((sharpmz_get_scandoubler_fx() + 1) & 0x07, 1);
                    *menustate = MENU_SHARPMZ_DISPLAY1;
                    break;

                case 7:
                    *menustate = MENU_SHARPMZ_MAIN1;
                    *menusub   = *menusub_last;
                    break;
                }
            }
            if (left)
            {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub = *menusub_last;
            }
            break;

        // Debug options.
        case MENU_SHARPMZ_DEBUG1:
            menuItem  = 0;
            subItem   = 0;
            *menumask = 0;

            OsdSetTitle("Debug", OSD_ARROW_LEFT);

            OsdWrite(menuItem++, "", 0, 0);

            strcpy(sBuf, " Select Memory Bank: ");
            strcat(sBuf, sharpmz_get_memory_bank_string(memoryBank));
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            if(fileDumped == 1)
            {
                strcpy(sBuf, "  File ");
                strcat(sBuf, sharpmz_get_memory_bank_file(memoryBank));
                strcat(sBuf, " written!");
                fileDumped = 0;
            } else
            {
                strcpy(sBuf, "  Dump To \x16 ");
                strcat(sBuf, sharpmz_get_memory_bank_file(memoryBank));
            }
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            OsdWrite(menuItem++, "", 0, 0);
            strcpy(sBuf, " Debug Mode:         ");
            strcat(sBuf, sharpmz_get_debug_enable_string());
            OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            // Display the debug menu only when enabled.
            //
            if(sharpmz_get_debug_enable())
            {
                strcpy(sBuf, "   CPU Frequency:    ");
                strcat(sBuf, sharpmz_get_debug_cpufreq_string());
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;

                strcpy(sBuf, "   Debug LEDS:       ");
                strcat(sBuf, sharpmz_get_debug_leds_string());
                OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                *menumask = (*menumask << 1) | 1;

                // Display the LED menu only when enabled.
                //
                if(sharpmz_get_debug_leds())
                {
                    strcpy(sBuf, "     Sample Freq:    ");
                    strcat(sBuf, sharpmz_get_debug_leds_smpfreq_string());
                    OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                    *menumask = (*menumask << 1) | 1;

                    strcpy(sBuf, "     Signal Block:   ");
                    strcat(sBuf, sharpmz_get_debug_leds_bank_string());
                    OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                    *menumask = (*menumask << 1) | 1;

                    strcpy(sBuf, "            Bank:    ");
                    strcat(sBuf, sharpmz_get_debug_leds_subbank_string());
                    OsdWrite(menuItem++, sBuf, *menusub == subItem++, 0);
                    *menumask = (*menumask << 1) | 1;
                }
            }

            for (; menuItem < 15; menuItem++)
            {
                OsdWrite(menuItem, "", 0, 0);
            }

            OsdWrite(menuItem, "            exit", *menusub == subItem++, 0);
            *menumask = (*menumask << 1) | 1;

            *parentstate = *menustate;
            *menustate = MENU_SHARPMZ_DEBUG2;
            break;

        case MENU_SHARPMZ_DEBUG2:
            if (menu) {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub = *menusub_last;
            }
            if (select)
            {
                *menusub_last = *menusub;
                switch(*menusub)
                {
                case 0:  // Select Memory Bank to be dumped.
                    memoryBank = sharpmz_get_next_memory_bank();
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 1:  // Dump selected Memory Bank
                    sharpmz_read_ram(sharpmz_get_memory_bank_file(memoryBank), memoryBank);
                    fileDumped = 1;
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 2:  // Debug Enable
                    sharpmz_set_debug_enable(sharpmz_get_debug_enable() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 3:  // CPU Frequency
                    debugCPUFrequency = sharpmz_get_next_debug_cpufreq();
                    sharpmz_set_debug_cpufreq(debugCPUFrequency, 1);
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 4:  // Debug LEDS
                    sharpmz_set_debug_leds(sharpmz_get_debug_leds() ? 0 : 1, 1);
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 5:  // Sample Frequency
                    debugLedsSampleFrequency = sharpmz_get_next_debug_leds_smpfreq();
                    sharpmz_set_debug_leds_smpfreq(debugLedsSampleFrequency, 1);
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 6:  // Signal Block
                    debugLedsBank = sharpmz_get_next_debug_leds_bank();
                    sharpmz_set_debug_leds_bank(debugLedsBank, 1);
                    sharpmz_set_debug_leds_subbank(0, 1);             // Always switch to auto if bank changed.
                    sharpmz_get_next_debug_leds_subbank(1);           // And reset the next loop.
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 7:  // Signal Bank
                    sharpmz_set_debug_leds_subbank(sharpmz_get_next_debug_leds_subbank(0), 1);
                    *menustate = MENU_SHARPMZ_DEBUG1;
                    break;

                case 8:  // Exit
                    *menustate = MENU_SHARPMZ_MAIN1;
                    *menusub   = *menusub_last;
                    break;
                }
            }
            if (left)
            {
                *menustate = MENU_SHARPMZ_MAIN1;
                *menusub   = *menusub_last;
            }
            break;

        default:
            break;
    }
}
