/*
    ZEsarUX  ZX Second-Emulator And Released for UniX
    Copyright (C) 2013 Cesar Hernandez Bano

    This file is part of ZEsarUX.

    ZEsarUX 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 program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>



#include "zxpand.h"
#include "mmc.h"
#include "cpu.h"
#include "debug.h"
#include "utils.h"
#include "zxvision.h"
#include "menu_items.h"
#include "screen.h"
#include "zx8081.h"
#include "joystick.h"

z80_bit zxpand_enabled={0};


z80_byte *zxpand_memory_pointer;

//int zxpand_operating_counter=0;

//Si rom zxpand esta activa (1) o no (0)
z80_bit zxpand_overlay_rom={0};



//Todo el mensaje mas el 40, 1 del principio mas el FF del final no pueden ser mayor que 32. O sea el mensaje debe ser maximo 32-3=29
//1234567890123456789012345678901
//                         3.1-SN
char *zxpand_config_message="ZXpand ZEsarUX " EMULATOR_VERSION;

//Al leer directorio se usa zxpand_root_dir y zxpand_cwd
char zxpand_root_dir[PATH_MAX]="";
char zxpand_cwd[PATH_MAX]="";

/*
 * La rom del zxpand debe cargar en otra pagina de memoria aparte , y no en la zona de memoria_spectrum,
 * porque parece que esta rom no tiene tabla de caracteres, y dicha tabla de caracteres la ula la carga siempre de la rom principal
 * Por eso es que tenemos zxpand_memory_pointer=malloc(8192); y la rutina de peek byte de zx80 mira cuando zxpand esta enabled o no
 */

#define ZXPAND_ZX81_ROM_NAME "zxpand_zx81.rom"
#define ZXPAND_ZX80_ROM_NAME "zxpand_zx80.rom"



//Configuration byte parameters
z80_byte zxpand_configByte;

z80_bit dragons_lair_hack={0};
z80_byte dragons_lair_hack_counter=0;

FILE *ptr_zxpand_read_file_command=NULL;

/*
 * >> -variable LATD holds the value returned to a IN opcode
 * >> -variable PORTD holds the value sent to a OUT opcode
 * >> -variable PORTA is the 8 bit high port address (>>5) send to a IN or OUT function
 */
z80_byte zxpand_latd;
z80_byte zxpand_portd;
z80_byte zxpand_porta;

z80_byte zxpand_globalindex;
z80_byte zxpand_globaldata[256];

//usado en lectura de archivos
z80_int zxpand_globalAmount;

//donde guardar nombre fichero
z80_byte *zxpand_fp_fn = &zxpand_globaldata[128];

//Variables al abrir fichero
z80_int zxpand_flags;
z80_int zxpand_start;
z80_int zxpand_length;


//Control de reescritura de archivos
z80_byte zxpand_fsConfig=0;


char *zxpand_texto_comandos[8]={
	"data channel",
	"get data from globalbuffer",
	"put data into globalbuffer",
	"directory control port",
	"file control port",
	"get data from file",
	"put data to file",
	"interface control"
};

char zxpand_defaultExtension;
z80_int zxpand_defaultLoadAddr;


//El texto original era:
//char zx80Token2ascii[] = ";,()?-+*/???=<>";
/*
Pero esto suele generar el warning
warning: trigraph ??= ignored, use -trigraphs to enable [-Wtrigraphs]
En algunos compiladores viejos (y si se activase -trigraphs) esa sentencia ??= se sustituiria por #
Esto no sucedera, pero aun asi lo escapamos y asi tampoco se queja con warning
https://gcc.gnu.org/onlinedocs/cpp/Initial-processing.html

*/
char zx80Token2ascii[] = ";,()?-+*/??\?=<>";


char *SEMICOL = ";";
char *SEPARATOR = "=";

//No usados este:
//static const rom char* EIGHT40 =   "8-40K";



char *SIXTEEN48 = "16-48K";

char *EIGHT48 = "8-48K";


//usados al leer directorio
z80_byte zxpand_filinfo_fattrib;
struct dirent *zxpand_dp;
DIR *zxpand_dfd=NULL;


//ultimo directorio leido al listar archivos
char zxpand_last_dir_open[PATH_MAX]=".";

// take the character required for the equivalent joystick - 0x1b and
// report the offset at that index; Z = 0x3f. 0x3f-0x1b = 36d. offs2char[36] = 0.
//
// '0', or space, is hardcoded as 34d, n/l as 29d
//
static z80_byte zxpand_char2offs[] =
{
   35,19, // 1b
   14,15, // 1d
   16,17, // 1f
   18,23, // 21
   22,21, // 23
   20, 4, // 25
   38, 2, // 27
    6,11, // 29
    7, 8, // 2b
   33,26, // 2d
   32,31, // 2f
   30,36, // 31
   37,25, // 33
   24, 9, // 35
   12, 5, // 37
   13,27, // 39
    3,10, // 3b
    1,28, // 3d
    0, 0  // 3f + pad
};


z80_byte zxpand_jsmap[6];
void zxpand_changedir(char *d);



void zxpand_alloc_mem(void)
{
	zxpand_memory_pointer=malloc(8192);
	if (zxpand_memory_pointer==NULL) cpu_panic ("Can not allocate memory for zxpand ROM");
}

void zxpand_footer_print_zxpand_operating(void)
{

	generic_footertext_print_operating("ZXPAND");
    watermark_tell_device_activity();

	//Y poner icono en inverso
	if (!zxdesktop_icon_zxpand_inverse) {
			zxdesktop_icon_zxpand_inverse=1;
			menu_draw_ext_desktop();
	}
}

void zxpand_footer_zxpand_operating(void)
{

		zxpand_footer_print_zxpand_operating();

}






//0 si ok
int zxpand_load_rom_overlay(void)
{
	FILE *ptr_zxpand_romfile;
	int leidos=0;

	char *romfile=ZXPAND_ZX81_ROM_NAME;

	if (MACHINE_IS_ZX80_TYPE) romfile=ZXPAND_ZX80_ROM_NAME;

	debug_printf (VERBOSE_INFO,"Loading zxpand rom %s",romfile);

	//ptr_zxpand_romfile=fopen(ZXPAND_ROM_NAME,"rb");
	open_sharedfile(romfile,&ptr_zxpand_romfile);


	if (ptr_zxpand_romfile!=NULL) {

		leidos=fread(zxpand_memory_pointer,1,8192,ptr_zxpand_romfile);
		//leidos=fread(memoria_spectrum,1,8192,ptr_zxpand_romfile);
		fclose(ptr_zxpand_romfile);

	}



	if (leidos!=8192 || ptr_zxpand_romfile==NULL) {
		debug_printf (VERBOSE_ERR,"Error reading ZXPAND rom %s. Disabling ZXpand",romfile);
		zxpand_disable();
		return 1;
	}

	return 0;
}


//void zxpand_load_normal_rom(void)
//{
//	rom_load(NULL);
//}

void zxpand_enable(void)
{

	//esto solo puede pasar activandolo por linea de comandos
	if (!MACHINE_IS_ZX8081) {
		debug_printf (VERBOSE_INFO,"ZXpand can only be enabled on ZX80/81");
		return;
	}

	//asignar memoria
	zxpand_alloc_mem();

	//Cuando hay reset (o hard reset) hay que volver a meter activa esta rom
	zxpand_overlay_rom.v=1;

	//Volver si error
	if (zxpand_load_rom_overlay()) return;

	//ZXpand habilita 32kb. En las direcciones 8-40k o bien 16-48k
	//de momento yo habilito desde 8-48k
	enable_ram_in_32768();
	ram_in_8192.v=1;

	//habilitar real video y wrx
	enable_rainbow();
	enable_wrx();


	zxpand_enabled.v=1;

	//root dir se pone directorio actual si esta vacio
	if (zxpand_root_dir[0]==0) getcwd(zxpand_root_dir,PATH_MAX);

	//directorio zxpand vacio
	zxpand_cwd[0]=0;

	//configbyte a FF
	zxpand_configByte=0xFF;

	//desactivar dragons lair hack
	dragons_lair_hack.v=0;

	//Asignamos joystick tipo zxpand
	joystick_emulation=JOYSTICK_ZXPAND;

	//array jsmap. estos son valores estaticos
	//The default programming for the joystick INKEY$ emulation is cursor keys + 0, i.e CONFIG "J=76580"
	//z80_byte zxpand_jsmap[6];
	zxpand_jsmap[0]=zxpand_char2offs[0x23-0x1b];  //0x23 is char number for '7'
	zxpand_jsmap[1]=zxpand_char2offs[0x22-0x1b];  //0x22 is char number for '6'
	zxpand_jsmap[2]=zxpand_char2offs[0x21-0x1b];  //0x21 is char number for '5'
	zxpand_jsmap[3]=zxpand_char2offs[0x24-0x1b];  //0x24 is char number for '8'
	zxpand_jsmap[4]=zxpand_char2offs[0x1c-0x1b];  //0x1c is char number for '0'


}

void zxpand_disable(void)
{
	zxpand_overlay_rom.v=0;
	//zxpand_load_normal_rom();
	zxpand_enabled.v=0;
}






//Convertir cadena de texto de zx81 a ascii
void zxpand_deZeddify(unsigned char* buffer)
{
	unsigned char q;
	while (*buffer)
	{
		q = *buffer;
		if (q & 0x40)
		{
			if (q > 0xd6 && q < 0xe6)
			{
				*buffer = zx80Token2ascii[q - 0xd7];
			}
			else
			{
				*buffer = '?';
			}
		}
		else
		{
			*buffer = da_codigo_zx81_no_artistic(q & 0x3f);
		}

		++buffer;
	}

	//temp
	//ver contenido de zx80Token2ascii
	//int i;
	//for (i=0;i<strlen(zx80Token2ascii);i++) printf ("%c",zx80Token2ascii[i]);
	//printf (" %d\n",strlen(zx80Token2ascii) );

}

//Convertir cadena de texto ascii a zx81
void zxpand_zeddify(unsigned char *buffer)
{
	while (*buffer)
	{
		*buffer = ascii_to_zx81(*buffer);
		++buffer;
	}
	// force 'zeddy' type termination
	*buffer = 255;
}




// check that the supplied ascii filename only consists of alphanums slash and dot.
char zxpand_isValidFN(unsigned char* buffer)
{

	char c;

	while (*buffer)
	{
		//char c = chk_chr(validfns, *buffer);

		c=*buffer;

		if (
			!
			(
				(c>='A' && c<='Z') ||
				(c>='0' && c<'9') ||
				(c=='/') ||
				(c==';') ||
				(c=='.')

			)
		)
		{
			c=0;
		}

		if (c == 0 || c == ';') return c;
		++buffer;
	}

	return 1;
}




//Funciones de gestion de comandos

//modo lectura o escritura
//rellena fullpath con ruta completa
z80_byte zxpand_fileopen(char *modo,char *fullpath)
{

	char* token;
	char autogenext = 1;
	char* p = (char*)zxpand_globaldata;

	zxpand_deZeddify((unsigned char *)p);

	if (*p == '+' || *p == '>')
	{
		++p;
	}

	if (*p == '/')
	{
		autogenext = 0;
	}

	if (!zxpand_isValidFN((unsigned char*)p))
	{
		return 0x40 + ZXPAND_FR_INVALID_NAME;
	}

	//token = strtokpgmram(p, (RFC)SEMICOL);
	token = strtok(p, SEMICOL);
	if (token==NULL)
	{
		// no filename specified
		return 0x40 + ZXPAND_FR_INVALID_NAME;
	}

	// change $ for 0x7e, to support short LFN forms
	{
		if (p[6] == '$')
		{
			p[6] = 0x7e;
		}
	}


	zxpand_start = zxpand_defaultLoadAddr;
	zxpand_length = 0;
	zxpand_flags = 0;

	// parse optional parameters
	//
	//while ((token = strtokpgmram((char*)NULL, (RFC)SEMICOL)) != NULL)
	while ((token = strtok((char*)NULL, SEMICOL)) != NULL)
	{
		// if it starts with an alpha then it's a flag - add it to the bitset
		if (isalpha(*token))
		{
			if (*token == 'X')
			{
				zxpand_flags |= 1;
			}
		}
		else
		{
			// see if it's of the form start,end: if not it's just start
			//
			char* comma = strchr(token,',');
			zxpand_start = atoi(token);
			if (comma)
			{
				zxpand_length = atoi(comma+1);
			}
		}
	}

	// now all the params are parsed, we can create the filename
	//
	{
		char* newFN = (char *) zxpand_fp_fn;
		char found = 0;
		for(token = p; *token; ++token, ++newFN)
		{
			*newFN = *token;
			if (*token == '.')
			{
				found = 1;
			}
		}
		*newFN = 0;
		if (!found && autogenext)
		{
			*newFN = '.';
			++newFN;
			*newFN = zxpand_defaultExtension;
			++newFN;
			*newFN = 0;
		}
	}



	//TODO: habria que proteger que en el nombre indicado no se use ../.. para ir a ruta raiz inferior a zxpand_root_dir
	sprintf (fullpath,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,(char *)zxpand_fp_fn);


	int existe_archivo=si_existe_archivo(fullpath);



	//Si no existe buscar archivo sin comparar mayusculas/minusculas
	//en otras partes del codigo directamente se busca primero el archivo con util_busca_archivo_nocase,
	//aqui se hace al reves. Se busca normal, y si no se encuentra, se busca nocase
	//if (!strcmp(modo,"rb")) {

		if (!existe_archivo) {
			debug_printf (VERBOSE_DEBUG,"File %s not found. Searching without case sensitive",fullpath);
			char encontrado[PATH_MAX];
			char directorio[PATH_MAX];
			util_get_complete_path(zxpand_root_dir,zxpand_cwd,directorio);
			//sprintf (directorio,"%s/%s",zxpand_root_dir,zxpand_cwd);
			if (util_busca_archivo_nocase ((char *)zxpand_fp_fn,directorio,encontrado) ) {
				debug_printf (VERBOSE_DEBUG,"Found with name %s",encontrado);
				existe_archivo=1;

				//cambiamos el nombre fullpath y el zxpand_fp_fn por el encontrado
				sprintf ((char *)zxpand_fp_fn,"%s",encontrado);

				sprintf (fullpath,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,(char *)zxpand_fp_fn);
				//sprintf (fullpath,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,encontrado);
				debug_printf (VERBOSE_DEBUG,"Found file %s searching without case sensitive",fullpath);
			}
		}
	//}

	//Si modo es escritura (wb), si existe, no sobreescribir
	if (!strcmp(modo,"wb")) {
		if (existe_archivo) {
			debug_printf (VERBOSE_DEBUG,"File %s already exists",fullpath);
			return 0x48;
		}
	}

	debug_printf (VERBOSE_DEBUG,"Calling fopen filename %s (fullpath: %s) mode %s",zxpand_fp_fn,fullpath,modo);

	ptr_zxpand_read_file_command=fopen(fullpath,modo);
	if (ptr_zxpand_read_file_command==NULL) {
		//file not found.
		return 0x40+ZXPAND_FR_NO_FILE;
	}

	return 0x40;


}

void zxpand_COM_FileOpenRead(void)
{

	//printf ("zxpand_COM_FileOpenRead\n");
	char fullpath[PATH_MAX];
	z80_byte res=zxpand_fileopen("rb",fullpath);

	if (res==0x40)
	{
		long long int longitud_total=get_file_size(fullpath);


		if (zxpand_length == 0)
		{
			zxpand_length = longitud_total & 65535;
		}

		// hack to make programs auto-disable ROM if read-only attribute is set
		//if (zxpand_filinfo_fattrib & AM_RDO)
		//{
		//   flags |= 1;
		//}

		zxpand_globaldata[0] = zxpand_length & 255;
		zxpand_globaldata[1] = zxpand_length / 256;
		zxpand_globaldata[2] = zxpand_start & 255;
		zxpand_globaldata[3] = zxpand_start / 256;
		zxpand_globaldata[4] = zxpand_flags & 255;
		zxpand_globaldata[5] = zxpand_flags / 256;

		zxpand_globaldata[6] = longitud_total & 0xff;
		zxpand_globaldata[7] = (longitud_total >>  8) & 0xff;
		zxpand_globaldata[8] = (longitud_total >> 16) & 0xff;
		zxpand_globaldata[9] = (longitud_total >> 24) & 0xff;

		memset(&zxpand_globaldata[10], 0, 32-10);

		//printf ("zxpand_start: 0x%X zxpand_length: 0x%X zxpand_flags: 0x%X\n",zxpand_start,zxpand_length,zxpand_flags);

	}

	zxpand_latd = res;

}


//Retorna la ruta a un archivo sin tener en cuenta mayusculas/minusculas
//Retorna 1 si encontrado. 0 si no
//Orig: ruta de entrada. fullpath: ruta de salida
int zxpand_get_lowercase_path(char *orig,char *fullpath)
{

        char nombre_minus_encontrado[PATH_MAX];
        char nombre_a_buscar[PATH_MAX];
        char dir[PATH_MAX];

        util_get_dir(orig,dir);

        util_get_file_no_directory(orig,nombre_a_buscar);


        if (util_busca_archivo_nocase(nombre_a_buscar,dir,nombre_minus_encontrado)) {
                //Retornar fullpath
		util_get_complete_path(dir,nombre_minus_encontrado,fullpath);
                //sprintf (fullpath,"%s/%s",dir,nombre_minus_encontrado);
                debug_printf (VERBOSE_DEBUG,"Found file with non-uppercase name: %s",fullpath);
		return 1;
        }
	return 0;
}


//Devuelve codigo error zxpand. 0 si ok, etc..
int zxpand_rename(char *orig, char *dest)
{
	debug_printf (VERBOSE_DEBUG,"Renaming file %s to %s",orig,dest);
	char lowercasepath[PATH_MAX];
	if (zxpand_get_lowercase_path(orig,lowercasepath)==0) {
		//no encontrado
		return ZXPAND_FR_NO_FILE;
	}

	//Hacer rename
	if (rename(lowercasepath,dest)==0) return 0;
	return ZXPAND_FR_INVALID_NAME;


	//Aqui ya no se llega
	//TODO: borrar esto hasta final de funcion si todo va bien


	if (rename(orig,dest)==0) return 0;

	//Si falla el rename, probar antes que no vaya a ser que el archivo origen no este en mayusculas
	char nombre_minus_encontrado[PATH_MAX];
	char nombre_a_buscar[PATH_MAX];
	char dir[PATH_MAX];

	util_get_dir(orig,dir);

	util_get_file_no_directory(orig,nombre_a_buscar);


	if (util_busca_archivo_nocase(nombre_a_buscar,dir,nombre_minus_encontrado)) {
		//Probar de nuevo el rename
		char fullpath[PATH_MAX];
		util_get_complete_path(dir,nombre_minus_encontrado,fullpath);
		//sprintf (fullpath,"%s/%s",dir,nombre_minus_encontrado);
		debug_printf (VERBOSE_DEBUG,"Found source file with non-uppercase name: %s. Trying again to rename",fullpath);
		if (rename(fullpath,dest)==0) return 0;
	}


	//Cualquier otra cosa, error
	return ZXPAND_FR_INVALID_NAME;

	/*else {
		//no lo ha encontrado. devolver error
		return ZXPAND_FR_INVALID_NAME;
	}

	//Si falla el rename, devolver invalid name.
	if (rename(orig,dest)!=0) return ZXPAND_FR_INVALID_NAME;
	else return 0;
	*/
}

void zxpand_COM_FileOpenWrite(void)
{
	char fullpath[PATH_MAX];
	z80_byte res=zxpand_fileopen("wb",fullpath);

	  if (res == 0x48)
	  {
	  	// file exists
		  if (zxpand_globaldata[0] == '+' || (zxpand_fsConfig & 0x03) == 1)
		  {
			char zxpand_fp_fnBak[PATH_MAX];
		 	char* p = zxpand_fp_fnBak;
	 		memcpy((void*)zxpand_fp_fnBak, (void*)zxpand_fp_fn, 32);
		 	while(*p != '.'){++p;}
		 	strcpy(p, ".BAK");

			char orig[PATH_MAX],dest[PATH_MAX];
			  //TODO: habria que proteger que en el nombre indicado no se use ../.. para ir a ruta raiz inferior a zxpand_root_dir
	                sprintf (orig,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,(char *)zxpand_fp_fn);
        	        sprintf (dest,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,(char *)zxpand_fp_fnBak);

			res = 0x40 | zxpand_rename(orig,dest);
		}

		if (zxpand_globaldata[0] == '>' || (zxpand_fsConfig & 0x03) == 2)
		{
			// overwrite (ala dos pipe)
			res = 0x40;
			if (unlink(fullpath)) res |=ZXPAND_FR_DENIED;
		}

		if (res == 0x40)
		{
			// now try again
			//res = 0x40 | f_open(&fil, zxpand_fp_fn, FA_CREATE_NEW|FA_WRITE);
			//printf ("trying to open again file\n");
		        ptr_zxpand_read_file_command=fopen(fullpath,"wb");
		        if (ptr_zxpand_read_file_command==NULL) {
                		//file not found.
		                res=0x40+ZXPAND_FR_NO_FILE;
		        }
		}
	}

	if (res==0x40)
	{

		long long int longitud_total;

		//Si no existe, tiene sentido esto??
		//valores zxpand_length, zxpand_start, zxpand_flags, longitud_total tienen valores indefinidos

		//para que no se queje el compilador
		longitud_total=0;


		zxpand_globaldata[0] = zxpand_length & 255;
		zxpand_globaldata[1] = zxpand_length / 256;
		zxpand_globaldata[2] = zxpand_start & 255;
		zxpand_globaldata[3] = zxpand_start / 256;
		zxpand_globaldata[4] = zxpand_flags & 255;
		zxpand_globaldata[5] = zxpand_flags / 256;

		zxpand_globaldata[6] = longitud_total & 0xff;
		zxpand_globaldata[7] = (longitud_total >>  8) & 0xff;
		zxpand_globaldata[8] = (longitud_total >> 16) & 0xff;
		zxpand_globaldata[9] = (longitud_total >> 24) & 0xff;

		memset(&zxpand_globaldata[10], 0, 32-10);

	}

	zxpand_latd = res;

}

void zxpand_COM_FileClose(void)
{

	//printf ("zxpand_COM_FileClose\n");


	if (ptr_zxpand_read_file_command!=NULL) {
		fclose(ptr_zxpand_read_file_command);
		ptr_zxpand_read_file_command=NULL;
	}

	zxpand_latd=0x40;


}

void zxpand_COM_FileRead(void)
{

	//esto no deberia pasar:
	if (ptr_zxpand_read_file_command==NULL) {
		debug_printf (VERBOSE_DEBUG,"trying to read from a non opened file. error");
		return;
	}


	//z80_int read;

	if (zxpand_globalAmount == 0)
	{
		zxpand_globalAmount = 256;
	}

	//printf ("zxpand_COM_FileRead. reading %d bytes\n",zxpand_globalAmount);

	//read=fread(&zxpand_globaldata,1,zxpand_globalAmount,ptr_zxpand_read_file_command);
	fread(&zxpand_globaldata,1,zxpand_globalAmount,ptr_zxpand_read_file_command);

	//printf ("read: %d bytes\n",read);

	//siempre devolver ok
	zxpand_latd=0x40;

	//Llamadas al filesystem en zxpand original basadas en Chan's fat file system
	//f_read dice:
	/*


Return Values

FR_OK, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT


Description

The file read/write pointer of the file object advances number of bytes read. After the function succeeded, *br should be checked to detect end of the file. In case of *br is less than btr, it means the read/write pointer reached end of the file during read operation.

	*/
	//por tanto en la llamada original:
	//LATD = 0x40 | f_read(&fil, globalData, globalAmount, &read);
	//no se devuelve error si se leen menos bytes (a no ser, claro esta, que se produzca alguno de :
	//FR_OK, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT
	//como estos no suceden normalmente, devolver ok, incluso si se leen menos bytes


}


void zxpand_COM_FileWrite(void)
{

	//esto no deberia pasar:
	if (ptr_zxpand_read_file_command==NULL) {
		debug_printf (VERBOSE_DEBUG,"trying to write to a non opened file. error");
		return;
	}

	//z80_int written;

	if (zxpand_globalAmount == 0)
	{
		zxpand_globalAmount = 256;
	}

	//printf ("zxpand_COM_FileWrite. escribiendo %d bytes\n",zxpand_globalAmount);

	//written=fwrite(&zxpand_globaldata,1,zxpand_globalAmount,ptr_zxpand_read_file_command);
	fwrite(&zxpand_globaldata,1,zxpand_globalAmount,ptr_zxpand_read_file_command);

	//printf ("written %d bytes\n",written);


	//siempre devolver ok
	//misma razon que zxpand_COM_FileRead, ver ahi
	zxpand_latd=0x40;

}

void zxpand_COM_FileSeek(void)
{
	//TODO. Esto no se ha probado
	z80_int posicion;
	posicion=value_8_to_16(zxpand_globaldata[1],zxpand_globaldata[0]);
	fseek(ptr_zxpand_read_file_command,posicion,SEEK_SET);

	zxpand_latd=0x40;

}

void zxpand_COM_FileRename(void)
{
	char* token;
	char* p = (char*)zxpand_globaldata;

	char ret = 0x40 + ZXPAND_FR_INVALID_NAME;

	zxpand_deZeddify(zxpand_globaldata);

	token = strtok(p, SEMICOL);
	if (NULL != token)
	{
		token = strtok((char*)NULL, SEMICOL);
		if (NULL != token)
		{
			if (zxpand_isValidFN(zxpand_globaldata) && zxpand_isValidFN((unsigned char *)token))
			{

				debug_printf (VERBOSE_DEBUG,"Called rename %s to %s. Current dir: %s",(char *)&zxpand_globaldata[0],token,zxpand_cwd);
        	                char orig[PATH_MAX],dest[PATH_MAX];
                	        //TODO: habria que proteger que en el nombre indicado no se use ../.. para ir a ruta raiz inferior a zxpand_root_dir
                        	sprintf (orig,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,(char *)&zxpand_globaldata[0]);
	                        sprintf (dest,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,token);

				//ret = 0x40 | zxpand_rename((char *)&zxpand_globaldata[0], token);
				ret = 0x40 | zxpand_rename(orig,dest);
			}
		}
	}

	zxpand_latd = ret;

}

void zxpand_COM_FileDelete(void)
{
	//Suponemos error
	z80_byte ret = 0x40 + ZXPAND_FR_INVALID_NAME;

	char fullpath[PATH_MAX];

	zxpand_deZeddify(zxpand_globaldata);
	if (zxpand_isValidFN(zxpand_globaldata))
	{

		//TODO: habria que proteger que en el nombre indicado no se use ../.. para ir a ruta raiz inferior a zxpand_root_dir
		sprintf (fullpath,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,zxpand_globaldata);

		debug_printf (VERBOSE_DEBUG,"Calling file delete file name %s (fullpath: %s)",zxpand_globaldata,fullpath);

		ret=0x40;


	        char lowercasepath[PATH_MAX];
        	if (zxpand_get_lowercase_path(fullpath,lowercasepath)==0) {
	                //no encontrado
        	        ret |=ZXPAND_FR_NO_FILE;
	        }

		else {
			if (unlink(lowercasepath)) ret |=ZXPAND_FR_NO_FILE;
			//Quiza si falla unlink podria ser por permisos, entonces se deberia retornar ZXPAND_FR_WRITE_PROTECTED
		}

		//if (unlink(fullpath)) ret |=ZXPAND_FR_NO_FILE;

	}

	zxpand_latd = ret;

}

void zxpand_go_high(void)
{
	enable_ram_in_32768();
	ram_in_8192.v=0;
}

void zxpand_go_low(void)
{
	enable_ram_in_32768();
	ram_in_8192.v=1;
}



z80_byte zxpand_read_joystick(void)
{


        z80_byte valor_joystick=0xF9;

        //si estamos con menu abierto, no retornar nada
        if (zxvision_key_not_sent_emulated_mach() ) return valor_joystick;


        //Si no es joystick de tipo zxpand, volver
        if (joystick_emulation!=JOYSTICK_ZXPAND) return valor_joystick;


        //0b11111001

        /*
         * Bit -- Direction
         * 7 Up 128
         * 6 Down       64
         * 5 Left       32
         * 4 Right      16
         * 3 Fire       8
         *
         * The corresponding bit will be 0 when the joystick is pushed in that direction. Bits 2 to 0 inclusive are
         * undefined, but you might just work out that bit 0 represents whether a card is present...
         */


        //z80_byte puerto_especial_joystick=0; //Fire Up Down Left Right

        if ((puerto_especial_joystick&1)) valor_joystick &=(255-16);
        if ((puerto_especial_joystick&2)) valor_joystick &=(255-32);
        if ((puerto_especial_joystick&4)) valor_joystick &=(255-64);
        if ((puerto_especial_joystick&8)) valor_joystick &=(255-128);
        if ((puerto_especial_joystick&16)) valor_joystick &=(255-8);
        return valor_joystick;
}


void zxpand_mapJS(z80_byte dirn, z80_byte val)
{
   int q = 0;

   if (val == 0)
   {
      q = 39;
   }
   else
   if (val == 0x76)
   {
      q = 29;
   }
   else
   {
      q = zxpand_char2offs[val - 0x1b];
   }

   zxpand_jsmap[dirn] = q;
}


void zxpand_decodeJS(void)
{
   z80_byte idx = 5;
   z80_byte temp = zxpand_read_joystick();

   // fire gets priority
   //
   if ((temp & 0x08) == 0)
   {
      idx = 4;
   }
   else if((temp & 0x80) == 0)
   {
      idx = 0;
   }
   else if ((temp & 0x40) == 0)
   {
      idx = 1;
   }
   else if ((temp & 0x20) == 0)
   {
      idx = 2;
   }
   else if ((temp & 0x10) == 0)
   {
      idx = 3;
   }

   if (idx != 5)
   {
      zxpand_latd = zxpand_jsmap[idx];
   }
   else
   {
      zxpand_latd = 0xff;
   }
}




//Comando Config
void zxpand_COM_ParseBuffer(void)
{
	char* token;
	z80_byte retcode = 0x40;

	// keep any raw keycodes that might be lost in a conversion
	//
	memcpy((void*)&zxpand_globaldata[128], (void*)&zxpand_globaldata[0], 128);

	zxpand_deZeddify(zxpand_globaldata);

	if(!isalpha(zxpand_globaldata[0]))
	{
		retcode |= ZXPAND_FR_INVALID_OBJECT;
		goto retok;
	}

	// 'A' = 3.
	zxpand_globaldata[0] = zxpand_globaldata[0]-'A'+3;
	//token = strtokpgmram((char*)&zxpand_globaldata[1], (RFC)SEPARATOR);
	token = strtok((char*)&zxpand_globaldata[1], SEPARATOR);

	if (zxpand_globaldata[0] == 'V'-'A'+3)
	{
		// version string

		//strcpypgm2ram((char*)&zxpand_globaldata[1], (RFC)VERSION);

		//zxpand_config_message

		sprintf((char *) &zxpand_globaldata[1],"%s",zxpand_config_message);

		zxpand_zeddify(&zxpand_globaldata[1]);
		zxpand_globaldata[0] = 1;
		goto retok;
	}

	else if (zxpand_globaldata[0] == 'D'-'A'+3)
	{
		// set current working directory

		if (!token)
		{
			zxpand_globaldata[32]='\\';
			zxpand_globaldata[33]=0;
			token = (char*)&zxpand_globaldata[32];
		}

		//retcode |= f_chdir(token);
		retcode=0x40;
		zxpand_changedir(token);
		goto retok;
	}

	else if (zxpand_globaldata[0] == 'M'-'A'+3)
	{
		// memory map control

		if (token)
		{
			if (*token == 'H')
			{
				// HI ram

				//Habilitamos 16-48K
				zxpand_go_high();
			}
			else if (*token == 'L')
			{
				// LO ram
				//Habilitamos 8-48K
				//Deberia ser 8-40K pero este modo en ZEsarUX no esta permitido
				zxpand_go_low();
			}
			else
			{
				retcode |= ZXPAND_FR_INVALID_OBJECT;
				goto retok;
			}
		}
		else
		{
			if (ram_in_8192.v)
			{
				strcpy((char*)&zxpand_globaldata[1], EIGHT48);
			}
			else
			{
				strcpy((char*)&zxpand_globaldata[1], SIXTEEN48);
			}

			zxpand_zeddify(&zxpand_globaldata[1]);
			zxpand_globaldata[0] = 1;
		}

		goto retok;
	}

	else if (zxpand_globaldata[0] == 'C'-'A'+3)
	{
		// config control

		if (token)
		{
			unsigned char n = *token - '0';
			if (n > 9) n -= 7;
			if (n > 15)
			{
				retcode |= ZXPAND_FR_INVALID_OBJECT;
				goto retok;
			}
			++token;
			zxpand_configByte = n * 16;
			n = *token - '0';
			if (n > 9) n -= 7;
			if (n > 15)
			{
				retcode |= ZXPAND_FR_INVALID_OBJECT;
				goto retok;
			}
			zxpand_configByte += n;

			debug_printf (VERBOSE_DEBUG,"Setting zxpand_configByte with %d",zxpand_configByte);

			//TODO WriteEEPROM(0x04, zxpand_configByte);
		}
		else
		{
			unsigned char* p = &zxpand_globaldata[0];
			*p = 1;
			++p;
			*p = ((zxpand_configByte & 0xf0) >> 4) + 0x1c;
			++p;
			*p = (zxpand_configByte & 15) + 0x1c;
			++p;
			*p = 0xff;
			++p;
			*p = zxpand_configByte;
		}

		goto retok;
	}



	 else if (zxpand_globaldata[0] == 'O'-'A'+3)
	 {
		 // overwrite control
		 if (token)
		 {
		 	unsigned char n = *token - '0';
		 	if (n > 2)
			{
				retcode |= ZXPAND_FR_INVALID_OBJECT;
				goto retok;
			}
			zxpand_fsConfig = n;
			//WriteEEPROM(0x05, zxpand_fsConfig);
		}
		else
		{
			unsigned char* p = &zxpand_globaldata[0];
			*p = 1;
			++p;
			if ((zxpand_fsConfig & 3) == 1)
			{
				strcpy((char*)p, "BAK");
			}

			else if ((zxpand_fsConfig & 3) == 2)
			{
				strcpy((char*)p, "OVR");
			}

			else
			{
				strcpy((char*)p, "ERR");
			}
			zxpand_zeddify(p);
			p+= 3;
			*p = 0xff;
		}

		goto retok;
	}


	else if (zxpand_globaldata[0] == 'J'-'A'+3)
	{
		// joystick mapping control

		if (token)
		{
			token += 128;
			zxpand_mapJS(0, *token);
			++token;
			zxpand_mapJS(1, *token);
			++token;
			zxpand_mapJS(2, *token);
			++token;
			zxpand_mapJS(3, *token);
			++token;
			zxpand_mapJS(4, *token & 0x3f); // might have top bit set being last char in string
			// might not. there might not be 4 chars there.
		}
		else
		{
			//printf ("sin token\n");
			//int i;
			//for (i=0;i<6;i++) printf ("i: %d value: 0x%02X\n",i,zxpand_jsmap[i]);

			retcode |= ZXPAND_FR_INVALID_OBJECT;
		}

		goto retok;
	}




	// generic command follows? e.g.:
	// R(amtop),nnnn

	{
		z80_byte n = 0;

		token = strtok((char*)&zxpand_globaldata[0], SEPARATOR);
		if (token)
		{
			// parse optional parameters
			//
			while ((token = strtok((char*)NULL, SEPARATOR)) != NULL)
			{
				zxpand_start = atoi(token);

				//printf ("comando config ramtop. valor: %d\n",zxpand_start);

				zxpand_globaldata[128+n] = zxpand_start & 255;
				zxpand_globaldata[129+n] = zxpand_start / 256;
				n += 2;
			}

			memcpy((void*)&zxpand_globaldata[1], (void*)&zxpand_globaldata[128], n);

			// values have been decoded to the buffer. parameterised commands may now execute



			/* comentado en origen
			 *	// to ensure that a paramterised command has its parameters, put it inside this clause
			 *	//
			 *	if (zxpand_globaldata[0] == 'B'-'A'+3)
			 *	{
			 *	// set bank
			 *
			 *	if (zxpand_globaldata[1])
			 *	{
			 *	GO_BANK1;
		}
		else
		{
		GO_BANK0;
		}
		}
		if (zxpand_globaldata[0] == 'E'-'A'+3)
		{
		retcode |= zxpand_globaldata[1];
		}
		fin comentado en origen  */


		}

		if (n == 0 && zxpand_globaldata[0] == 'R'-'A'+3)
		{
			retcode |= ZXPAND_FR_INVALID_OBJECT;
		}
	}



	retok:
	zxpand_latd = retcode;
}

//tener en cuenta raiz y directorio actual
//si localdir no es NULL, devolver directorio local (quitando zxpand_root_dir)
void zxpand_get_final_directory(char *dir, char *finaldir, char *localdir)
{


	//printf ("zxpand_get_final_directory. dir: %s zxpand_root_dir: %s\n",dir,zxpand_root_dir);

	//Guardamos directorio actual del emulador
	char directorio_actual[PATH_MAX];
	getcwd(directorio_actual,PATH_MAX);

	//cambiar a directorio indicado, juntando raiz, dir actual de zxpand, y dir
	char dir_pedido[PATH_MAX];

	//Si directorio pedido es absoluto, cambiar cwd
	if (dir[0]=='/') {
		sprintf (zxpand_cwd,"%s",dir);
		sprintf (dir_pedido,"%s/%s",zxpand_root_dir,zxpand_cwd);
	}


	else {
		sprintf (dir_pedido,"%s/%s/%s",zxpand_root_dir,zxpand_cwd,dir);
	}

	chdir(dir_pedido);

	//Ver en que directorio estamos
	//char dir_final[PATH_MAX];
	getcwd(finaldir,PATH_MAX);
	//printf ("total final directory: %s . zxpand_root_dir: %s\n",finaldir,zxpand_root_dir);
	//Esto suele retornar sim barra al final

	//Si finaldir no tiene barra al final, haremos que zxpand_root_dir tampoco la tenga
	int l=strlen(finaldir);

	if (l) {
		if (finaldir[l-1]!='/' && finaldir[l-1]!='\\') {
			//finaldir no tiene barra al final
			int m=strlen(zxpand_root_dir);
			if (m) {
				if (zxpand_root_dir[m-1]=='/' || zxpand_root_dir[m-1]=='\\') {
					//root dir tiene barra al final. quitarla
					//printf ("quitando barra del final de zxpand_root_dir\n");
					zxpand_root_dir[m-1]=0;
				}
			}
		}
	}


	//Ahora hay que quitar la parte del directorio raiz
	//printf ("running strstr (%s,%s)\n",finaldir,zxpand_root_dir);
	char *s=strstr(finaldir,zxpand_root_dir);

	if (s==NULL) {
		debug_printf (VERBOSE_DEBUG,"Directory change not allowed");
		//directorio final es el mismo que habia
		sprintf (finaldir,"%s",zxpand_cwd);
		return;
	}

	//Si esta bien, meter parte local
	if (localdir!=NULL) {
		int l=strlen(zxpand_root_dir);
		sprintf (localdir,"%s",&finaldir[l]);
		//printf ("local directory: %s\n",localdir);
	}

	//printf ("directorio final local de zxpand: %s\n",finaldir);


	//Restauramos directorio actual del emulador
	chdir(directorio_actual);
}

//Cambiar directorio
void zxpand_changedir(char *d)
{
	char directorio_final[PATH_MAX];

	debug_printf (VERBOSE_DEBUG,"Changing to directory %s",d);

	zxpand_get_final_directory(d,directorio_final,zxpand_cwd);

}


//Abrir directorio.
void zxpand_COM_DirectoryOpen(void)
{
	char ret = 0x40 + ZXPAND_FR_INVALID_NAME;

	zxpand_deZeddify(zxpand_globaldata);

	zxpand_filinfo_fattrib = 0; // normal dir mode

	if (*zxpand_globaldata == '>')
	{
		// we will change to the directory
		zxpand_changedir((char *)zxpand_globaldata + 1);
		ret=0x40;


		if (ret == 0x40)
		{
			// the CD succeeded, so instruct the 'read directory entry' routine to exit with 'all done'
			zxpand_filinfo_fattrib = 0x42;
		}
	}
	else if (*zxpand_globaldata == '+')
	{

		char directorio_actual[PATH_MAX];
		//obtener directorio final
		zxpand_get_final_directory("",directorio_actual,NULL);


		char directorio_final[PATH_MAX];
		util_get_complete_path(directorio_actual,(char *) (&zxpand_globaldata[1]),directorio_final);
		//sprintf (directorio_final,"%s/%s",directorio_actual,(char *) (&zxpand_globaldata[1]) );

		debug_printf (VERBOSE_DEBUG,"Creating directory %s (total path: %s)",(char *) (&zxpand_globaldata[1]),directorio_final);


		int retorno_mkdir;

		ret = 0x40;

		#ifndef MINGW
		retorno_mkdir=mkdir(directorio_final,S_IRWXU);
		#else
		retorno_mkdir=mkdir(directorio_final);
		#endif

		//Ver si mkdir ha funcionado
		if (retorno_mkdir) ret |=ZXPAND_FR_DENIED;


		if (ret == 0x40)
		{
			// the CD succeeded, so instruct the 'read directory entry' routine to exit with 'all done'
			zxpand_filinfo_fattrib = 0x42;
		}
	}
	else
	{
		// Separate wildcard and path
		//TODO GetWildcard();

		if (zxpand_isValidFN(zxpand_globaldata))
		{
			//ret = 0x40 + f_opendir(&dir, (const char*)&zxpand_globaldata[0]);

			//Si directorio es "", cambiar por "."
			/*if (zxpand_globaldata[0]==0) {
			 *		zxpand_globaldata[0]='.';
			 *		zxpand_globaldata[1]=0;
			}
			*/


			//printf ("opening directory %s\n",zxpand_globaldata);
			char directorio_final[PATH_MAX];
			//obtener directorio final
			zxpand_get_final_directory((char *) zxpand_globaldata,directorio_final,NULL);


			//Guardar directorio final, hace falta al leer cada entrada para saber su tamanyo
			sprintf (zxpand_last_dir_open,"%s",directorio_final);
			zxpand_dfd = opendir(directorio_final);

			if (zxpand_dfd == NULL) {
				debug_printf(VERBOSE_ERR,"Can't open directory %s (full: %s)", zxpand_globaldata,directorio_final);
				ret=0x40 + ZXPAND_FR_NO_PATH;
			}

			else ret=0x40;

		}
	}

	zxpand_latd = ret;
}


//comprobaciones de nombre de archivos en directorio
int zxpand_readdir_no_valido(char *s)
{

	//Si longitud mayor que 12 (8 nombre, punto, 3 extension)
	//if (strlen(s)>12) return 0;

	//printf ("checking is name %s is valid\n",s);


	char extension[NAME_MAX];
	char nombre[NAME_MAX];

	util_get_file_extension(s,extension);
	util_get_file_without_extension(s,nombre);

	//si nombre mayor que 8
	if (strlen(nombre)>8) return 0;

	//si extension mayor que 3
	if (strlen(extension)>3) return 0;


	//si hay letras minusculas
	//int i;
	//for (i=0;s[i];i++) {
	//	if (s[i]>='a' && s[i]<'z') return 0;
	//}



	return 1;

}

void zxpand_COM_DirectoryRead(void)
{

	if (zxpand_dfd==NULL) {
		zxpand_latd=0x3f;
		return;
	}

	do {

		zxpand_dp = readdir(zxpand_dfd);

		if (zxpand_dp == NULL) {
			closedir(zxpand_dfd);
			zxpand_dfd=NULL;
			//no hay mas archivos
			zxpand_latd=0x3f;
			return;
		}


	} while(!zxpand_readdir_no_valido(zxpand_dp->d_name));


	//if (zxpand_isValidFN(zxpand_globaldata)

	//meter flags
	zxpand_filinfo_fattrib=0;

	int longitud_nombre=strlen(zxpand_dp->d_name);

	//obtener nombre con directorio. obtener combinando directorio root, actual y inicio listado
	char nombre_final[PATH_MAX];
	util_get_complete_path(zxpand_last_dir_open,zxpand_dp->d_name,nombre_final);
	//sprintf (nombre_final,"%s/%s",zxpand_last_dir_open,zxpand_dp->d_name);

	//if (get_file_type(zxpand_dp->d_type,zxpand_dp->d_name)==2) {


	if (get_file_type(nombre_final)==2) {



		//meter flags directorio y nombre entre <>
		zxpand_filinfo_fattrib |=16;
		sprintf((char *) &zxpand_globaldata[0],"<%s>",zxpand_dp->d_name);
		longitud_nombre +=2;
	}

	else {
		sprintf((char *) &zxpand_globaldata[0],"%s",zxpand_dp->d_name);
	}


	zxpand_zeddify(&zxpand_globaldata[0]);

	//nombre acabado con 0
	zxpand_globaldata[longitud_nombre]=0;



	int indice=longitud_nombre+1;

	zxpand_globaldata[indice++]=zxpand_filinfo_fattrib;


	long long int longitud_total=get_file_size(nombre_final);



	//copia para ir dividiendo entre 256
	long long int l=longitud_total;

	zxpand_globaldata[indice++]=l&0xFF;

	l=l>>8;
	zxpand_globaldata[indice++]=l&0xFF;

	l=l>>8;
	zxpand_globaldata[indice++]=l&0xFF;

	l=l>>8;
	zxpand_globaldata[indice++]=l&0xFF;

	zxpand_latd=0x40;


}



//Funcion de lectura/escritura de zxpand
void zxpand_process(int waswrite)
{

	void (*worker)(void) = NULL;


	//Activar footer de ZXpand, pero no si solo leemos el joystick
	if (! (zxpand_porta==7 && ( zxpand_portd==0xA0 || zxpand_portd==0xA1) )) {
		zxpand_footer_zxpand_operating();
	}

	if (!waswrite && dragons_lair_hack.v) {
		//Devolver 1 byte
		//printf ("returning byte from dragons lair hack counter=%d. reg_hl=0x%X reg_pc=0x%X\n",dragons_lair_hack_counter,reg_hl,reg_pc);
       	        //zxpand_COM_FileRead_Dragons();
		zxpand_latd=zxpand_globaldata[dragons_lair_hack_counter];
		dragons_lair_hack_counter++;

		//Como incrementa antes y esto es de 8 bits, si vale 0 quiere decir que ha llegado a 256
		if (dragons_lair_hack_counter==0) dragons_lair_hack.v=0;
		return;
	}



	switch (zxpand_porta) {
		// data channel
		case 0x00:
			// poke 0 here to prepare for reading,
			// poke nonzero here to prepare for writing to globalbuffer.
			if (waswrite) {
				if (zxpand_portd==0) {
					// initialise the output latch with the 1st globalbuffer byte,
					// set the read index to the next byte to be read
					zxpand_latd = zxpand_globaldata[0];

					//En el codigo original se mete a 1, pero seguramente porque esto se ejecuta de manera asincrona
					//Aqui en el emulador debe ser 0
					//zxpand_globalindex = 1;
					//
					zxpand_globalindex = 0;

				}

				else if (zxpand_portd == 42) {
					//Dragons Lair hack
					zxpand_globalindex=0;
					//printf ("dragons lair hack.\n");
					dragons_lair_hack.v=1;
					dragons_lair_hack_counter=0;
					//zxpand_latd = zxpand_globaldata[0];
					zxpand_latd = 0x40;
					//The next 256 "in" opcodes will read from zxpand_globaldata directly
				}

				else {
					// reset buffer index ready for writing
					zxpand_globalindex = 0;
				}

			}


			break;




			// get data from globalbuffer
		case 0x01:
			// must have poked zero at port 0 before starting to peek data from here.
			zxpand_latd=(zxpand_globaldata[zxpand_globalindex]);
			//printf ("Returning value 0x%02X from buffer index %d\n",zxpand_latd,zxpand_globalindex);
			zxpand_globalindex++;
			break;

			// put data into globalbuffer
		case 0x02:
			// must have poked nonzero at port 0 before starting to poke data to here.
			if (waswrite) {
				zxpand_globaldata[zxpand_globalindex] = zxpand_portd;
				zxpand_globalindex++;
			}
			break;


			// directory control port
		case 0x03:
			if (waswrite)
			{
				zxpand_latd=0x80;

				if (zxpand_portd == 0u)
				{
					// reset the directory reader
					//
					worker = zxpand_COM_DirectoryOpen;
				}
				else
				{
					// get next directory entry
					//
					worker = zxpand_COM_DirectoryRead;
				}
			}

			break;


			// file control port
		case 0x04:

			if (waswrite)
			{


				z80_byte pd = zxpand_portd;
				zxpand_latd=0x80;

				if (pd & 2) // ZX80 defaults to 'O' file extension and load address of 0x4000
				{
					zxpand_defaultExtension = 'O';
					zxpand_defaultLoadAddr = 0x4000;
				}
				else
				{
					zxpand_defaultExtension = 'P';
					zxpand_defaultLoadAddr = 0x4009;
				}

				// remove bit 1 - the zx80/zx81 flag / bit 1 set = zx80 file
				pd &= 0xfd;

				if (pd == 0)
				{
					// open the file with name in global data buffer for read access
					// indicate there are no bytes in the sector buffer
					// leaves 32 byte file info in global data: size (W), load address (W), flags (W) + 26 others
					//
					worker = zxpand_COM_FileOpenRead;
				}
				else if (pd == 1)
				{
					// open the file with name in global data buffer for write access
					// creating if necessary. global buffer not damaged in case a delete() is following
					//
					worker = zxpand_COM_FileOpenWrite;
				}
				else if (pd == 0x80)
				{
					// close the open file
					//
					worker = zxpand_COM_FileClose;
				}
				else if (pd == 0xd0)
				{
					// seek information is already in the global buffer
					//
					worker = zxpand_COM_FileSeek;
				}
				else if (pd == 0xe0)
				{
					// delete the file with name in global data buffer
					//
					worker = zxpand_COM_FileRename;
				}
				else if (pd == 0xf0)
				{
					// delete the file with name in global data buffer
					//
					worker = zxpand_COM_FileDelete;
				}
			}

			break;

		// get data from file
		case 0x05:

			if (waswrite)
			{
				zxpand_latd=0x80;

				// read the next N bytes into the data buffer
				// PORTD = 0 means read 256 bytes.
				//
				zxpand_globalAmount = zxpand_portd;
				worker = zxpand_COM_FileRead;
			}

			break;

		// put data to file
		case 0x06:

			// write the next N bytes from the global data buffer
			// PORTD = 0 means write 256 bytes.
			//
			if (waswrite)
			{
				zxpand_latd=0x80;

				zxpand_globalAmount = zxpand_portd;
				worker = zxpand_COM_FileWrite;
			}

			break;


		// interface control
		case 0x7:
			//
			if (waswrite)
			{
				unsigned char pd = zxpand_portd;

				zxpand_latd=0x80;

				if (pd == 0x00)
				{
					// parse a command buffer
					worker = zxpand_COM_ParseBuffer;
				}

				//  0xAn functions return something
				else if (pd == 0xa0)
					//Joystick
				{
					// assemble joystick value/card detect and pop it into latd
					// 0b11111001
					//

					z80_byte temp = zxpand_latd=zxpand_read_joystick();
					zxpand_latd = temp & 0xf9;

				}


				else if (pd == 0xa1)
				{
					// fully decoded j/s - this command may take a few nanos.
					// used by INKEY$
					//
					zxpand_decodeJS();
				}


				else
					if (pd == 0xa2)
					{
						z80_byte temp = ~zxpand_read_joystick();
						z80_byte fb = (temp & 16) << 1;
						z80_byte db = temp >> 4;
						zxpand_latd = fb | db;
					}
				//else
				//if (pd == 0xaa) : see heartbeat, below
				else
					if (pd == 0xab)
					{
					//if (mousex < 0) mousex = 0;
					//if (mousex > 255) mousex = 255;
					//if (mousey < 0) mousey = 0;
					//if (mousey > 191) mousey = 191;
					//zxpand_globaldata[0] = mouseSerialNum;
					//zxpand_globaldata[1] = mousex;
					//zxpand_globaldata[2] = mousey;
					//zxpand_globaldata[3] = (PORTB & 8) | (PORTC & 128);
					//zxpand_globaldata[4] = ~mouseSerialNum;
					//++mouseSerialNum;
					zxpand_latd = 0x40;
				}


				else if (pd == 0xac)
				{
					zxpand_latd = zxpand_configByte;
					debug_printf (VERBOSE_DEBUG,"Returning value zxpand_configByte (%d)",zxpand_configByte);
				}

				else if (pd == 0xae)
				{
					// get EEPROM data; global data contains start,count
					z80_byte i = 0;
	 				z80_byte j = 2;
	 				z80_byte k = zxpand_globaldata[0] + 5;
	 				while (i < zxpand_globaldata[1])
	 				{
		 				//zxpand_globaldata[j] = ReadEEPROM(k);
		 				++j;
		 				++k;
		 				++i;
					}
					zxpand_latd = 0x40;
				}


				else
					if (pd == 0xaf)
					{
					// return whether a card is present
					//
					//LATD = CARDPRESENT();
					zxpand_latd=0x40;
				}



				//  0xBn functions set something and may take time
				else if (pd == 0xb0)
				{
					// temporarily disable overlay until next zeddy reset
					//DISABLE_OVERLAY;
					zxpand_overlay_rom.v=0;
					debug_printf (VERBOSE_DEBUG,"Disabling overlay rom until reset");
				}
				else if (pd == 0xb1)
				{
					// temporarily disable overlay until next zeddy reset
					//??disable or enable??
					//ENABLE_OVERLAY;
					zxpand_overlay_rom.v=1;
					debug_printf (VERBOSE_DEBUG,"Enabling overlay rom");
				}

				else if (pd == 0xb2)
				{
					// M=L
					zxpand_go_low();
				}
				else if (pd == 0xb3)
				{
					// M=H
					zxpand_go_high();
				}


				else
					if (pd == 0xb4)
					{
					//GO_BANK0;
				}
				else
					if (pd == 0xb5)
					{
					//GO_BANK1;
				}
				else
					if (pd == 0xb6)
					{
					//GREENLEDON();
				}
				else
					if (pd == 0xb7)
					{
					//GREENLEDOFF();
				}
				else
					if (pd == 0xb8)
					{
					//REDLEDON();
				}
				else
					if (pd == 0xb9)
					{
					//REDLEDOFF();
				}
				else
					if (pd == 0xba)
					{
					// enable mouse mode

					//mousex = 0;
					//mousey = 0;
					//GOMOUSE;
					}
				else
					if (pd == 0xbb)
					{
					//mousex = zxpand_globaldata[0];
					//mousey = zxpand_globaldata[1];
				}




				else if (pd == 0xbc) {
					// set config byte from global data buffer
					//
					zxpand_configByte = zxpand_globaldata[0];
					debug_printf (VERBOSE_DEBUG,"Setting zxpand_configByte = zxpand_globaldata[0] = %d",zxpand_configByte);
					//TODO: WriteEEPROM(0x04, zxpand_configByte);
					zxpand_latd = 0x40;
				}



				else if (pd == 0xbe)
				{
					z80_byte i = 0;
	 				z80_byte j = 2;
	 				z80_byte k = zxpand_globaldata[0] + 5;
	 				while(i < zxpand_globaldata[1])
	 				{
		 				//WriteEEPROM(k, zxpand_globaldata[j]);
		 				++j;
		 				++k;
		 				++i;
					}
					zxpand_latd = 0x40;
				}



				else if (pd == 0xf0)
				{
					// delay a couple of milliseconds, then disable
					//TODO delay DelayMillis(2);
					//DISABLE_OVERLAY;
					zxpand_overlay_rom.v=0;
					debug_printf (VERBOSE_DEBUG,"Disabling overlay rom until reset, with delay");
				}




				else if (pd == 0xaa)
			 	{
				 	// heartbeat/debug 0
				 	zxpand_latd = 0xf0;
				}



				else if (pd == 0x55)
				{
					// heartbeat/debug 1
					zxpand_latd = 0x0f;
				}



				if ((pd & 0xf0) == 0xb0)
				{
					zxpand_latd = 0x40;
				}



			}
			break;






	}

	if (worker) {
		//printf ("calling worker function %p\n",worker);
		worker();
	}

}


z80_byte zxpand_read(z80_byte puerto_h)
{
	zxpand_porta=(puerto_h>>5)&7;

	//if (dragons_lair_hack.v==0) printf ("zxpand read operation. command: %d (%s)\n",zxpand_porta,zxpand_texto_comandos[zxpand_porta]);
	//else printf ("zxpand read operation. with dragons lair hack enabled\n");

	zxpand_process(0);
	//printf ("zxpand read operation result: 0x%02X\n",zxpand_latd);
	return zxpand_latd;
}
void zxpand_write(z80_byte puerto_h,z80_byte value)
{
	zxpand_porta=(puerto_h>>5)&7;
	zxpand_portd=value;
	//printf ("zxpand write operation. command: %d (%s) value: 0x%02X\n",zxpand_porta,zxpand_texto_comandos[zxpand_porta],zxpand_portd);
	zxpand_process(1);
}
