Subversion Repositories psp

[/] [trunk/] [caprice32/] [cap32.cpp] - Rev 183

Compare with Previous | Blame | View Log

/* Caprice32 - Amstrad CPC Emulator
   (c) Copyright 1997-2005 Ulrich Doewich
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 
 
   Oct 11, 2000 - 22:12 preliminary IN/OUT handlers; started with the GA OUT register write routines
   Oct 12, 2000 - 23:31 added GA ROM select to z80_OUT_handler
   Oct 14, 2000 - 13:58 added PPI IN/OUT handlers
   Oct 15, 2000 - 15:33 added CRTC IN/OUT handlers
   Oct 20, 2000 - 23:23 fixed some IN/OUT handler bugs
   Oct 27, 2000 - 17:39 added reset_CPC
   Oct 30, 2000 - 21:05 found the problem with the streched display: scr_bps needs to be in dwords, not bytes!
   Nov 01, 2000 - 23:43 aargh! found the BASIC 'reset' bug: the pbROMhi variable was pointing to the wrong location!
   Nov 03, 2000 - 18:03 added keyboard_interrupt handler
   Nov 03, 2000 - 19:10 added preliminary PSG register write handlers
   Nov 03, 2000 - 23:37 fixed the PPI port C handler; CPC keyboard works now!
   Dec 12, 2000 - 18:14 changed load_dsk() to work with a C based disk info structure
   Jan 24, 2001 - 22:28 loading from disk images works (again)!
   Apr 06, 2001 - 12:52 fixed the keyboard translation table (Allegro WIP 3.9.34 added 3 more keys to the array)
 
   Jun 13, 2001 - 17:25 added DirectDraw and DirectInput8 init routines for the new Windows port of Caprice32
   Jun 13, 2001 - 20:50 keyboard emualtion works now via the DI8 action mapping
   Jun 21, 2001 - 00:30 changed DDraw code to work in windowed mode; added routines to render CPC screen in 32bpp; added drag'n drop functionality for CPC drive A
   Jun 23, 2001 - 16:15 only the visible portion of the CPC screen is rendered to the frame buffer; Windows window is now sized to the proper CPC screen dimensions
   Jun 28, 2001 - 18:42 fixed the extended DSK parsing; loading DSK images via common dialog file selector; handling of joystick axis data
   Jun 29, 2001 - 20:16 speed throttling to original CPC speed; fixed the joystick mapping in the CPC keyboard matrix; implemented more menu commands
   Jun 30, 2001 - 22:57 settings stored in and retrieved from the registry; updated emulator_init and emulator_shutdown; file selector remembers path and selected file
   Jul 01, 2001 - 17:54 ROM slot cofiguration is stored in and retrieved from the registry; automatically loads last DSK images on startup
   Jul 04, 2001 - 00:34 colour tables for 16bpp (555 & 565) and 8bpp PC video modes; DDraw init updated with bit depth dependant code
   Jul 04, 2001 - 22:06 fixed 16bpp colour tables; updated VDU screen centering code to work with all bit depths
   Jul 06, 2001 - 00:35 implemented pause option; emulation is halted when application becomes inactive
   Jul 06, 2001 - 19:34 zip decompression for DSK and SNA loading
   Jul 07, 2001 - 00:58 fixed setting of GA colour table in load_snapshot; last DSK image load on startup supports ZIP files
   Jul 18, 2001 - 18:40 DirectSound init code finally works correctly!
   Jul 20, 2001 - 18:17 found the problem with the sound emulation: snd_multiplier calculation was done with integer arithmetic! grrrr...
   Jul 25, 2001 - 00:48 *finally* got a clean sound code that won't break up anymore!
   Aug 01, 2001 - 17:38 sound configuration is pulled from and stored in the registry
   Aug 02, 2001 - 18:31 floppy drive LED is now shown on the CPC screen during data transfers
   Aug 03, 2001 - 19:26 added About box
   Aug 03, 2001 - 23:49 altered load_dsk() to allocate memory on a track by track basis (in preparation of fdc_writeID() support)
   Aug 06, 2001 - 18:42 started work on the Options dialog
   Aug 10, 2001 - 19:10 finished the General Options property sheet
   Aug 11, 2001 - 17:29 up to 576KB of RAM supported
   Aug 12, 2001 - 21:43 extracted psg_reg_write() from the psg_write #define; fixed the PSG setup in load_snapshot()
   Aug 16, 2001 - 00:49 load_snapshot() now adjusts RAM size, and loads the correct CPC ROM as necessary
   Aug 20, 2001 - 00:59 added save_snapshot(); header contains preliminary v3 info
   Aug 20, 2001 - 16:10 updated load_snapshot() with processing of the 'official' v3 info
   Aug 22, 2001 - 23:26 added Audio Options property sheet to control PSG emulation
   Oct 06, 2001 - 13:22 removed the "experimental" joystick 2 support from the action map code - did not work as expected
   Oct 07, 2001 - 20:27 added the save_dsk() routine
   Oct 13, 2001 - 18:51 completed rewrite of zip handling; users can now choose images from multiple file archives
   Nov 08, 2001 - 19:08 bug fix: the zip_dir routine caused a crash if a zip archive would not open properly
   Nov 09, 2001 - 00:04 the ROM loading routine now checks the validity of the image, and skips a possible 128 bytes header
 
   May 09, 2002 - 19:07 removed the DDSCAPS_3DDEVICE flag from the InitDirectDraw code; modified UpdateBounds to prevent strechblits
 
   Jun 12, 2002 - 18:24 started converting DirectX specific stuff to Allegro
   Jun 14, 2002 - 17:49 emulator runs via GDI blits; re-implemented Allegro keyboard handler
   Jun 22, 2002 - 17:34 finally got the windowed DirectX mode working using the new Allegro CVS version
   Jun 25, 2002 - 18:41 added timer based speed throttling
   Jun 27, 2002 - 18:08 CPC screen blits correctly now: source coordinates were being mangled
   Jul 14, 2002 - 17:42 rewrote the PC sound playback routine to dynamically adjust the rate at which the buffer is filled
   Jul 24, 2002 - 22:16 fixed a possible crash on startup if a zipped DSK in drive A/B had been (re)moved between sessions
   Jul 27, 2002 - 16:45 added processing of a "language" file
   Jul 27, 2002 - 19:14 changed LoadConfiguration and SaveConfiguration to use the Allegro configuration rountines
   Jul 29, 2002 - 22:56 traced the 'access violation' problem back to the sound pause/resume functions
   Aug 02, 2002 - 22:37 added some error condition checks and modified sub-routines to report them properly
 
   Aug 24, 2002 - 23:00 determined Allegro to be unsuitable - reverting everything back to DirectX; implemented DirectInput keyboard translation
   Aug 26, 2002 - 22:51 fixed the initial application window size to use the correct dimensions
   Aug 27, 2002 - 18:23 streamlined the code to be more Windows friendly; app now goes to sleep when minimized
   Sep 07, 2002 - 18:28 added fullscreen mode; corrected 8bpp palette init; GDI calls (e.g. file selector) still work
   Sep 08, 2002 - 16:06 rewrote PCVideo init routines to support windowed/fullscreen mode switching
   Sep 12, 2002 - 00:02 fixed 50Hz timer by using QueryPerformanceCounter
   Sep 20, 2002 - 18:19 re-implemented DirectSound support; fixed timing problems - sound playback should no longer pop
   Sep 21, 2002 - 15:35 LoadConfiguration & SaveConfiguration now use the cap32.cfg file instead of the Registry
   Sep 25, 2002 - 22:49 full screen resolution is selectable in the Options dialog; auto-sizes visible portion; allows mode change while being fs
   Sep 26, 2002 - 22:26 added scanline and line doubling (in software) rendering modes
   Sep 28, 2002 - 17:38 added a speed slider to the General page of the Options dialog
   Sep 29, 2002 - 19:31 added colour monitor and green screen option; switched to QueryPerformanceCounter for the FPS counter
   Sep 29, 2002 - 23:03 added the 15 and 16 bpp green monitor colour maps - thanks to Ralf's Excel sheet!
   Oct 02, 2002 - 23:43 added fault condition checks to zip_dir, zip_extract and load_snapshot
   Oct 04, 2002 - 18:29 added fault condition checks to save_snapshot, load_dsk and save_dsk; added altered DSK check on exit
   Oct 04, 2002 - 22:34 added disk drive activity indicator
   Oct 07, 2002 - 17:48 fixed switching CPC monitor type "on-the-fly"; fixed surface restore in fullscreen mode
   Oct 07, 2002 - 21:58 added line doubling (in hardware) rendering mode
   Oct 16, 2002 - 22:18 added enumeration and initilization of all installed keyboards and joysticks
   Oct 17, 2002 - 23:40 replaced the controls property page in the options dialog
   Oct 28, 2002 - 19:50 added support for custom controls: CPC joystick functions can now be mapped to any attached input device(s)
   Nov 01, 2002 - 15:54 added custom print routine for on-screen messages (e.g. FPS counter)
   Nov 02, 2002 - 16:44 mouse cursor now auto-hides with no mouse movement for 2 seconds
   Nov 10, 2002 - 17:38 added ROM patching to support the french and spanish CPC keyboard layouts
   Nov 10, 2002 - 21:31 fixed PPI port C handler
   Nov 12, 2002 - 22:08 changes to the OUT handler: GA and CRTC cannot be accessed at the same time; fixed the GA pen selection
   Nov 13, 2002 - 22:48 tweaked the IN/OUT handlers a bit more
   Dec 05, 2002 - 00:06 fixed the problem with the tape startup delay: the PPI Port C bit manipulation only considered 4 instead of 8 bits
   Dec 08, 2002 - 22:05 updated the Z80_OUT_handler to accept FDC data on ports 0x7f and 0x7e, as per Kevin's I/O port document
   Dec 10, 2002 - 23:50 print now draws double spaced in scanline mode: don't have to remove the text again this way
   Dec 19, 2002 - 23:28 added 48 & 96 kHz audio options; added sanity checks for LoadConfiguration
   Dec 21, 2002 - 15:50 PCAudio_init reports if selected sample rate is not supported; added empty path handling to LoadConfiguration
   Dec 22, 2002 - 17:21 found a typo in my keyboard lookup table: CPC key 8 was returning the same as CPC key 0
   Jan 12, 2003 - 01:05 added the ability to search for multiple extensions to zip_dir
   Jan 12, 2003 - 15:57 completed processing of dropped dsk, sna and cdt files
   Jan 15, 2003 - 17:30 added the removal of the temp file to process_drop when uncompressing zip archives
   Jan 21, 2003 - 18:53 changed the window handle in all MessageBox calls from NULL to hAppWnd: errors display correctly in full-screen mode now
   Jan 24, 2003 - 00:44 altered the validity check on loading expansion ROMs, as some may not contain a jump instruction at offset 0x06
   Jan 25, 2003 - 16:49 fixed the FDC port handling in z80_in_handler: "Famous 5" loads now!
   Jan 27, 2003 - 18:47 updated the resource file to support themed Windows XP controls
   Jan 28, 2003 - 19:18 added insert_voc: converts a sound sample into a CDT direct recording block
   Jan 30, 2003 - 20:37 added 24bpp display mode support
   Jan 31, 2003 - 16:50 CPC speed slider range is now 50% to 800%, and can be set in 25% increments
   Jan 31, 2003 - 22:42 AMSDOS is placed in ROM slot 7 if the config file does not yet exist
   Feb 23, 2003 - 14:48 fixed the Windows system palette problems in 256 colour mode
   Mar 04, 2003 - 21:04 removed the background brush in the app window class; replaced "return 0L" with "break" in WindowProc
   Mar 11, 2003 - 22:38 emulation can now continue to run when focus is lost; added more invalid object checks to ensure clean exits
   Mar 12, 2003 - 18:08 had to add the background brush again: fullscreen mode would otherwise not update the unused area
   Mar 12, 2003 - 19:02 added the 'auto-pause on loss of focus' feature to the Options dialog
   Mar 12, 2003 - 22:07 added the display of HRESULT codes for DirectDraw function calls
   Mar 17, 2003 - 22:40 added a check_drvX call to the eject disk menu functions
   Mar 22, 2003 - 18:39 added the ability to insert blank disks in either CPC drive; the new Disk page in the Options dialog allows you to choose the format; implemented the Flip Disk option to reverse sides
   Mar 23, 2003 - 21:41 custom disk formats can now be specified in the config file; modified LoadConfiguration and SaveConfiguration to support this feature
   Mar 29, 2003 - 16:40 tape motor is not turned on if there is no tape in the drive
   Apr 02, 2003 - 21:31 changes to the Options dialog: moved the ROM slots to their own page; some cosmetic changes; remembers last page user was on
   Apr 03, 2003 - 22:12 added the option to capture printer output to file; aborting a save dialog for a changed DSK now drops back to the emulation without taking any further action
   Apr 07, 2003 - 19:09 modified the z80_OUT_handler to capture port writes for the MF2; added all the necessary bits for MF2 emulation, but for now it doesn't display the MF2 menu correctly (text is invisible!)...
   Apr 07, 2003 - 21:59 doh! fixed the MF2 problem: the MF2 page-out port always set RAM bank0 instead if checking if the lower ROM was active
   Apr 09, 2003 - 15:49 MF2 ROM is now restored at every reset to ensure the ROM area has not been corrupted by memory writes
   Apr 13, 2003 - 16:18 added code to display the logo in the About box with a transparent background
   Apr 16, 2003 - 16:18 joystick emulation can be toggled on/off; keyboard control assignments now override regular CPC keyboard actions; all dialogs now feature the "Caprice32 - " string to make identification easier; ZIP selector shows ZIP file name
   Apr 26, 2003 - 14:03 added CaptureScreenshot to save the screen contents to a PNG file
   Apr 26, 2003 - 16:25 finished screen capture implementation: added file selector and path/file storage to config file
   May 05, 2003 - 22:46 updated the Audio page in the Options dialog with controls to choose the sample size and adjust the volume
   May 06, 2003 - 18:59 load_snapshot and save_snapshot now use the v3 PSG information + the printer port data
   May 13, 2003 - 17:41 fixed the file selector problem on drag and drop: the exit condition check was reversed
   May 18, 2003 - 01:01 moved Gate Array memory handling to ga_memory_manager; save_snapshot correctly stores current RAM bank now
   May 21, 2003 - 00:31 changed the colour palette generation to support an intensity level slider
   May 21, 2003 - 14:42 added the Intensity slider control to the Video options
   May 26, 2003 - 20:06 emulation loop needs to keep running to produce proper key events: fixes the Pause problem
   May 28, 2003 - 13:59 modified the shadow of the print routine to make it more readable on a white background
   May 29, 2003 - 15:09 implemented the info message display system and added strings for most emulator actions and keyboard shortcuts
   May 30, 2003 - 12:17 added the tape PLAY/STOP button control for proper tape operation
   Jun 02, 2003 - 15:05 if the CreateSurface call fails with video memory, it now attempts to allocate it in system memory
   Jun 04, 2003 - 19:38 added the 'on printer port' drop-down to the Audio options page
   Aug 10, 2003 - 14:35 z80_IN_handler: CRTC write only registers return 0; load_snapshot: fixed broken snapshot support (PPI values were written to the wrong ports!); digiblaster/soundplayer combined into one - changed from a drop down to a check box
 
   May 19, 2004 - 23:13 removed all DirectX/Windows specific parts and replaced them (wherever possible) with the SDL equivalent
   May 23, 2004 - 17:48 dropped double buffer scheme in favour of a back buffer/custom blit operation; fixed colour palette init in 8bpp mode; added support for half size render mode; back buffer is cleared in video_init(); replaced the SDL_Flip operation with an SDL_BlitSurface; initial ExitCondition is now EC_FRAME_COMPLETE to ensure we have a valid video memory pointer to start with
   May 24, 2004 - 00:49 reintroduced snapshot_load & snapshot_save; modified vdu_init to update the two SDL_Rect structures to center or crop the CPC screen; introduced the dwXScale var to support the half size render mode
   May 29, 2004 - 18:09 reintroduced tape_eject, tape_insert and tape_insert_voc; added sound support via the native SDL audio routines
*/
 
#include <zlib.h>
#include "SDL.h"
 
#include "cap32.h"
#include "crtc.h"
#include "tape.h"
#include "video.h"
#include "z80.h"
 
#define VERSION_STRING "v4.2.0"
 
#define ERR_INPUT_INIT           1
#define ERR_VIDEO_INIT           2
#define ERR_VIDEO_SET_MODE       3
#define ERR_VIDEO_SURFACE        4
#define ERR_VIDEO_PALETTE        5
#define ERR_VIDEO_COLOUR_DEPTH   6
#define ERR_AUDIO_INIT           7
#define ERR_AUDIO_RATE           8
#define ERR_OUT_OF_MEMORY        9
#define ERR_CPC_ROM_MISSING      10
#define ERR_NOT_A_CPC_ROM        11
#define ERR_ROM_NOT_FOUND        12
#define ERR_FILE_NOT_FOUND       13
#define ERR_FILE_BAD_ZIP         14
#define ERR_FILE_EMPTY_ZIP       15
#define ERR_FILE_UNZIP_FAILED    16
#define ERR_SNA_INVALID          17
#define ERR_SNA_SIZE             18
#define ERR_SNA_CPC_TYPE         19
#define ERR_SNA_WRITE            20
#define ERR_DSK_INVALID          21
#define ERR_DSK_SIDES            22
#define ERR_DSK_SECTORS          23
#define ERR_DSK_WRITE            24
#define MSG_DSK_ALTERED          25
#define ERR_TAP_INVALID          26
#define ERR_TAP_UNSUPPORTED      27
#define ERR_TAP_BAD_VOC          28
#define ERR_PRINTER              29
#define ERR_BAD_MF2_ROM          30
#define ERR_SDUMP                31
 
#define MSG_SNA_LOAD             32
#define MSG_SNA_SAVE             33
#define MSG_DSK_LOAD             34
#define MSG_DSK_SAVE             35
#define MSG_JOY_ENABLE           36
#define MSG_JOY_DISABLE          37
#define MSG_SPD_NORMAL           38
#define MSG_SPD_FULL             39
#define MSG_TAP_INSERT           40
#define MSG_SDUMP_SAVE           41
#define MSG_PAUSED               42
#define MSG_TAP_PLAY             43
#define MSG_TAP_STOP             44
 
#define MAX_LINE_LEN 256
 
#define MIN_SPEED_SETTING 2
#define MAX_SPEED_SETTING 32
#define DEF_SPEED_SETTING 4
 
 
 
extern byte bTapeLevel;
extern t_z80regs z80;
 
extern dword *ScanPos;
extern dword *ScanStart;
extern word MaxVSync;
extern t_flags1 flags1;
extern t_new_dt new_dt;
 
SDL_AudioSpec *audio_spec = NULL;
 
SDL_Surface *back_surface = NULL;
video_plugin* vid_plugin;
 
dword dwTicks, dwTicksOffset, dwTicksTarget, dwTicksTargetFPS;
dword dwFPS, dwFrameCount;
dword dwXScale, dwYScale;
dword dwSndBufferCopied;
 
dword dwBreakPoint, dwTrace, dwMF2ExitAddr;
dword dwMF2Flags = 0;
byte *pbGPBuffer = NULL;
byte *pbSndBuffer = NULL;
byte *pbSndBufferEnd = NULL;
byte *pbSndStream = NULL;
byte *membank_read[4], *membank_write[4], *memmap_ROM[256];
byte *pbRAM = NULL;
byte *pbROMlo = NULL;
byte *pbROMhi = NULL;
byte *pbExpansionROM = NULL;
byte *pbMF2ROMbackup = NULL;
byte *pbMF2ROM = NULL;
byte *pbTapeImage = NULL;
byte *pbTapeImageEnd = NULL;
byte keyboard_matrix[16];
 
static byte *membank_config[8][4];
 
FILE *pfileObject;
FILE *pfoPrinter;
 
#ifdef DEBUG
#define DEBUG_KEY SDLK_F9
dword dwDebugFlag = 0;
FILE *pfoDebug;
#endif
 
#define MAX_FREQ_ENTRIES 5
dword freq_table[MAX_FREQ_ENTRIES] = {
   11025,
   22050,
   44100,
   48000,
   96000
};
 
#include "font.c"
 
static double colours_rgb[32][3] = {
   { 0.5, 0.5, 0.5 }, { 0.5, 0.5, 0.5 },{ 0.0, 1.0, 0.5 }, { 1.0, 1.0, 0.5 },
   { 0.0, 0.0, 0.5 }, { 1.0, 0.0, 0.5 },{ 0.0, 0.5, 0.5 }, { 1.0, 0.5, 0.5 },
   { 1.0, 0.0, 0.5 }, { 1.0, 1.0, 0.5 },{ 1.0, 1.0, 0.0 }, { 1.0, 1.0, 1.0 },
   { 1.0, 0.0, 0.0 }, { 1.0, 0.0, 1.0 },{ 1.0, 0.5, 0.0 }, { 1.0, 0.5, 1.0 },
   { 0.0, 0.0, 0.5 }, { 0.0, 1.0, 0.5 },{ 0.0, 1.0, 0.0 }, { 0.0, 1.0, 1.0 },
   { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 1.0 },{ 0.0, 0.5, 0.0 }, { 0.0, 0.5, 1.0 },
   { 0.5, 0.0, 0.5 }, { 0.5, 1.0, 0.5 },{ 0.5, 1.0, 0.0 }, { 0.5, 1.0, 1.0 },
   { 0.5, 0.0, 0.0 }, { 0.5, 0.0, 1.0 },{ 0.5, 0.5, 0.0 }, { 0.5, 0.5, 1.0 }
};
 
static double colours_green[32] = {
   0.5647, 0.5647, 0.7529, 0.9412,
   0.1882, 0.3765, 0.4706, 0.6588,
   0.3765, 0.9412, 0.9098, 0.9725,
   0.3451, 0.4078, 0.6275, 0.6902,
   0.1882, 0.7529, 0.7216, 0.7843,
   0.1569, 0.2196, 0.4392, 0.5020,
   0.2824, 0.8471, 0.8157, 0.8784,
   0.2510, 0.3137, 0.5333, 0.5961
};
 
SDL_Color colours[32];
 
static byte bit_values[8] = {
   0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};
 
#define MOD_CPC_SHIFT   (0x01 << 8)
#define MOD_CPC_CTRL    (0x02 << 8)
#define MOD_EMU_KEY     (0x10 << 8)
 
typedef enum {
   CAP32_EXIT = MOD_EMU_KEY,
   CAP32_FPS,
   CAP32_FULLSCRN,
   CAP32_JOY,
   CAP32_LOADDRVA,
   CAP32_LOADDRVB,
   CAP32_LOADSNAP,
   CAP32_LOADTAPE,
   CAP32_MF2RESET,
   CAP32_MF2STOP,
   CAP32_OPTIONS,
   CAP32_PAUSE,
   CAP32_RESET,
   CAP32_SAVESNAP,
   CAP32_SCRNSHOT,
   CAP32_SPEED,
   CAP32_TAPEPLAY
} CAP32_KEYS;
 
typedef enum {
   CPC_0,
   CPC_1,
   CPC_2,
   CPC_3,
   CPC_4,
   CPC_5,
   CPC_6,
   CPC_7,
   CPC_8,
   CPC_9,
   CPC_A,
   CPC_B,
   CPC_C,
   CPC_D,
   CPC_E,
   CPC_F,
   CPC_G,
   CPC_H,
   CPC_I,
   CPC_J,
   CPC_K,
   CPC_L,
   CPC_M,
   CPC_N,
   CPC_O,
   CPC_P,
   CPC_Q,
   CPC_R,
   CPC_S,
   CPC_T,
   CPC_U,
   CPC_V,
   CPC_W,
   CPC_X,
   CPC_Y,
   CPC_Z,
   CPC_a,
   CPC_b,
   CPC_c,
   CPC_d,
   CPC_e,
   CPC_f,
   CPC_g,
   CPC_h,
   CPC_i,
   CPC_j,
   CPC_k,
   CPC_l,
   CPC_m,
   CPC_n,
   CPC_o,
   CPC_p,
   CPC_q,
   CPC_r,
   CPC_s,
   CPC_t,
   CPC_u,
   CPC_v,
   CPC_w,
   CPC_x,
   CPC_y,
   CPC_z,
   CPC_AMPERSAND,
   CPC_ASTERISK,
   CPC_AT,
   CPC_BACKQUOTE,
   CPC_BACKSLASH,
   CPC_CAPSLOCK,
   CPC_CLR,
   CPC_COLON,
   CPC_COMMA,
   CPC_CONTROL,
   CPC_COPY,
   CPC_CPY_DOWN,
   CPC_CPY_LEFT,
   CPC_CPY_RIGHT,
   CPC_CPY_UP,
   CPC_CUR_DOWN,
   CPC_CUR_LEFT,
   CPC_CUR_RIGHT,
   CPC_CUR_UP,
   CPC_CUR_ENDBL,
   CPC_CUR_HOMELN,
   CPC_CUR_ENDLN,
   CPC_CUR_HOMEBL,
   CPC_DBLQUOTE,
   CPC_DEL,
   CPC_DOLLAR,
   CPC_ENTER,
   CPC_EQUAL,
   CPC_ESC,
   CPC_EXCLAMATN,
   CPC_F0,
   CPC_F1,
   CPC_F2,
   CPC_F3,
   CPC_F4,
   CPC_F5,
   CPC_F6,
   CPC_F7,
   CPC_F8,
   CPC_F9,
   CPC_FPERIOD,
   CPC_GREATER,
   CPC_HASH,
   CPC_LBRACKET,
   CPC_LCBRACE,
   CPC_LEFTPAREN,
   CPC_LESS,
   CPC_LSHIFT,
   CPC_MINUS,
   CPC_PERCENT,
   CPC_PERIOD,
   CPC_PIPE,
   CPC_PLUS,
   CPC_POUND,
   CPC_POWER,
   CPC_QUESTION,
   CPC_QUOTE,
   CPC_RBRACKET,
   CPC_RCBRACE,
   CPC_RETURN,
   CPC_RIGHTPAREN,
   CPC_RSHIFT,
   CPC_SEMICOLON,
   CPC_SLASH,
   CPC_SPACE,
   CPC_TAB,
   CPC_UNDERSCORE,
   CPC_J0_UP,
   CPC_J0_DOWN,
   CPC_J0_LEFT,
   CPC_J0_RIGHT,
   CPC_J0_FIRE1,
   CPC_J0_FIRE2,
   CPC_J1_UP,
   CPC_J1_DOWN,
   CPC_J1_LEFT,
   CPC_J1_RIGHT,
   CPC_J1_FIRE1,
   CPC_J1_FIRE2,
   CPC_ES_NTILDE,
   CPC_ES_nTILDE,
   CPC_ES_PESETA,
   CPC_FR_eACUTE,
   CPC_FR_eGRAVE,
   CPC_FR_cCEDIL,
   CPC_FR_aGRAVE,
   CPC_FR_uGRAVE
} CPC_KEYS;
 
static dword cpc_kbd[3][149] = {
   { // original CPC keyboard
         0x40,                   // CPC_0
         0x80,                   // CPC_1
         0x81,                   // CPC_2
         0x71,                   // CPC_3
         0x70,                   // CPC_4
         0x61,                   // CPC_5
         0x60,                   // CPC_6
         0x51,                   // CPC_7
         0x50,                   // CPC_8
         0x41,                   // CPC_9
         0x85 | MOD_CPC_SHIFT,   // CPC_A
         0x66 | MOD_CPC_SHIFT,   // CPC_B
         0x76 | MOD_CPC_SHIFT,   // CPC_C
         0x75 | MOD_CPC_SHIFT,   // CPC_D
         0x72 | MOD_CPC_SHIFT,   // CPC_E
         0x65 | MOD_CPC_SHIFT,   // CPC_F
         0x64 | MOD_CPC_SHIFT,   // CPC_G
         0x54 | MOD_CPC_SHIFT,   // CPC_H
         0x43 | MOD_CPC_SHIFT,   // CPC_I
         0x55 | MOD_CPC_SHIFT,   // CPC_J
         0x45 | MOD_CPC_SHIFT,   // CPC_K
         0x44 | MOD_CPC_SHIFT,   // CPC_L
         0x46 | MOD_CPC_SHIFT,   // CPC_M
         0x56 | MOD_CPC_SHIFT,   // CPC_N
         0x42 | MOD_CPC_SHIFT,   // CPC_O
         0x33 | MOD_CPC_SHIFT,   // CPC_P
         0x83 | MOD_CPC_SHIFT,   // CPC_Q
         0x62 | MOD_CPC_SHIFT,   // CPC_R
         0x74 | MOD_CPC_SHIFT,   // CPC_S
         0x63 | MOD_CPC_SHIFT,   // CPC_T
         0x52 | MOD_CPC_SHIFT,   // CPC_U
         0x67 | MOD_CPC_SHIFT,   // CPC_V
         0x73 | MOD_CPC_SHIFT,   // CPC_W
         0x77 | MOD_CPC_SHIFT,   // CPC_X
         0x53 | MOD_CPC_SHIFT,   // CPC_Y
         0x87 | MOD_CPC_SHIFT,   // CPC_Z
         0x85,                   // CPC_a
         0x66,                   // CPC_b
         0x76,                   // CPC_c
         0x75,                   // CPC_d
         0x72,                   // CPC_e
         0x65,                   // CPC_f
         0x64,                   // CPC_g
         0x54,                   // CPC_h
         0x43,                   // CPC_i
         0x55,                   // CPC_j
         0x45,                   // CPC_k
         0x44,                   // CPC_l
         0x46,                   // CPC_m
         0x56,                   // CPC_n
         0x42,                   // CPC_o
         0x33,                   // CPC_p
         0x83,                   // CPC_q
         0x62,                   // CPC_r
         0x74,                   // CPC_s
         0x63,                   // CPC_t
         0x52,                   // CPC_u
         0x67,                   // CPC_v
         0x73,                   // CPC_w
         0x77,                   // CPC_x
         0x53,                   // CPC_y
         0x87,                   // CPC_z
         0x60 | MOD_CPC_SHIFT,   // CPC_AMPERSAND
         0x35 | MOD_CPC_SHIFT,   // CPC_ASTERISK
         0x32,                   // CPC_AT
         0x26 | MOD_CPC_SHIFT,   // CPC_BACKQUOTE
         0x26,                   // CPC_BACKSLASH
         0x86,                   // CPC_CAPSLOCK
         0x20,                   // CPC_CLR
         0x35,                   // CPC_COLON
         0x47,                   // CPC_COMMA
         0x27,                   // CPC_CONTROL
         0x11,                   // CPC_COPY
         0x02 | MOD_CPC_SHIFT,   // CPC_CPY_DOWN
         0x10 | MOD_CPC_SHIFT,   // CPC_CPY_LEFT
         0x01 | MOD_CPC_SHIFT,   // CPC_CPY_RIGHT
         0x00 | MOD_CPC_SHIFT,   // CPC_CPY_UP
         0x02,                   // CPC_CUR_DOWN
         0x10,                   // CPC_CUR_LEFT
         0x01,                   // CPC_CUR_RIGHT
         0x00,                   // CPC_CUR_UP
         0x02 | MOD_CPC_CTRL,    // CPC_CUR_ENDBL
         0x10 | MOD_CPC_CTRL,    // CPC_CUR_HOMELN
         0x01 | MOD_CPC_CTRL,    // CPC_CUR_ENDLN
         0x00 | MOD_CPC_CTRL,    // CPC_CUR_HOMEBL
         0x81 | MOD_CPC_SHIFT,   // CPC_DBLQUOTE
         0x97,                   // CPC_DEL
         0x70 | MOD_CPC_SHIFT,   // CPC_DOLLAR
         0x06,                   // CPC_ENTER
         0x31 | MOD_CPC_SHIFT,   // CPC_EQUAL
         0x82,                   // CPC_ESC
         0x80 | MOD_CPC_SHIFT,   // CPC_EXCLAMATN
         0x17,                   // CPC_F0
         0x15,                   // CPC_F1
         0x16,                   // CPC_F2
         0x05,                   // CPC_F3
         0x24,                   // CPC_F4
         0x14,                   // CPC_F5
         0x04,                   // CPC_F6
         0x12,                   // CPC_F7
         0x13,                   // CPC_F8
         0x03,                   // CPC_F9
         0x07,                   // CPC_FPERIOD
         0x37 | MOD_CPC_SHIFT,   // CPC_GREATER
         0x71 | MOD_CPC_SHIFT,   // CPC_HASH
         0x21,                   // CPC_LBRACKET
         0x21 | MOD_CPC_SHIFT,   // CPC_LCBRACE
         0x50 | MOD_CPC_SHIFT,   // CPC_LEFTPAREN
         0x47 | MOD_CPC_SHIFT,   // CPC_LESS
         0x25,                   // CPC_LSHIFT
         0x31,                   // CPC_MINUS
         0x61 | MOD_CPC_SHIFT,   // CPC_PERCENT
         0x37,                   // CPC_PERIOD
         0x32 | MOD_CPC_SHIFT,   // CPC_PIPE
         0x34 | MOD_CPC_SHIFT,   // CPC_PLUS
         0x30 | MOD_CPC_SHIFT,   // CPC_POUND
         0x30,                   // CPC_POWER
         0x36 | MOD_CPC_SHIFT,   // CPC_QUESTION
         0x51 | MOD_CPC_SHIFT,   // CPC_QUOTE
         0x23,                   // CPC_RBRACKET
         0x23 | MOD_CPC_SHIFT,   // CPC_RCBRACE
         0x22,                   // CPC_RETURN
         0x41 | MOD_CPC_SHIFT,   // CPC_RIGHTPAREN
         0x25,                   // CPC_RSHIFT
         0x34,                   // CPC_SEMICOLON
         0x36,                   // CPC_SLASH
         0x57,                   // CPC_SPACE
         0x84,                   // CPC_TAB
         0x40 | MOD_CPC_SHIFT,   // CPC_UNDERSCORE
         0x90,                   // CPC_J0_UP
         0x91,                   // CPC_J0_DOWN
         0x92,                   // CPC_J0_LEFT
         0x93,                   // CPC_J0_RIGHT
         0x94,                   // CPC_J0_FIRE1
         0x95,                   // CPC_J0_FIRE2
         0x60,                   // CPC_J1_UP
         0x61,                   // CPC_J1_DOWN
         0x62,                   // CPC_J1_LEFT
         0x63,                   // CPC_J1_RIGHT
         0x64,                   // CPC_J1_FIRE1
         0x65,                   // CPC_J1_FIRE2
         0xff,                   // CPC_ES_NTILDE
         0xff,                   // CPC_ES_nTILDE
         0xff,                   // CPC_ES_PESETA
         0xff,                   // CPC_FR_eACUTE
         0xff,                   // CPC_FR_eGRAVE
         0xff,                   // CPC_FR_cCEDIL
         0xff,                   // CPC_FR_aGRAVE
         0xff,                   // CPC_FR_uGRAVE
   },
   { // French CPC keyboard
         0x40 | MOD_CPC_SHIFT,   // CPC_0
         0x80 | MOD_CPC_SHIFT,   // CPC_1
         0x81 | MOD_CPC_SHIFT,   // CPC_2
         0x71 | MOD_CPC_SHIFT,   // CPC_3
         0x70 | MOD_CPC_SHIFT,   // CPC_4
         0x61 | MOD_CPC_SHIFT,   // CPC_5
         0x60 | MOD_CPC_SHIFT,   // CPC_6
         0x51 | MOD_CPC_SHIFT,   // CPC_7
         0x50 | MOD_CPC_SHIFT,   // CPC_8
         0x41 | MOD_CPC_SHIFT,   // CPC_9
         0x83 | MOD_CPC_SHIFT,   // CPC_A
         0x66 | MOD_CPC_SHIFT,   // CPC_B
         0x76 | MOD_CPC_SHIFT,   // CPC_C
         0x75 | MOD_CPC_SHIFT,   // CPC_D
         0x72 | MOD_CPC_SHIFT,   // CPC_E
         0x65 | MOD_CPC_SHIFT,   // CPC_F
         0x64 | MOD_CPC_SHIFT,   // CPC_G
         0x54 | MOD_CPC_SHIFT,   // CPC_H
         0x43 | MOD_CPC_SHIFT,   // CPC_I
         0x55 | MOD_CPC_SHIFT,   // CPC_J
         0x45 | MOD_CPC_SHIFT,   // CPC_K
         0x44 | MOD_CPC_SHIFT,   // CPC_L
         0x35 | MOD_CPC_SHIFT,   // CPC_M
         0x56 | MOD_CPC_SHIFT,   // CPC_N
         0x42 | MOD_CPC_SHIFT,   // CPC_O
         0x33 | MOD_CPC_SHIFT,   // CPC_P
         0x85 | MOD_CPC_SHIFT,   // CPC_Q
         0x62 | MOD_CPC_SHIFT,   // CPC_R
         0x74 | MOD_CPC_SHIFT,   // CPC_S
         0x63 | MOD_CPC_SHIFT,   // CPC_T
         0x52 | MOD_CPC_SHIFT,   // CPC_U
         0x67 | MOD_CPC_SHIFT,   // CPC_V
         0x87 | MOD_CPC_SHIFT,   // CPC_W
         0x77 | MOD_CPC_SHIFT,   // CPC_X
         0x53 | MOD_CPC_SHIFT,   // CPC_Y
         0x73 | MOD_CPC_SHIFT,   // CPC_Z
         0x83,                   // CPC_a
         0x66,                   // CPC_b
         0x76,                   // CPC_c
         0x75,                   // CPC_d
         0x72,                   // CPC_e
         0x65,                   // CPC_f
         0x64,                   // CPC_g
         0x54,                   // CPC_h
         0x43,                   // CPC_i
         0x55,                   // CPC_j
         0x45,                   // CPC_k
         0x44,                   // CPC_l
         0x35,                   // CPC_m
         0x56,                   // CPC_n
         0x42,                   // CPC_o
         0x33,                   // CPC_p
         0x85,                   // CPC_q
         0x62,                   // CPC_r
         0x74,                   // CPC_s
         0x63,                   // CPC_t
         0x52,                   // CPC_u
         0x67,                   // CPC_v
         0x87,                   // CPC_w
         0x77,                   // CPC_x
         0x53,                   // CPC_y
         0x73,                   // CPC_z
         0x80,                   // CPC_AMPERSAND
         0x21,                   // CPC_ASTERISK
         0x26 | MOD_CPC_SHIFT,   // CPC_AT
         0xff,                   // CPC_BACKQUOTE
         0x26 | MOD_CPC_CTRL,    // CPC_BACKSLASH
         0x86,                   // CPC_CAPSLOCK
         0x20,                   // CPC_CLR
         0x37,                   // CPC_COLON
         0x46,                   // CPC_COMMA
         0x27,                   // CPC_CONTROL
         0x11,                   // CPC_COPY
         0x02 | MOD_CPC_SHIFT,   // CPC_CPY_DOWN
         0x10 | MOD_CPC_SHIFT,   // CPC_CPY_LEFT
         0x01 | MOD_CPC_SHIFT,   // CPC_CPY_RIGHT
         0x00 | MOD_CPC_SHIFT,   // CPC_CPY_UP
         0x02,                   // CPC_CUR_DOWN
         0x10,                   // CPC_CUR_LEFT
         0x01,                   // CPC_CUR_RIGHT
         0x00,                   // CPC_CUR_UP
         0x02 | MOD_CPC_CTRL,    // CPC_CUR_ENDBL
         0x10 | MOD_CPC_CTRL,    // CPC_CUR_HOMELN
         0x01 | MOD_CPC_CTRL,    // CPC_CUR_ENDLN
         0x00 | MOD_CPC_CTRL,    // CPC_CUR_HOMEBL
         0x71,                   // CPC_DBLQUOTE
         0x97,                   // CPC_DEL
         0x26,                   // CPC_DOLLAR
         0x06,                   // CPC_ENTER
         0x36,                   // CPC_EQUAL
         0x82,                   // CPC_ESC
         0x50,                   // CPC_EXCLAMATN
         0x17,                   // CPC_F0
         0x15,                   // CPC_F1
         0x16,                   // CPC_F2
         0x05,                   // CPC_F3
         0x24,                   // CPC_F4
         0x14,                   // CPC_F5
         0x04,                   // CPC_F6
         0x12,                   // CPC_F7
         0x13,                   // CPC_F8
         0x03,                   // CPC_F9
         0x07,                   // CPC_FPERIOD
         0x23 | MOD_CPC_SHIFT,   // CPC_GREATER
         0x23,                   // CPC_HASH
         0x31 | MOD_CPC_SHIFT,   // CPC_LBRACKET
         0xff,                   // CPC_LCBRACE
         0x61,                   // CPC_LEFTPAREN
         0x21 | MOD_CPC_SHIFT,   // CPC_LESS
         0x25,                   // CPC_LSHIFT
         0x30,                   // CPC_MINUS
         0x34 | MOD_CPC_SHIFT,   // CPC_PERCENT
         0x47 | MOD_CPC_SHIFT,   // CPC_PERIOD
         0x32 | MOD_CPC_SHIFT,   // CPC_PIPE
         0x36 | MOD_CPC_SHIFT,   // CPC_PLUS
         0xff,                   // CPC_POUND
         0x32,                   // CPC_POWER
         0x46 | MOD_CPC_SHIFT,   // CPC_QUESTION
         0x70,                   // CPC_QUOTE
         0x60,                   // CPC_RBRACKET
         0xff,                   // CPC_RCBRACE
         0x22,                   // CPC_RETURN
         0x31,                   // CPC_RIGHTPAREN
         0x25,                   // CPC_RSHIFT
         0x47,                   // CPC_SEMICOLON
         0x37 | MOD_CPC_SHIFT,   // CPC_SLASH
         0x57,                   // CPC_SPACE
         0x84,                   // CPC_TAB
         0x30 | MOD_CPC_SHIFT,   // CPC_UNDERSCORE
         0x90,                   // CPC_J0_UP
         0x91,                   // CPC_J0_DOWN
         0x92,                   // CPC_J0_LEFT
         0x93,                   // CPC_J0_RIGHT
         0x94,                   // CPC_J0_FIRE1
         0x95,                   // CPC_J0_FIRE2
         0x60,                   // CPC_J1_UP
         0x61,                   // CPC_J1_DOWN
         0x62,                   // CPC_J1_LEFT
         0x63,                   // CPC_J1_RIGHT
         0x64,                   // CPC_J1_FIRE1
         0x65,                   // CPC_J1_FIRE2
         0xff,                   // CPC_ES_NTILDE
         0xff,                   // CPC_ES_nTILDE
         0xff,                   // CPC_ES_PESETA
         0x81,                   // CPC_FR_eACUTE
         0x51,                   // CPC_FR_eGRAVE
         0x41,                   // CPC_FR_cCEDIL
         0x40,                   // CPC_FR_aGRAVE
         0x34,                   // CPC_FR_uGRAVE
   },
   { // Spanish CPC keyboard
         0x40,                   // CPC_0
         0x80,                   // CPC_1
         0x81,                   // CPC_2
         0x71,                   // CPC_3
         0x70,                   // CPC_4
         0x61,                   // CPC_5
         0x60,                   // CPC_6
         0x51,                   // CPC_7
         0x50,                   // CPC_8
         0x41,                   // CPC_9
         0x85 | MOD_CPC_SHIFT,   // CPC_A
         0x66 | MOD_CPC_SHIFT,   // CPC_B
         0x76 | MOD_CPC_SHIFT,   // CPC_C
         0x75 | MOD_CPC_SHIFT,   // CPC_D
         0x72 | MOD_CPC_SHIFT,   // CPC_E
         0x65 | MOD_CPC_SHIFT,   // CPC_F
         0x64 | MOD_CPC_SHIFT,   // CPC_G
         0x54 | MOD_CPC_SHIFT,   // CPC_H
         0x43 | MOD_CPC_SHIFT,   // CPC_I
         0x55 | MOD_CPC_SHIFT,   // CPC_J
         0x45 | MOD_CPC_SHIFT,   // CPC_K
         0x44 | MOD_CPC_SHIFT,   // CPC_L
         0x46 | MOD_CPC_SHIFT,   // CPC_M
         0x56 | MOD_CPC_SHIFT,   // CPC_N
         0x42 | MOD_CPC_SHIFT,   // CPC_O
         0x33 | MOD_CPC_SHIFT,   // CPC_P
         0x83 | MOD_CPC_SHIFT,   // CPC_Q
         0x62 | MOD_CPC_SHIFT,   // CPC_R
         0x74 | MOD_CPC_SHIFT,   // CPC_S
         0x63 | MOD_CPC_SHIFT,   // CPC_T
         0x52 | MOD_CPC_SHIFT,   // CPC_U
         0x67 | MOD_CPC_SHIFT,   // CPC_V
         0x73 | MOD_CPC_SHIFT,   // CPC_W
         0x77 | MOD_CPC_SHIFT,   // CPC_X
         0x53 | MOD_CPC_SHIFT,   // CPC_Y
         0x87 | MOD_CPC_SHIFT,   // CPC_Z
         0x85,                   // CPC_a
         0x66,                   // CPC_b
         0x76,                   // CPC_c
         0x75,                   // CPC_d
         0x72,                   // CPC_e
         0x65,                   // CPC_f
         0x64,                   // CPC_g
         0x54,                   // CPC_h
         0x43,                   // CPC_i
         0x55,                   // CPC_j
         0x45,                   // CPC_k
         0x44,                   // CPC_l
         0x46,                   // CPC_m
         0x56,                   // CPC_n
         0x42,                   // CPC_o
         0x33,                   // CPC_p
         0x83,                   // CPC_q
         0x62,                   // CPC_r
         0x74,                   // CPC_s
         0x63,                   // CPC_t
         0x52,                   // CPC_u
         0x67,                   // CPC_v
         0x73,                   // CPC_w
         0x77,                   // CPC_x
         0x53,                   // CPC_y
         0x87,                   // CPC_z
         0x60 | MOD_CPC_SHIFT,   // CPC_AMPERSAND
         0x21 | MOD_CPC_SHIFT,   // CPC_ASTERISK
         0x32,                   // CPC_AT
         0x26 | MOD_CPC_SHIFT,   // CPC_BACKQUOTE
         0x26,                   // CPC_BACKSLASH
         0x86,                   // CPC_CAPSLOCK
         0x20,                   // CPC_CLR
         0x34 | MOD_CPC_SHIFT,   // CPC_COLON
         0x47,                   // CPC_COMMA
         0x27,                   // CPC_CONTROL
         0x11,                   // CPC_COPY
         0x02 | MOD_CPC_SHIFT,   // CPC_CPY_DOWN
         0x10 | MOD_CPC_SHIFT,   // CPC_CPY_LEFT
         0x01 | MOD_CPC_SHIFT,   // CPC_CPY_RIGHT
         0x00 | MOD_CPC_SHIFT,   // CPC_CPY_UP
         0x02,                   // CPC_CUR_DOWN
         0x10,                   // CPC_CUR_LEFT
         0x01,                   // CPC_CUR_RIGHT
         0x00,                   // CPC_CUR_UP
         0x02 | MOD_CPC_CTRL,    // CPC_CUR_ENDBL
         0x10 | MOD_CPC_CTRL,    // CPC_CUR_HOMELN
         0x01 | MOD_CPC_CTRL,    // CPC_CUR_ENDLN
         0x00 | MOD_CPC_CTRL,    // CPC_CUR_HOMEBL
         0x81 | MOD_CPC_SHIFT,   // CPC_DBLQUOTE
         0x97,                   // CPC_DEL
         0x70 | MOD_CPC_SHIFT,   // CPC_DOLLAR
         0x06,                   // CPC_ENTER
         0x31 | MOD_CPC_SHIFT,   // CPC_EQUAL
         0x82,                   // CPC_ESC
         0x80 | MOD_CPC_SHIFT,   // CPC_EXCLAMATN
         0x17,                   // CPC_F0
         0x15,                   // CPC_F1
         0x16,                   // CPC_F2
         0x05,                   // CPC_F3
         0x24,                   // CPC_F4
         0x14,                   // CPC_F5
         0x04,                   // CPC_F6
         0x12,                   // CPC_F7
         0x13,                   // CPC_F8
         0x03,                   // CPC_F9
         0x07,                   // CPC_FPERIOD
         0x37 | MOD_CPC_SHIFT,   // CPC_GREATER
         0x71 | MOD_CPC_SHIFT,   // CPC_HASH
         0x21,                   // CPC_LBRACKET
         0xff,                   // CPC_LCBRACE
         0x50 | MOD_CPC_SHIFT,   // CPC_LEFTPAREN
         0x47 | MOD_CPC_SHIFT,   // CPC_LESS
         0x25,                   // CPC_LSHIFT
         0x31,                   // CPC_MINUS
         0x61 | MOD_CPC_SHIFT,   // CPC_PERCENT
         0x37,                   // CPC_PERIOD
         0x32 | MOD_CPC_SHIFT,   // CPC_PIPE
         0x23 | MOD_CPC_SHIFT,   // CPC_PLUS
         0xff,                   // CPC_POUND
         0x30,                   // CPC_POWER
         0x36 | MOD_CPC_SHIFT,   // CPC_QUESTION
         0x51 | MOD_CPC_SHIFT,   // CPC_QUOTE
         0x23,                   // CPC_RBRACKET
         0xff,                   // CPC_RCBRACE
         0x22,                   // CPC_RETURN
         0x41 | MOD_CPC_SHIFT,   // CPC_RIGHTPAREN
         0x25,                   // CPC_RSHIFT
         0x34,                   // CPC_SEMICOLON
         0x36,                   // CPC_SLASH
         0x57,                   // CPC_SPACE
         0x84,                   // CPC_TAB
         0x40 | MOD_CPC_SHIFT,   // CPC_UNDERSCORE
         0x90,                   // CPC_J0_UP
         0x91,                   // CPC_J0_DOWN
         0x92,                   // CPC_J0_LEFT
         0x93,                   // CPC_J0_RIGHT
         0x94,                   // CPC_J0_FIRE1
         0x95,                   // CPC_J0_FIRE2
         0x60,                   // CPC_J1_UP
         0x61,                   // CPC_J1_DOWN
         0x62,                   // CPC_J1_LEFT
         0x63,                   // CPC_J1_RIGHT
         0x64,                   // CPC_J1_FIRE1
         0x65,                   // CPC_J1_FIRE2
         0x35 | MOD_CPC_SHIFT,   // CPC_ES_NTILDE
         0x35,                   // CPC_ES_nTILDE
         0x30 | MOD_CPC_SHIFT,   // CPC_ES_PESETA
         0xff,                   // CPC_FR_eACUTE
         0xff,                   // CPC_FR_eGRAVE
         0xff,                   // CPC_FR_cCEDIL
         0xff,                   // CPC_FR_aGRAVE
         0xff,                   // CPC_FR_uGRAVE
   }
};
 
#define MOD_PC_SHIFT    (KMOD_SHIFT << 16)
#define MOD_PC_CTRL     (KMOD_CTRL << 16)
#define MOD_PC_MODE     (KMOD_MODE << 16)
 
#define KBD_MAX_ENTRIES 160
static int kbd_layout[4][KBD_MAX_ENTRIES][2] = {
   { // US PC to CPC keyboard layout translation
      { CPC_0,          SDLK_0 },
      { CPC_1,          SDLK_1 },
      { CPC_2,          SDLK_2 },
      { CPC_3,          SDLK_3 },
      { CPC_4,          SDLK_4 },
      { CPC_5,          SDLK_5 },
      { CPC_6,          SDLK_6 },
      { CPC_7,          SDLK_7 },
      { CPC_8,          SDLK_8 },
      { CPC_9,          SDLK_9 },
      { CPC_A,          SDLK_a | MOD_PC_SHIFT },
      { CPC_B,          SDLK_b | MOD_PC_SHIFT },
      { CPC_C,          SDLK_c | MOD_PC_SHIFT },
      { CPC_D,          SDLK_d | MOD_PC_SHIFT },
      { CPC_E,          SDLK_e | MOD_PC_SHIFT },
      { CPC_F,          SDLK_f | MOD_PC_SHIFT },
      { CPC_G,          SDLK_g | MOD_PC_SHIFT },
      { CPC_H,          SDLK_h | MOD_PC_SHIFT },
      { CPC_I,          SDLK_i | MOD_PC_SHIFT },
      { CPC_J,          SDLK_j | MOD_PC_SHIFT },
      { CPC_K,          SDLK_k | MOD_PC_SHIFT },
      { CPC_L,          SDLK_l | MOD_PC_SHIFT },
      { CPC_M,          SDLK_m | MOD_PC_SHIFT },
      { CPC_N,          SDLK_n | MOD_PC_SHIFT },
      { CPC_O,          SDLK_o | MOD_PC_SHIFT },
      { CPC_P,          SDLK_p | MOD_PC_SHIFT },
      { CPC_Q,          SDLK_q | MOD_PC_SHIFT },
      { CPC_R,          SDLK_r | MOD_PC_SHIFT },
      { CPC_S,          SDLK_s | MOD_PC_SHIFT },
      { CPC_T,          SDLK_t | MOD_PC_SHIFT },
      { CPC_U,          SDLK_u | MOD_PC_SHIFT },
      { CPC_V,          SDLK_v | MOD_PC_SHIFT },
      { CPC_W,          SDLK_w | MOD_PC_SHIFT },
      { CPC_X,          SDLK_x | MOD_PC_SHIFT },
      { CPC_Y,          SDLK_y | MOD_PC_SHIFT },
      { CPC_Z,          SDLK_z | MOD_PC_SHIFT },
      { CPC_a,          SDLK_a },
      { CPC_b,          SDLK_b },
      { CPC_c,          SDLK_c },
      { CPC_d,          SDLK_d },
      { CPC_e,          SDLK_e },
      { CPC_f,          SDLK_f },
      { CPC_g,          SDLK_g },
      { CPC_h,          SDLK_h },
      { CPC_i,          SDLK_i },
      { CPC_j,          SDLK_j },
      { CPC_k,          SDLK_k },
      { CPC_l,          SDLK_l },
      { CPC_m,          SDLK_m },
      { CPC_n,          SDLK_n },
      { CPC_o,          SDLK_o },
      { CPC_p,          SDLK_p },
      { CPC_q,          SDLK_q },
      { CPC_r,          SDLK_r },
      { CPC_s,          SDLK_s },
      { CPC_t,          SDLK_t },
      { CPC_u,          SDLK_u },
      { CPC_v,          SDLK_v },
      { CPC_w,          SDLK_w },
      { CPC_x,          SDLK_x },
      { CPC_y,          SDLK_y },
      { CPC_z,          SDLK_z },
      { CPC_AMPERSAND,  SDLK_7 | MOD_PC_SHIFT },
      { CPC_ASTERISK,   SDLK_8 | MOD_PC_SHIFT },
      { CPC_AT,         SDLK_2 | MOD_PC_SHIFT },
      { CPC_BACKQUOTE,  SDLK_BACKQUOTE },
      { CPC_BACKSLASH,  SDLK_BACKSLASH },
      { CPC_CAPSLOCK,   SDLK_CAPSLOCK },
      { CPC_CLR,        SDLK_DELETE },
      { CPC_COLON,      SDLK_SEMICOLON | MOD_PC_SHIFT },
      { CPC_COMMA,      SDLK_COMMA },
      { CPC_CONTROL,    SDLK_LCTRL },
      { CPC_COPY,       SDLK_LALT },
      { CPC_CPY_DOWN,   SDLK_DOWN | MOD_PC_SHIFT },
      { CPC_CPY_LEFT,   SDLK_LEFT | MOD_PC_SHIFT },
      { CPC_CPY_RIGHT,  SDLK_RIGHT | MOD_PC_SHIFT },
      { CPC_CPY_UP,     SDLK_UP | MOD_PC_SHIFT },
      { CPC_CUR_DOWN,   SDLK_DOWN },
      { CPC_CUR_LEFT,   SDLK_LEFT },
      { CPC_CUR_RIGHT,  SDLK_RIGHT },
      { CPC_CUR_UP,     SDLK_UP },
      { CPC_CUR_ENDBL,  SDLK_END | MOD_PC_CTRL },
      { CPC_CUR_HOMELN, SDLK_HOME },
      { CPC_CUR_ENDLN,  SDLK_END },
      { CPC_CUR_HOMEBL, SDLK_HOME | MOD_PC_CTRL },
      { CPC_DBLQUOTE,   SDLK_QUOTE | MOD_PC_SHIFT },
      { CPC_DEL,        SDLK_BACKSPACE },
      { CPC_DOLLAR,     SDLK_4 | MOD_PC_SHIFT },
      { CPC_ENTER,      SDLK_KP_ENTER },
      { CPC_EQUAL,      SDLK_EQUALS },
      { CPC_ESC,        SDLK_ESCAPE },
      { CPC_EXCLAMATN,  SDLK_1 | MOD_PC_SHIFT },
      { CPC_F0,         SDLK_KP0 },
      { CPC_F1,         SDLK_KP1 },
      { CPC_F2,         SDLK_KP2 },
      { CPC_F3,         SDLK_KP3 },
      { CPC_F4,         SDLK_KP4 },
      { CPC_F5,         SDLK_KP5 },
      { CPC_F6,         SDLK_KP6 },
      { CPC_F7,         SDLK_KP7 },
      { CPC_F8,         SDLK_KP8 },
      { CPC_F9,         SDLK_KP9 },
      { CPC_FPERIOD,    SDLK_KP_PERIOD },
      { CPC_GREATER,    SDLK_PERIOD | MOD_PC_SHIFT },
      { CPC_HASH,       SDLK_3 | MOD_PC_SHIFT },
      { CPC_LBRACKET,   SDLK_LEFTBRACKET },
      { CPC_LCBRACE,    SDLK_LEFTBRACKET | MOD_PC_SHIFT },
      { CPC_LEFTPAREN,  SDLK_9 | MOD_PC_SHIFT },
      { CPC_LESS,       SDLK_COMMA | MOD_PC_SHIFT },
      { CPC_LSHIFT,     SDLK_LSHIFT },
      { CPC_MINUS,      SDLK_MINUS },
      { CPC_PERCENT,    SDLK_5 | MOD_PC_SHIFT },
      { CPC_PERIOD,     SDLK_PERIOD },
      { CPC_PIPE,       SDLK_BACKSLASH | MOD_PC_SHIFT },
      { CPC_PLUS,       SDLK_EQUALS | MOD_PC_SHIFT },
      { CPC_POUND,      0 },
      { CPC_POWER,      SDLK_6 | MOD_PC_SHIFT },
      { CPC_QUESTION,   SDLK_SLASH | MOD_PC_SHIFT },
      { CPC_QUOTE,      SDLK_QUOTE },
      { CPC_RBRACKET,   SDLK_RIGHTBRACKET },
      { CPC_RCBRACE,    SDLK_RIGHTBRACKET | MOD_PC_SHIFT },
      { CPC_RETURN,     SDLK_RETURN },
      { CPC_RIGHTPAREN, SDLK_0 | MOD_PC_SHIFT },
      { CPC_RSHIFT,     SDLK_RSHIFT },
      { CPC_SEMICOLON,  SDLK_SEMICOLON },
      { CPC_SLASH,      SDLK_SLASH },
      { CPC_SPACE,      SDLK_SPACE },
      { CPC_TAB,        SDLK_TAB },
      { CPC_UNDERSCORE, SDLK_MINUS | MOD_PC_SHIFT },
      { CAP32_EXIT,     SDLK_F10 },
      { CAP32_FPS,      SDLK_F12 | MOD_PC_CTRL },
      { CAP32_FULLSCRN, SDLK_F1 },
      { CAP32_JOY,      SDLK_F8 | MOD_PC_CTRL },
      { CAP32_LOADDRVA, SDLK_F6 },
      { CAP32_LOADDRVB, SDLK_F7 },
      { CAP32_LOADSNAP, SDLK_F2 },
      { CAP32_LOADTAPE, SDLK_F3 },
      { CAP32_MF2RESET, SDLK_F5 | MOD_PC_CTRL },
      { CAP32_MF2STOP,  SDLK_F11 },
      { CAP32_OPTIONS,  SDLK_F8 },
      { CAP32_PAUSE,    SDLK_BREAK },
      { CAP32_RESET,    SDLK_F5 },
      { CAP32_SAVESNAP, SDLK_F4 },
      { CAP32_SCRNSHOT, SDLK_PRINT },
      { CAP32_SPEED,    SDLK_F12 },
      { CAP32_TAPEPLAY, SDLK_F3 | MOD_PC_CTRL }
   },
   { // French PC to CPC keyboard layout translation
      { CPC_0,          SDLK_WORLD_64 | MOD_PC_SHIFT },
      { CPC_1,          SDLK_AMPERSAND | MOD_PC_SHIFT },
      { CPC_2,          SDLK_WORLD_73 | MOD_PC_SHIFT},
      { CPC_3,          SDLK_QUOTEDBL | MOD_PC_SHIFT },
      { CPC_4,          SDLK_QUOTE | MOD_PC_SHIFT },
      { CPC_5,          SDLK_LEFTPAREN | MOD_PC_SHIFT },
      { CPC_6,          SDLK_MINUS | MOD_PC_SHIFT },
      { CPC_7,          SDLK_WORLD_72 | MOD_PC_SHIFT },
      { CPC_8,          SDLK_UNDERSCORE | MOD_PC_SHIFT },
      { CPC_9,          SDLK_WORLD_71 | MOD_PC_SHIFT },
      { CPC_A,          SDLK_a | MOD_PC_SHIFT },
      { CPC_B,          SDLK_b | MOD_PC_SHIFT },
      { CPC_C,          SDLK_c | MOD_PC_SHIFT },
      { CPC_D,          SDLK_d | MOD_PC_SHIFT },
      { CPC_E,          SDLK_e | MOD_PC_SHIFT },
      { CPC_F,          SDLK_f | MOD_PC_SHIFT },
      { CPC_G,          SDLK_g | MOD_PC_SHIFT },
      { CPC_H,          SDLK_h | MOD_PC_SHIFT },
      { CPC_I,          SDLK_i | MOD_PC_SHIFT },
      { CPC_J,          SDLK_j | MOD_PC_SHIFT },
      { CPC_K,          SDLK_k | MOD_PC_SHIFT },
      { CPC_L,          SDLK_l | MOD_PC_SHIFT },
      { CPC_M,          SDLK_m | MOD_PC_SHIFT },
      { CPC_N,          SDLK_n | MOD_PC_SHIFT },
      { CPC_O,          SDLK_o | MOD_PC_SHIFT },
      { CPC_P,          SDLK_p | MOD_PC_SHIFT },
      { CPC_Q,          SDLK_q | MOD_PC_SHIFT },
      { CPC_R,          SDLK_r | MOD_PC_SHIFT },
      { CPC_S,          SDLK_s | MOD_PC_SHIFT },
      { CPC_T,          SDLK_t | MOD_PC_SHIFT },
      { CPC_U,          SDLK_u | MOD_PC_SHIFT },
      { CPC_V,          SDLK_v | MOD_PC_SHIFT },
      { CPC_W,          SDLK_w | MOD_PC_SHIFT },
      { CPC_X,          SDLK_x | MOD_PC_SHIFT },
      { CPC_Y,          SDLK_y | MOD_PC_SHIFT },
      { CPC_Z,          SDLK_z | MOD_PC_SHIFT },
      { CPC_a,          SDLK_a },
      { CPC_b,          SDLK_b },
      { CPC_c,          SDLK_c },
      { CPC_d,          SDLK_d },
      { CPC_e,          SDLK_e },
      { CPC_f,          SDLK_f },
      { CPC_g,          SDLK_g },
      { CPC_h,          SDLK_h },
      { CPC_i,          SDLK_i },
      { CPC_j,          SDLK_j },
      { CPC_k,          SDLK_k },
      { CPC_l,          SDLK_l },
      { CPC_m,          SDLK_m },
      { CPC_n,          SDLK_n },
      { CPC_o,          SDLK_o },
      { CPC_p,          SDLK_p },
      { CPC_q,          SDLK_q },
      { CPC_r,          SDLK_r },
      { CPC_s,          SDLK_s },
      { CPC_t,          SDLK_t },
      { CPC_u,          SDLK_u },
      { CPC_v,          SDLK_v },
      { CPC_w,          SDLK_w },
      { CPC_x,          SDLK_x },
      { CPC_y,          SDLK_y },
      { CPC_z,          SDLK_z },
      { CPC_AMPERSAND,  SDLK_AMPERSAND },
      { CPC_ASTERISK,   SDLK_ASTERISK },
      { CPC_AT,         SDLK_WORLD_64 | MOD_PC_MODE },
      { CPC_BACKQUOTE,  SDLK_WORLD_73 | MOD_PC_MODE },
      { CPC_BACKSLASH,  SDLK_UNDERSCORE | MOD_PC_MODE },
      { CPC_CAPSLOCK,   SDLK_CAPSLOCK },
      { CPC_CLR,        SDLK_DELETE },
      { CPC_COLON,      SDLK_COLON },
      { CPC_COMMA,      SDLK_COMMA },
      { CPC_CONTROL,    SDLK_LCTRL },
      { CPC_COPY,       SDLK_LALT },
      { CPC_CPY_DOWN,   SDLK_DOWN | MOD_PC_SHIFT },
      { CPC_CPY_LEFT,   SDLK_LEFT | MOD_PC_SHIFT },
      { CPC_CPY_RIGHT,  SDLK_RIGHT | MOD_PC_SHIFT },
      { CPC_CPY_UP,     SDLK_UP | MOD_PC_SHIFT },
      { CPC_CUR_DOWN,   SDLK_DOWN },
      { CPC_CUR_LEFT,   SDLK_LEFT },
      { CPC_CUR_RIGHT,  SDLK_RIGHT },
      { CPC_CUR_UP,     SDLK_UP },
      { CPC_CUR_ENDBL,  SDLK_END | MOD_PC_CTRL },
      { CPC_CUR_HOMELN, SDLK_HOME },
      { CPC_CUR_ENDLN,  SDLK_END },
      { CPC_CUR_HOMEBL, SDLK_HOME | MOD_PC_CTRL },
      { CPC_DBLQUOTE,   SDLK_QUOTEDBL  },
      { CPC_DEL,        SDLK_BACKSPACE },
      { CPC_DOLLAR,     SDLK_DOLLAR },
      { CPC_ENTER,      SDLK_KP_ENTER },
      { CPC_EQUAL,      SDLK_EQUALS },
      { CPC_ESC,        SDLK_ESCAPE },
      { CPC_EXCLAMATN,  SDLK_EXCLAIM },
      { CPC_F0,         SDLK_KP0 },
      { CPC_F1,         SDLK_KP1 },
      { CPC_F2,         SDLK_KP2 },
      { CPC_F3,         SDLK_KP3 },
      { CPC_F4,         SDLK_KP4 },
      { CPC_F5,         SDLK_KP5 },
      { CPC_F6,         SDLK_KP6 },
      { CPC_F7,         SDLK_KP7 },
      { CPC_F8,         SDLK_KP8 },
      { CPC_F9,         SDLK_KP9 },
      { CPC_FR_aGRAVE,  SDLK_WORLD_64 },
      { CPC_FR_cCEDIL,  SDLK_WORLD_71 },
      { CPC_FR_eACUTE,  SDLK_WORLD_72 },
      { CPC_FR_eGRAVE,  SDLK_WORLD_73 },
      { CPC_FR_uGRAVE,  SDLK_WORLD_89 },
      { CPC_FPERIOD,    SDLK_KP_PERIOD },
      { CPC_GREATER,    SDLK_LESS | MOD_PC_SHIFT },
      { CPC_HASH,       SDLK_QUOTEDBL | MOD_PC_MODE },
      { CPC_LBRACKET,   SDLK_LEFTPAREN | MOD_PC_MODE },
      { CPC_LCBRACE,    SDLK_QUOTE | MOD_PC_MODE },
      { CPC_LEFTPAREN,  SDLK_LEFTPAREN },
      { CPC_LESS,       SDLK_LESS },
      { CPC_LSHIFT,     SDLK_LSHIFT },
      { CPC_MINUS,      SDLK_MINUS },
      { CPC_PERCENT,    SDLK_WORLD_89 | MOD_PC_SHIFT },
      { CPC_PERIOD,     SDLK_SEMICOLON | MOD_PC_SHIFT },
      { CPC_PIPE,       SDLK_MINUS | MOD_PC_MODE },
      { CPC_PLUS,       SDLK_EQUALS | MOD_PC_SHIFT },
      { CPC_POUND,      SDLK_DOLLAR | MOD_PC_SHIFT },
      { CPC_POWER,      SDLK_CARET },
      { CPC_QUESTION,   SDLK_COMMA | MOD_PC_SHIFT },
      { CPC_QUOTE,      SDLK_QUOTE },
      { CPC_RBRACKET,   SDLK_RIGHTPAREN | MOD_PC_MODE },
      { CPC_RCBRACE,    SDLK_EQUALS | MOD_PC_MODE },
      { CPC_RETURN,     SDLK_RETURN },
      { CPC_RIGHTPAREN, SDLK_RIGHTPAREN },
      { CPC_RSHIFT,     SDLK_RSHIFT },
      { CPC_SEMICOLON,  SDLK_SEMICOLON },
      { CPC_SLASH,      SDLK_COLON | MOD_PC_SHIFT },
      { CPC_SPACE,      SDLK_SPACE },
      { CPC_TAB,        SDLK_TAB },
      { CPC_UNDERSCORE, SDLK_UNDERSCORE },
      { CAP32_EXIT,     SDLK_F10 },
      { CAP32_FPS,      SDLK_F12 | MOD_PC_CTRL },
      { CAP32_FULLSCRN, SDLK_F1 },
      { CAP32_JOY,      SDLK_F8 | MOD_PC_CTRL },
      { CAP32_LOADDRVA, SDLK_F6 },
      { CAP32_LOADDRVB, SDLK_F7 },
      { CAP32_LOADSNAP, SDLK_F2 },
      { CAP32_LOADTAPE, SDLK_F3 },
      { CAP32_MF2RESET, SDLK_F5 | MOD_PC_CTRL },
      { CAP32_MF2STOP,  SDLK_F11 },
      { CAP32_OPTIONS,  SDLK_F8 },
      { CAP32_PAUSE,    SDLK_BREAK },
      { CAP32_RESET,    SDLK_F5 },
      { CAP32_SAVESNAP, SDLK_F4 },
      { CAP32_SCRNSHOT, SDLK_PRINT },
      { CAP32_SPEED,    SDLK_F12 },
      { CAP32_TAPEPLAY, SDLK_F3 | MOD_PC_CTRL }
   },
   { // Spanish PC to CPC keyboard layout translation
      { CPC_0,          SDLK_0 },
      { CPC_1,          SDLK_1 },
      { CPC_2,          SDLK_2 },
      { CPC_3,          SDLK_3 },
      { CPC_4,          SDLK_4 },
      { CPC_5,          SDLK_5 },
      { CPC_6,          SDLK_6 },
      { CPC_7,          SDLK_7 },
      { CPC_8,          SDLK_8 },
      { CPC_9,          SDLK_9 },
      { CPC_A,          SDLK_a | MOD_PC_SHIFT },
      { CPC_B,          SDLK_b | MOD_PC_SHIFT },
      { CPC_C,          SDLK_c | MOD_PC_SHIFT },
      { CPC_D,          SDLK_d | MOD_PC_SHIFT },
      { CPC_E,          SDLK_e | MOD_PC_SHIFT },
      { CPC_F,          SDLK_f | MOD_PC_SHIFT },
      { CPC_G,          SDLK_g | MOD_PC_SHIFT },
      { CPC_H,          SDLK_h | MOD_PC_SHIFT },
      { CPC_I,          SDLK_i | MOD_PC_SHIFT },
      { CPC_J,          SDLK_j | MOD_PC_SHIFT },
      { CPC_K,          SDLK_k | MOD_PC_SHIFT },
      { CPC_L,          SDLK_l | MOD_PC_SHIFT },
      { CPC_M,          SDLK_m | MOD_PC_SHIFT },
      { CPC_N,          SDLK_n | MOD_PC_SHIFT },
      { CPC_O,          SDLK_o | MOD_PC_SHIFT },
      { CPC_P,          SDLK_p | MOD_PC_SHIFT },
      { CPC_Q,          SDLK_q | MOD_PC_SHIFT },
      { CPC_R,          SDLK_r | MOD_PC_SHIFT },
      { CPC_S,          SDLK_s | MOD_PC_SHIFT },
      { CPC_T,          SDLK_t | MOD_PC_SHIFT },
      { CPC_U,          SDLK_u | MOD_PC_SHIFT },
      { CPC_V,          SDLK_v | MOD_PC_SHIFT },
      { CPC_W,          SDLK_w | MOD_PC_SHIFT },
      { CPC_X,          SDLK_x | MOD_PC_SHIFT },
      { CPC_Y,          SDLK_y | MOD_PC_SHIFT },
      { CPC_Z,          SDLK_z | MOD_PC_SHIFT },
      { CPC_a,          SDLK_a },
      { CPC_b,          SDLK_b },
      { CPC_c,          SDLK_c },
      { CPC_d,          SDLK_d },
      { CPC_e,          SDLK_e },
      { CPC_f,          SDLK_f },
      { CPC_g,          SDLK_g },
      { CPC_h,          SDLK_h },
      { CPC_i,          SDLK_i },
      { CPC_j,          SDLK_j },
      { CPC_k,          SDLK_k },
      { CPC_l,          SDLK_l },
      { CPC_m,          SDLK_m },
      { CPC_n,          SDLK_n },
      { CPC_o,          SDLK_o },
      { CPC_p,          SDLK_p },
      { CPC_q,          SDLK_q },
      { CPC_r,          SDLK_r },
      { CPC_s,          SDLK_s },
      { CPC_t,          SDLK_t },
      { CPC_u,          SDLK_u },
      { CPC_v,          SDLK_v },
      { CPC_w,          SDLK_w },
      { CPC_x,          SDLK_x },
      { CPC_y,          SDLK_y },
      { CPC_z,          SDLK_z },
      { CPC_AMPERSAND,  SDLK_7 | MOD_PC_SHIFT },
      { CPC_ASTERISK,   SDLK_8 | MOD_PC_SHIFT },
      { CPC_AT,         SDLK_2 | MOD_PC_SHIFT },
      { CPC_BACKQUOTE,  SDLK_BACKQUOTE },
      { CPC_BACKSLASH,  SDLK_BACKSLASH },
      { CPC_CAPSLOCK,   SDLK_CAPSLOCK },
      { CPC_CLR,        SDLK_DELETE },
      { CPC_COLON,      SDLK_SEMICOLON | MOD_PC_SHIFT },
      { CPC_COMMA,      SDLK_COMMA },
      { CPC_CONTROL,    SDLK_LCTRL },
      { CPC_COPY,       SDLK_LALT },
      { CPC_CPY_DOWN,   SDLK_DOWN | MOD_PC_SHIFT },
      { CPC_CPY_LEFT,   SDLK_LEFT | MOD_PC_SHIFT },
      { CPC_CPY_RIGHT,  SDLK_RIGHT | MOD_PC_SHIFT },
      { CPC_CPY_UP,     SDLK_UP | MOD_PC_SHIFT },
      { CPC_CUR_DOWN,   SDLK_DOWN },
      { CPC_CUR_LEFT,   SDLK_LEFT },
      { CPC_CUR_RIGHT,  SDLK_RIGHT },
      { CPC_CUR_UP,     SDLK_UP },
      { CPC_CUR_ENDBL,  SDLK_END | MOD_PC_CTRL },
      { CPC_CUR_HOMELN, SDLK_HOME },
      { CPC_CUR_ENDLN,  SDLK_END },
      { CPC_CUR_HOMEBL, SDLK_HOME | MOD_PC_CTRL },
      { CPC_DBLQUOTE,   SDLK_QUOTE | MOD_PC_SHIFT },
      { CPC_DEL,        SDLK_BACKSPACE },
      { CPC_DOLLAR,     SDLK_4 | MOD_PC_SHIFT },
      { CPC_ENTER,      SDLK_KP_ENTER },
      { CPC_EQUAL,      SDLK_EQUALS },
      { CPC_ESC,        SDLK_ESCAPE },
      { CPC_EXCLAMATN,  SDLK_1 | MOD_PC_SHIFT },
      { CPC_F0,         SDLK_KP0 },
      { CPC_F1,         SDLK_KP1 },
      { CPC_F2,         SDLK_KP2 },
      { CPC_F3,         SDLK_KP3 },
      { CPC_F4,         SDLK_KP4 },
      { CPC_F5,         SDLK_KP5 },
      { CPC_F6,         SDLK_KP6 },
      { CPC_F7,         SDLK_KP7 },
      { CPC_F8,         SDLK_KP8 },
      { CPC_F9,         SDLK_KP9 },
      { CPC_FPERIOD,    SDLK_KP_PERIOD },
      { CPC_GREATER,    SDLK_PERIOD | MOD_PC_SHIFT },
      { CPC_HASH,       SDLK_3 | MOD_PC_SHIFT },
      { CPC_LBRACKET,   SDLK_LEFTBRACKET },
      { CPC_LCBRACE,    SDLK_LEFTBRACKET | MOD_PC_SHIFT },
      { CPC_LEFTPAREN,  SDLK_9 | MOD_PC_SHIFT },
      { CPC_LESS,       SDLK_COMMA | MOD_PC_SHIFT },
      { CPC_LSHIFT,     SDLK_LSHIFT },
      { CPC_MINUS,      SDLK_MINUS },
      { CPC_PERCENT,    SDLK_5 | MOD_PC_SHIFT },
      { CPC_PERIOD,     SDLK_PERIOD },
      { CPC_PIPE,       SDLK_BACKSLASH | MOD_PC_SHIFT },
      { CPC_PLUS,       SDLK_EQUALS | MOD_PC_SHIFT },
      { CPC_POUND,      0 },
      { CPC_POWER,      SDLK_6 | MOD_PC_SHIFT },
      { CPC_QUESTION,   SDLK_SLASH | MOD_PC_SHIFT },
      { CPC_QUOTE,      SDLK_QUOTE },
      { CPC_RBRACKET,   SDLK_RIGHTBRACKET },
      { CPC_RCBRACE,    SDLK_RIGHTBRACKET | MOD_PC_SHIFT },
      { CPC_RETURN,     SDLK_RETURN },
      { CPC_RIGHTPAREN, SDLK_0 | MOD_PC_SHIFT },
      { CPC_RSHIFT,     SDLK_RSHIFT },
      { CPC_SEMICOLON,  SDLK_SEMICOLON },
      { CPC_SLASH,      SDLK_SLASH },
      { CPC_SPACE,      SDLK_SPACE },
      { CPC_TAB,        SDLK_TAB },
      { CPC_UNDERSCORE, SDLK_MINUS | MOD_PC_SHIFT },
      { CAP32_EXIT,     SDLK_F10 },
      { CAP32_FPS,      SDLK_F12 | MOD_PC_CTRL },
      { CAP32_FULLSCRN, SDLK_F1 },
      { CAP32_JOY,      SDLK_F8 | MOD_PC_CTRL },
      { CAP32_LOADDRVA, SDLK_F6 },
      { CAP32_LOADDRVB, SDLK_F7 },
      { CAP32_LOADSNAP, SDLK_F2 },
      { CAP32_LOADTAPE, SDLK_F3 },
      { CAP32_MF2RESET, SDLK_F5 | MOD_PC_CTRL },
      { CAP32_MF2STOP,  SDLK_F11 },
      { CAP32_OPTIONS,  SDLK_F8 },
      { CAP32_PAUSE,    SDLK_BREAK },
      { CAP32_RESET,    SDLK_F5 },
      { CAP32_SAVESNAP, SDLK_F4 },
      { CAP32_SCRNSHOT, SDLK_PRINT },
      { CAP32_SPEED,    SDLK_F12 },
      { CAP32_TAPEPLAY, SDLK_F3 | MOD_PC_CTRL }
   }
};
 
static dword keyboard_normal[SDLK_LAST];
static dword keyboard_shift[SDLK_LAST];
static dword keyboard_ctrl[SDLK_LAST];
static dword keyboard_mode[SDLK_LAST];
 
static int joy_layout[12][2] = {
   { CPC_J0_UP,      SDLK_UP },
   { CPC_J0_DOWN,    SDLK_DOWN },
   { CPC_J0_LEFT,    SDLK_LEFT },
   { CPC_J0_RIGHT,   SDLK_RIGHT },
   { CPC_J0_FIRE1,   SDLK_z },
   { CPC_J0_FIRE2,   SDLK_x },
   { CPC_J1_UP,      0 },
   { CPC_J1_DOWN,    0 },
   { CPC_J1_LEFT,    0 },
   { CPC_J1_RIGHT,   0 },
   { CPC_J1_FIRE1,   0 },
   { CPC_J1_FIRE2,   0 }
};
 
#define MAX_ROM_MODS 2
#include "rom_mods.c"
 
char chAppPath[_MAX_PATH + 1];
char chROMSelected[_MAX_PATH + 1];
char chROMFile[3][14] = {
   "cpc464.rom",
   "cpc664.rom",
   "cpc6128.rom"
};
 
t_CPC CPC;
t_CRTC CRTC;
t_FDC FDC;
t_GateArray GateArray;
t_PPI PPI;
t_PSG PSG;
t_VDU VDU;
 
t_drive driveA;
t_drive driveB;
 
t_zip_info zip_info;
 
#define MAX_DISK_FORMAT 8
#define DEFAULT_DISK_FORMAT 0
#define FIRST_CUSTOM_DISK_FORMAT 2
t_disk_format disk_format[MAX_DISK_FORMAT] = {
   { "178K Data Format", 40, 1, 9, 2, 0x52, 0xe5, {{ 0xc1, 0xc6, 0xc2, 0xc7, 0xc3, 0xc8, 0xc4, 0xc9, 0xc5 }} },
   { "169K Vendor Format", 40, 1, 9, 2, 0x52, 0xe5, {{ 0x41, 0x46, 0x42, 0x47, 0x43, 0x48, 0x44, 0x49, 0x45 }} }
};
 
 
 
#define psg_write \
{ \
   byte control = PSG.control & 0xc0; /* isolate PSG control bits */ \
   if (control == 0xc0) { /* latch address? */ \
      PSG.reg_select = psg_data; /* select new PSG register */ \
   } else if (control == 0x80) { /* write? */ \
      if (PSG.reg_select < 16) { /* valid register? */ \
         SetAYRegister(PSG.reg_select, psg_data); \
      } \
   } \
}
 
 
 
void ga_init_banking (void)
{
   byte *romb0, *romb1, *romb2, *romb3, *romb4, *romb5, *romb6, *romb7;
   byte *pbRAMbank;
 
   romb0 = pbRAM;
   romb1 = pbRAM + 1*16384;
   romb2 = pbRAM + 2*16384;
   romb3 = pbRAM + 3*16384;
 
   pbRAMbank = pbRAM + ((GateArray.RAM_bank + 1) * 65536);
   romb4 = pbRAMbank;
   romb5 = pbRAMbank + 1*16384;
   romb6 = pbRAMbank + 2*16384;
   romb7 = pbRAMbank + 3*16384;
 
   membank_config[0][0] = romb0;
   membank_config[0][1] = romb1;
   membank_config[0][2] = romb2;
   membank_config[0][3] = romb3;
 
   membank_config[1][0] = romb0;
   membank_config[1][1] = romb1;
   membank_config[1][2] = romb2;
   membank_config[1][3] = romb7;
 
   membank_config[2][0] = romb4;
   membank_config[2][1] = romb5;
   membank_config[2][2] = romb6;
   membank_config[2][3] = romb7;
 
   membank_config[3][0] = romb0;
   membank_config[3][1] = romb3;
   membank_config[3][2] = romb2;
   membank_config[3][3] = romb7;
 
   membank_config[4][0] = romb0;
   membank_config[4][1] = romb4;
   membank_config[4][2] = romb2;
   membank_config[4][3] = romb3;
 
   membank_config[5][0] = romb0;
   membank_config[5][1] = romb5;
   membank_config[5][2] = romb2;
   membank_config[5][3] = romb3;
 
   membank_config[6][0] = romb0;
   membank_config[6][1] = romb6;
   membank_config[6][2] = romb2;
   membank_config[6][3] = romb3;
 
   membank_config[7][0] = romb0;
   membank_config[7][1] = romb7;
   membank_config[7][2] = romb2;
   membank_config[7][3] = romb3;
}
 
 
 
void ga_memory_manager (void)
{
   dword mem_bank;
   if (CPC.ram_size == 64) { // 64KB of RAM?
      mem_bank = 0; // no expansion memory
      GateArray.RAM_config = 0; // the only valid configuration is 0
   } else {
      mem_bank = (GateArray.RAM_config >> 3) & 7; // extract expansion memory bank
      if (((mem_bank+2)*64) > CPC.ram_size) { // selection is beyond available memory?
         mem_bank = 0; // force default mapping
      }
   }
   if (mem_bank != GateArray.RAM_bank) { // requested bank is different from the active one?
      GateArray.RAM_bank = mem_bank;
      ga_init_banking();
   }
   for (int n = 0; n < 4; n++) { // remap active memory banks
      membank_read[n] = membank_config[GateArray.RAM_config & 7][n];
      membank_write[n] = membank_config[GateArray.RAM_config & 7][n];
   }
   if (!(GateArray.ROM_config & 0x04)) { // lower ROM is enabled?
      if (dwMF2Flags & MF2_ACTIVE) { // is the Multiface 2 paged in?
         membank_read[0] = pbMF2ROM;
         membank_write[0] = pbMF2ROM;
      } else {
         membank_read[0] = pbROMlo; // 'page in' lower ROM
      }
   }
   if (!(GateArray.ROM_config & 0x08)) { // upper/expansion ROM is enabled?
      membank_read[3] = pbExpansionROM; // 'page in' upper/expansion ROM
   }
}
 
 
 
byte z80_IN_handler (reg_pair port)
{
   byte ret_val;
 
   ret_val = 0xff; // default return value
// CRTC -----------------------------------------------------------------------
   if (!(port.b.h & 0x40)) { // CRTC chip select?
      if ((port.b.h & 3) == 3) { // read CRTC register?
         if ((CRTC.reg_select > 11) && (CRTC.reg_select < 18)) { // valid range?
            ret_val = CRTC.registers[CRTC.reg_select];
         }
         else {
            ret_val = 0; // write only registers return 0
         }
      }
   }
// PPI ------------------------------------------------------------------------
   else if (!(port.b.h & 0x08)) { // PPI chip select?
      byte ppi_port = port.b.h & 3;
      switch (ppi_port) {
         case 0: // read from port A?
            if (PPI.control & 0x10) { // port A set to input?
               if ((PSG.control & 0xc0) == 0x40) { // PSG control set to read?
                  if (PSG.reg_select < 16) { // within valid range?
                     if (PSG.reg_select == 14) { // PSG port A?
                        if (!(PSG.RegisterAY.Index[7] & 0x40)) { // port A in input mode?
                           ret_val = keyboard_matrix[CPC.keyboard_line & 0x0f]; // read keyboard matrix node status
                        } else {
                           ret_val = PSG.RegisterAY.Index[14] & (keyboard_matrix[CPC.keyboard_line & 0x0f]); // return last value w/ logic AND of input
                        }
                     } else if (PSG.reg_select == 15) { // PSG port B?
                        if ((PSG.RegisterAY.Index[7] & 0x80)) { // port B in output mode?
                           ret_val = PSG.RegisterAY.Index[15]; // return stored value
                        }
                     } else {
                        ret_val = PSG.RegisterAY.Index[PSG.reg_select]; // read PSG register
                     }
                  }
               }
            } else {
               ret_val = PPI.portA; // return last programmed value
            }
            break;
 
         case 1: // read from port B?
            if (PPI.control & 2) { // port B set to input?
               ret_val = bTapeLevel | // tape level when reading
                         (CPC.printer ? 0 : 0x40) | // ready line of connected printer
                         (CPC.jumpers & 0x7f) | // manufacturer + 50Hz
								 (CRTC.flag_invsync ? 1 : 0); // VSYNC status
            } else {
               ret_val = PPI.portB; // return last programmed value
            }
            break;
 
         case 2: // read from port C?
            byte direction = PPI.control & 9; // isolate port C directions
            ret_val = PPI.portC; // default to last programmed value
            if (direction) { // either half set to input?
               if (direction & 8) { // upper half set to input?
                  ret_val &= 0x0f; // blank out upper half
                  byte val = PPI.portC & 0xc0; // isolate PSG control bits
                  if (val == 0xc0) { // PSG specify register?
                     val = 0x80; // change to PSG write register
                  }
                  ret_val |= val | 0x20; // casette write data is always set
                  if (CPC.tape_motor) {
                     ret_val |= 0x10; // set the bit if the tape motor is running
                  }
               }
               if (!(direction & 1)) { // lower half set to output?
                  ret_val |= 0x0f; // invalid - set all bits
               }
            }
            break;
      }
   }
// ----------------------------------------------------------------------------
   else if (!(port.b.h & 0x04)) { // external peripheral?
      if ((port.b.h == 0xfb) && (!(port.b.l & 0x80))) { // FDC?
         if (!(port.b.l & 0x01)) { // FDC status register?
            ret_val = fdc_read_status();
         } else { // FDC data register
            ret_val = fdc_read_data();
         }
      }
   }
   return ret_val;
}
 
 
 
void z80_OUT_handler (reg_pair port, byte val)
{
// Gate Array -----------------------------------------------------------------
   if ((port.b.h & 0xc0) == 0x40) { // GA chip select?
      switch (val >> 6) {
         case 0: // select pen
            #ifdef DEBUG_GA
            if (dwDebugFlag) {
               fprintf(pfoDebug, "pen 0x%02x\r\n", val);
            }
            #endif
            GateArray.pen = val & 0x10 ? 0x10 : val & 0x0f; // if bit 5 is set, pen indexes the border colour
            if (CPC.mf2) { // MF2 enabled?
               *(pbMF2ROM + 0x03fcf) = val;
            }
            break;
         case 1: // set colour
            #ifdef DEBUG_GA
            if (dwDebugFlag) {
               fprintf(pfoDebug, "clr 0x%02x\r\n", val);
            }
            #endif
            {
               byte colour = val & 0x1f; // isolate colour value
               GateArray.ink_values[GateArray.pen] = colour;
               GateArray.palette[GateArray.pen] = SDL_MapRGB(back_surface->format,
                colours[colour].r, colours[colour].g, colours[colour].b);
               if (GateArray.pen < 2) {
                  byte r = ((dword)colours[GateArray.ink_values[0]].r + (dword)colours[GateArray.ink_values[1]].r) >> 1;
                  byte g = ((dword)colours[GateArray.ink_values[0]].g + (dword)colours[GateArray.ink_values[1]].g) >> 1;
                  byte b = ((dword)colours[GateArray.ink_values[0]].b + (dword)colours[GateArray.ink_values[1]].b) >> 1;
                  GateArray.palette[18] = SDL_MapRGB(back_surface->format, r, g, b); // update the mode 2 'anti-aliasing' colour
               }
            }
            if (CPC.mf2) { // MF2 enabled?
               int iPen = *(pbMF2ROM + 0x03fcf);
               *(pbMF2ROM + (0x03f90 | ((iPen & 0x10) << 2) | (iPen & 0x0f))) = val;
            }
            break;
         case 2: // set mode
            #ifdef DEBUG_GA
            if (dwDebugFlag) {
               fprintf(pfoDebug, "rom 0x%02x\r\n", val);
            }
            #endif
            GateArray.ROM_config = val;
            GateArray.requested_scr_mode = val & 0x03; // request a new CPC screen mode
            ga_memory_manager();
            if (val & 0x10) { // delay Z80 interrupt?
               z80.int_pending = 0; // clear pending interrupts
               GateArray.sl_count = 0; // reset GA scanline counter
            }
            if (CPC.mf2) { // MF2 enabled?
               *(pbMF2ROM + 0x03fef) = val;
            }
            break;
         case 3: // set memory configuration
            #ifdef DEBUG_GA
            if (dwDebugFlag) {
               fprintf(pfoDebug, "mem 0x%02x\r\n", val);
            }
            #endif
            GateArray.RAM_config = val;
            ga_memory_manager();
            if (CPC.mf2) { // MF2 enabled?
               *(pbMF2ROM + 0x03fff) = val;
            }
            break;
      }
   }
// CRTC -----------------------------------------------------------------------
   if (!(port.b.h & 0x40)) { // CRTC chip select?
      byte crtc_port = port.b.h & 3;
      if (crtc_port == 0) { // CRTC register select?
         CRTC.reg_select = val;
         if (CPC.mf2) { // MF2 enabled?
            *(pbMF2ROM + 0x03cff) = val;
         }
      }
      else if (crtc_port == 1) { // CRTC write data?
         if (CRTC.reg_select < 16) { // only registers 0 - 15 can be written to
            switch (CRTC.reg_select) {
               case 0: // horizontal total
                  CRTC.registers[0] = val;
                  break;
               case 1: // horizontal displayed
                  CRTC.registers[1] = val;
                  update_skew();
                  break;
               case 2: // horizontal sync position
                  CRTC.registers[2] = val;
                  break;
               case 3: // sync width
                  CRTC.registers[3] = val;
                  CRTC.hsw = val & 0x0f; // isolate horizontal sync width
                  CRTC.vsw = val >> 4; // isolate vertical sync width
                  break;
               case 4: // vertical total
                  CRTC.registers[4] = val & 0x7f;
                  if (CRTC.CharInstMR == (void(*)(void))CharMR2) {
                     if (CRTC.line_count == CRTC.registers[4]) { // matches vertical total?
                        if (CRTC.raster_count == CRTC.registers[9]) { // matches maximum raster address?
                           CRTC.flag_startvta = 1;
                        }
                     }
                  }
                  break;
               case 5: // vertical total adjust
                  CRTC.registers[5] = val & 0x1f;
                  break;
               case 6: // vertical displayed
                  CRTC.registers[6] = val & 0x7f;
                  if (CRTC.line_count == CRTC.registers[6]) { // matches vertical displayed?
                     new_dt.NewDISPTIMG = 0;
                  }
                  break;
               case 7: // vertical sync position
                  CRTC.registers[7] = val & 0x7f;
                  {
                     register dword temp = 0;
                     if (CRTC.line_count == CRTC.registers[7]) { // matches vertical sync position?
                        temp++;
                        if (CRTC.r7match != temp) {
                           CRTC.r7match = temp;
                           if (CRTC.char_count >= 2) {
                              CRTC.flag_resvsync = 0;
                              if (!CRTC.flag_invsync) {
                                 CRTC.vsw_count = 0;
                                 CRTC.flag_invsync = 1;
                                 flags1.monVSYNC = 26;
                                 GateArray.hs_count = 2; // GA delays its VSYNC by two CRTC HSYNCs
                              }
                           }
                        }
                     }
                     else {
                        CRTC.r7match = 0;
                     }
                  }
                  break;
               case 8: // interlace and skew
                  CRTC.registers[8] = val;
                  update_skew();
                  break;
               case 9: // maximum raster count
                  CRTC.registers[9] = val & 0x1f;
                  {
                     register dword temp = 0;
                     if (CRTC.raster_count == CRTC.registers[9]) { // matches maximum raster address?
                        temp = 1;
                        CRTC.flag_resscan = 1; // request a raster counter reset
                     }
                     if (CRTC.r9match != temp) {
                        CRTC.r9match = temp;
                        if (temp) {
                           CRTC.CharInstMR = (void(*)(void))CharMR1;
                        }
                     }
                     if (CRTC.raster_count == CRTC.registers[9]) { // matches maximum raster address?
                        if (CRTC.char_count == CRTC.registers[1]) {
                           CRTC.next_addr = CRTC.addr + CRTC.char_count;
                        }
                        if (CRTC.char_count == CRTC.registers[0]) { // matches horizontal total?
                           CRTC.flag_reschar = 1; // request a line count update
                        }
                        if (!CRTC.flag_startvta) {
                           CRTC.flag_resscan = 1;
                        }
                     } else {
                        if (!CRTC.flag_invta) { // not in vertical total adjust?
                           CRTC.flag_resscan = 0;
                        }
                     }
                  }
                  break;
               case 10: // cursor start raster
                  CRTC.registers[10] = val & 0x7f;
                  break;
               case 11: // cursor end raster
                  CRTC.registers[11] = val & 0x1f;
                  break;
               case 12: // start address high byte
                  CRTC.registers[12] = val & 0x3f;
                  CRTC.requested_addr = CRTC.registers[13] + (CRTC.registers[12] << 8);
                  break;
               case 13: // start address low byte
                  CRTC.registers[13] = val;
                  CRTC.requested_addr = CRTC.registers[13] + (CRTC.registers[12] << 8);
                  break;
               case 14: // cursor address high byte
                  CRTC.registers[14] = val & 0x3f;
                  break;
               case 15: // cursor address low byte
                  CRTC.registers[15] = val;
                  break;
            }
         }
         if (CPC.mf2) { // MF2 enabled?
            *(pbMF2ROM + (0x03db0 | (*(pbMF2ROM + 0x03cff) & 0x0f))) = val;
         }
         #ifdef DEBUG_CRTC
         if (dwDebugFlag) {
            fprintf(pfoDebug, "%02x = %02x\r\n", CRTC.reg_select, val);
         }
         #endif
      }
   }
// ROM select -----------------------------------------------------------------
   if (!(port.b.h & 0x20)) { // ROM select?
      GateArray.upper_ROM = val;
      pbExpansionROM = memmap_ROM[val];
      if (pbExpansionROM == NULL) { // selected expansion ROM not present?
         pbExpansionROM = pbROMhi; // revert to BASIC ROM
      }
      if (!(GateArray.ROM_config & 0x08)) { // upper/expansion ROM is enabled?
         membank_read[3] = pbExpansionROM; // 'page in' upper/expansion ROM
      }
      if (CPC.mf2) { // MF2 enabled?
         *(pbMF2ROM + 0x03aac) = val;
      }
   }
// printer port ---------------------------------------------------------------
   if (!(port.b.h & 0x10)) { // printer port?
      CPC.printer_port = val ^ 0x80; // invert bit 7
      if (pfoPrinter) {
         if (!(CPC.printer_port & 0x80)) { // only grab data bytes; ignore the strobe signal
            fputc(CPC.printer_port, pfoPrinter); // capture printer output to file
         }
      }
   }
// PPI ------------------------------------------------------------------------
   if (!(port.b.h & 0x08)) { // PPI chip select?
      switch (port.b.h & 3) {
         case 0: // write to port A?
            PPI.portA = val;
            if (!(PPI.control & 0x10)) { // port A set to output?
               byte psg_data = val;
               psg_write
            }
            break;
         case 1: // write to port B?
            PPI.portB = val;
            break;
         case 2: // write to port C?
            PPI.portC = val;
            if (!(PPI.control & 1)) { // output lower half?
               CPC.keyboard_line = val;
            }
            if (!(PPI.control & 8)) { // output upper half?
               CPC.tape_motor = val & 0x10; // update tape motor control
               PSG.control = val; // change PSG control
               byte psg_data = PPI.portA;
               psg_write
            }
            break;
         case 3: // modify PPI control
            if (val & 0x80) { // change PPI configuration
               PPI.control = val; // update control byte
               PPI.portA = 0; // clear data for all ports
               PPI.portB = 0;
               PPI.portC = 0;
            } else { // bit manipulation of port C data
               if (val & 1) { // set bit?
                  byte bit = (val >> 1) & 7; // isolate bit to set
                  PPI.portC |= bit_values[bit]; // set requested bit
                  if (!(PPI.control & 1)) { // output lower half?
                     CPC.keyboard_line = PPI.portC;
                  }
                  if (!(PPI.control & 8)) { // output upper half?
                     CPC.tape_motor = PPI.portC & 0x10;
                     PSG.control = PPI.portC; // change PSG control
                     byte psg_data = PPI.portA;
                     psg_write
                  }
               } else {
                  byte bit = (val >> 1) & 7; // isolate bit to reset
                  PPI.portC &= ~(bit_values[bit]); // reset requested bit
                  if (!(PPI.control & 1)) { // output lower half?
                     CPC.keyboard_line = PPI.portC;
                  }
                  if (!(PPI.control & 8)) { // output upper half?
                     CPC.tape_motor = PPI.portC & 0x10;
                     PSG.control = PPI.portC; // change PSG control
                     byte psg_data = PPI.portA;
                     psg_write
                  }
               }
            }
            if (CPC.mf2) { // MF2 enabled?
               *(pbMF2ROM + 0x037ff) = val;
            }
            break;
      }
   }
// ----------------------------------------------------------------------------
   if ((port.b.h == 0xfa) && (!(port.b.l & 0x80))) { // floppy motor control?
      FDC.motor = val & 0x01;
      #ifdef DEBUG_FDC
      fputs(FDC.motor ? "\r\n--- motor on" : "\r\n--- motor off", pfoDebug);
      #endif
      FDC.flags |= STATUSDRVA_flag | STATUSDRVB_flag;
   }
   else if ((port.b.h == 0xfb) && (!(port.b.l & 0x80))) { // FDC data register?
      fdc_write_data(val);
   }
   else if ((CPC.mf2) && (port.b.h == 0xfe)) { // Multiface 2?
      if ((port.b.l == 0xe8) && (!(dwMF2Flags & MF2_INVISIBLE))) { // page in MF2 ROM?
         dwMF2Flags |= MF2_ACTIVE;
         ga_memory_manager();
      }
      else if (port.b.l == 0xea) { // page out MF2 ROM?
         dwMF2Flags &= ~MF2_ACTIVE;
         ga_memory_manager();
      }
   }
}
 
 
 
void print (dword *pdwAddr, char *pchStr, bool bolColour)
{
   int iLen, iIdx, iRow, iCol;
   dword dwColour;
   word wColour;
   byte bRow, bColour;
 
   switch (CPC.scr_bpp)
   {
      case 32:
         dwColour = bolColour ? 0x00ffffff : 0;
         iLen = strlen(pchStr); // number of characters to process
         for (int n = 0; n < iLen; n++) {
            dword *pdwLine, *pdwPixel;
            iIdx = (int)pchStr[n]; // get the ASCII value
            if ((iIdx < FNT_MIN_CHAR) || (iIdx > FNT_MAX_CHAR)) { // limit it to the range of chars in the font
               iIdx = FNT_BAD_CHAR;
            }
            iIdx -= FNT_MIN_CHAR; // zero base the index
            pdwLine = pdwAddr; // keep a reference to the current screen position
            for (iRow = 0; iRow < FNT_CHAR_HEIGHT; iRow++) { // loop for all rows in the font character
               pdwPixel = pdwLine;
               bRow = bFont[iIdx]; // get the bitmap information for one row
               for (iCol = 0; iCol < FNT_CHAR_WIDTH; iCol++) { // loop for all columns in the font character
                  if (bRow & 0x80) { // is the bit set?
                     *(pdwPixel+1) = 0; // draw the "shadow"
                     *(pdwPixel+CPC.scr_line_offs) = 0;
                     *(pdwPixel+CPC.scr_line_offs+1) = 0;
                     *pdwPixel = dwColour; // draw the character pixel
                  }
                  pdwPixel++; // update the screen position
                  bRow <<= 1; // advance to the next bit
               }
               pdwLine += CPC.scr_line_offs; // advance to next screen line
               iIdx += FNT_CHARS; // advance to next row in font data
            }
            pdwAddr += FNT_CHAR_WIDTH; // set screen address to next character position
         }
         break;
 
      case 24:
         dwColour = bolColour ? 0x00ffffff : 0;
         iLen = strlen(pchStr); // number of characters to process
         for (int n = 0; n < iLen; n++) {
            dword *pdwLine;
            byte *pbPixel;
            iIdx = (int)pchStr[n]; // get the ASCII value
            if ((iIdx < FNT_MIN_CHAR) || (iIdx > FNT_MAX_CHAR)) { // limit it to the range of chars in the font
               iIdx = FNT_BAD_CHAR;
            }
            iIdx -= FNT_MIN_CHAR; // zero base the index
            pdwLine = pdwAddr; // keep a reference to the current screen position
            for (iRow = 0; iRow < FNT_CHAR_HEIGHT; iRow++) { // loop for all rows in the font character
               pbPixel = (byte *)pdwLine;
               bRow = bFont[iIdx]; // get the bitmap information for one row
               for (iCol = 0; iCol < FNT_CHAR_WIDTH; iCol++) { // loop for all columns in the font character
                  if (bRow & 0x80) { // is the bit set?
                     *((dword *)pbPixel+CPC.scr_line_offs) = 0; // draw the "shadow"
                     *(dword *)pbPixel = dwColour; // draw the character pixel
                  }
                  pbPixel += 3; // update the screen position
                  bRow <<= 1; // advance to the next bit
               }
               pdwLine += CPC.scr_line_offs; // advance to next screen line
               iIdx += FNT_CHARS; // advance to next row in font data
            }
            pdwAddr += FNT_CHAR_WIDTH-2; // set screen address to next character position
         }
         break;
 
      case 15:
      case 16:
         wColour = bolColour ? 0xffff : 0;
         iLen = strlen(pchStr); // number of characters to process
         for (int n = 0; n < iLen; n++) {
            dword *pdwLine;
            word *pwPixel;
            iIdx = (int)pchStr[n]; // get the ASCII value
            if ((iIdx < FNT_MIN_CHAR) || (iIdx > FNT_MAX_CHAR)) { // limit it to the range of chars in the font
               iIdx = FNT_BAD_CHAR;
            }
            iIdx -= FNT_MIN_CHAR; // zero base the index
            pdwLine = pdwAddr; // keep a reference to the current screen position
            for (iRow = 0; iRow < FNT_CHAR_HEIGHT; iRow++) { // loop for all rows in the font character
               pwPixel = (word *)pdwLine;
               bRow = bFont[iIdx]; // get the bitmap information for one row
               for (iCol = 0; iCol < FNT_CHAR_WIDTH; iCol++) { // loop for all columns in the font character
                  if (bRow & 0x80) { // is the bit set?
                     *(pwPixel+1) = 0; // draw the "shadow"
                     *(word *)((dword *)pwPixel+CPC.scr_line_offs) = 0;
                     *((word *)((dword *)pwPixel+CPC.scr_line_offs)+1) = 0;
                     *pwPixel = wColour; // draw the character pixel
                  }
                  pwPixel++; // update the screen position
                  bRow <<= 1; // advance to the next bit
               }
               pdwLine += CPC.scr_line_offs; // advance to next screen line
               iIdx += FNT_CHARS; // advance to next row in font data
            }
            pdwAddr += FNT_CHAR_WIDTH/2; // set screen address to next character position
         }
         break;
 
      case 8:
         bColour = bolColour ? SDL_MapRGB(back_surface->format,255,255,255) : SDL_MapRGB(back_surface->format,0,0,0);
         iLen = strlen(pchStr); // number of characters to process
         for (int n = 0; n < iLen; n++) {
            dword *pdwLine;
            byte *pbPixel;
            iIdx = (int)pchStr[n]; // get the ASCII value
            if ((iIdx < FNT_MIN_CHAR) || (iIdx > FNT_MAX_CHAR)) { // limit it to the range of chars in the font
               iIdx = FNT_BAD_CHAR;
            }
            iIdx -= FNT_MIN_CHAR; // zero base the index
            pdwLine = pdwAddr; // keep a reference to the current screen position
            for (iRow = 0; iRow < FNT_CHAR_HEIGHT; iRow++) { // loop for all rows in the font character
               pbPixel = (byte *)pdwLine;
               bRow = bFont[iIdx]; // get the bitmap information for one row
               for (iCol = 0; iCol < FNT_CHAR_WIDTH; iCol++) { // loop for all columns in the font character
                  if (bRow & 0x80) { // is the bit set?
                     *(pbPixel+1) = 0; // draw the "shadow"
                     *(byte *)((dword *)pbPixel+CPC.scr_line_offs) = 0;
                     *((byte *)((dword *)pbPixel+CPC.scr_line_offs)+1) = 0;
                     *pbPixel = bColour; // draw the character pixel
                  }
                  pbPixel++; // update the screen position
                  bRow <<= 1; // advance to the next bit
               }
               pdwLine += CPC.scr_line_offs; // advance to next screen line
               iIdx += FNT_CHARS; // advance to next row in font data
            }
            pdwAddr += FNT_CHAR_WIDTH/4; // set screen address to next character position
         }
         break;
   }
}
 
 
 
int file_size (int file_num)
{
   struct stat s;
 
   if (!fstat(file_num, &s)) {
      return s.st_size;
   } else {
      return 0;
   }
}
 
 
 
int zip_dir (t_zip_info *zi)
{
   int n, iFileCount;
   long lFilePosition;
   dword dwCentralDirPosition, dwNextEntry;
   word wCentralDirEntries, wCentralDirSize, wFilenameLength;
   byte *pbPtr;
   char *pchStrPtr;
   dword dwOffset;
 
   iFileCount = 0;
   if ((pfileObject = fopen(zi->pchZipFile, "rb")) == NULL) {
      return ERR_FILE_NOT_FOUND;
   }
 
   wCentralDirEntries = 0;
   wCentralDirSize = 0;
   dwCentralDirPosition = 0;
   lFilePosition = -256;
   do {
      fseek(pfileObject, lFilePosition, SEEK_END);
      if (fread(pbGPBuffer, 256, 1, pfileObject) == 0) {
         fclose(pfileObject);
         return ERR_FILE_BAD_ZIP; // exit if loading of data chunck failed
      }
      pbPtr = pbGPBuffer + (256 - 22); // pointer to end of central directory (under ideal conditions)
      while (pbPtr != (byte *)pbGPBuffer) {
         if (*(dword *)pbPtr == 0x06054b50) { // check for end of central directory signature
            wCentralDirEntries = *(word *)(pbPtr + 10);
            wCentralDirSize = *(word *)(pbPtr + 12);
            dwCentralDirPosition = *(dword *)(pbPtr + 16);
            break;
         }
         pbPtr--; // move backwards through buffer
      }
      lFilePosition -= 256; // move backwards through ZIP file
   } while (wCentralDirEntries == 0);
   if (wCentralDirSize == 0) {
      fclose(pfileObject);
      return ERR_FILE_BAD_ZIP; // exit if no central directory was found
   }
   fseek(pfileObject, dwCentralDirPosition, SEEK_SET);
   if (fread(pbGPBuffer, wCentralDirSize, 1, pfileObject) == 0) {
      fclose(pfileObject);
      return ERR_FILE_BAD_ZIP; // exit if loading of data chunck failed
   }
 
   pbPtr = pbGPBuffer;
   if (zi->pchFileNames) {
      free(zi->pchFileNames); // dealloc old string table
   }
   zi->pchFileNames = (char *)malloc(wCentralDirSize); // approximate space needed by using the central directory size
   pchStrPtr = zi->pchFileNames;
 
   for (n = wCentralDirEntries; n; n--) {
      wFilenameLength = *(word *)(pbPtr + 28);
      dwOffset = *(dword *)(pbPtr + 42);
      dwNextEntry = wFilenameLength + *(word *)(pbPtr + 30) + *(word *)(pbPtr + 32);
      pbPtr += 46;
      char *pchThisExtension = zi->pchExtension;
      while (*pchThisExtension != '\0') { // loop for all extensions to be checked
         if (strncasecmp((char *)pbPtr + (wFilenameLength - 4), pchThisExtension, 4) == 0) {
            strncpy(pchStrPtr, (char *)pbPtr, wFilenameLength); // copy filename from zip directory
            pchStrPtr[wFilenameLength] = 0; // zero terminate string
            pchStrPtr += wFilenameLength+1;
            *(dword *)pchStrPtr = dwOffset; // associate offset with string
            pchStrPtr += 4;
            iFileCount++;
            break;
         }
         pchThisExtension += 4; // advance to next extension
      }
      pbPtr += dwNextEntry;
   }
   fclose(pfileObject);
 
   if (iFileCount == 0) { // no files found?
      return ERR_FILE_EMPTY_ZIP;
   }
 
   zi->iFiles = iFileCount;
   return 0; // operation completed successfully
}
 
 
 
int zip_extract (char *pchZipFile, char *pchFileName, dword dwOffset)
{
   int iStatus, iCount;
   dword dwSize;
   byte *pbInputBuffer, *pbOutputBuffer;
   FILE *pfileOut, *pfileIn;
   z_stream z;
 
   tmpnam(pchFileName); // generate a unique (temporary) file name for the decompression process
   if (!(pfileOut = fopen(pchFileName, "wb"))) {
      return ERR_FILE_UNZIP_FAILED; // couldn't create output file
   }
   pfileIn = fopen(pchZipFile, "rb"); // open ZIP file for reading
   fseek(pfileIn, dwOffset, SEEK_SET); // move file pointer to beginning of data block
   fread(pbGPBuffer, 30, 1, pfileIn); // read local header
   dwSize = *(dword *)(pbGPBuffer + 18); // length of compressed data
   dwOffset += 30 + *(word *)(pbGPBuffer + 26) + *(word *)(pbGPBuffer + 28);
   fseek(pfileIn, dwOffset, SEEK_SET); // move file pointer to start of compressed data
 
   pbInputBuffer = pbGPBuffer; // space for compressed data chunck
   pbOutputBuffer = pbInputBuffer + 16384; // space for uncompressed data chunck
   z.zalloc = (alloc_func)0;
   z.zfree = (free_func)0;
   z.opaque = (voidpf)0;
   iStatus = inflateInit2(&z, -MAX_WBITS); // init zlib stream (no header)
   do {
      z.next_in = pbInputBuffer;
      if (dwSize > 16384) { // limit input size to max 16K or remaining bytes
         z.avail_in = 16384;
      } else {
         z.avail_in = dwSize;
      }
      z.avail_in = fread(pbInputBuffer, 1, z.avail_in, pfileIn); // load compressed data chunck from ZIP file
      while ((z.avail_in) && (iStatus == Z_OK)) { // loop until all data has been processed
         z.next_out = pbOutputBuffer;
         z.avail_out = 16384;
         iStatus = inflate(&z, Z_NO_FLUSH); // decompress data
         iCount = 16384 - z.avail_out;
         if (iCount) { // save data to file if output buffer is full
            fwrite(pbOutputBuffer, 1, iCount, pfileOut);
         }
      }
      dwSize -= 16384; // advance to next chunck
   } while ((dwSize > 0) && (iStatus == Z_OK)) ; // loop until done
   if (iStatus != Z_STREAM_END) {
      return ERR_FILE_UNZIP_FAILED; // abort on error
   }
   iStatus = inflateEnd(&z); // clean up
   fclose(pfileIn);
   fclose(pfileOut);
 
   return 0; // data was successfully decompressed
}
 
 
 
int snapshot_load (char *pchFileName)
{
   int n;
   dword dwSnapSize, dwModel, dwFlags;
   char chPath[_MAX_PATH + 1];
   byte val;
   reg_pair port;
   t_SNA_header sh;
 
   memset(&sh, 0, sizeof(sh));
   if ((pfileObject = fopen(pchFileName, "rb")) != NULL) {
      fread(&sh, sizeof(sh), 1, pfileObject); // read snapshot header
      if (memcmp(sh.id, "MV - SNA", 8) != 0) { // valid SNApshot image?
         fclose(pfileObject);
         return ERR_SNA_INVALID;
      }
      dwSnapSize = sh.ram_size[0] + (sh.ram_size[1] * 256); // memory dump size
      dwSnapSize &= ~0x3f; // limit to multiples of 64
      if (!dwSnapSize) {
         fclose(pfileObject);
         return ERR_SNA_SIZE;
      }
      if (dwSnapSize > CPC.ram_size) { // memory dump size differs from current RAM size?
         byte *pbTemp;
 
         pbTemp = new byte [dwSnapSize*1024];
         if (pbTemp) {
            delete [] pbRAM;
            CPC.ram_size = dwSnapSize;
            pbRAM = pbTemp;
         } else {
            fclose(pfileObject);
            return ERR_OUT_OF_MEMORY;
         }
      }
      emulator_reset(false);
      n = fread(pbRAM, dwSnapSize*1024, 1, pfileObject); // read memory dump into CPC RAM
      fclose(pfileObject);
      if (!n) {
         emulator_reset(false);
         return ERR_SNA_INVALID;
      }
 
// Z80
      _A = sh.AF[1];
      _F = sh.AF[0];
      _B = sh.BC[1];
      _C = sh.BC[0];
      _D = sh.DE[1];
      _E = sh.DE[0];
      _H = sh.HL[1];
      _L = sh.HL[0];
      _R = sh.R & 0x7f;
      _Rb7 = sh.R & 0x80; // bit 7 of R
      _I = sh.I;
      if (sh.IFF0)
         _IFF1 = Pflag;
      if (sh.IFF1)
         _IFF2 = Pflag;
      _IXh = sh.IX[1];
      _IXl = sh.IX[0];
      _IYh = sh.IY[1];
      _IYl = sh.IY[0];
      z80.SP.b.h = sh.SP[1];
      z80.SP.b.l = sh.SP[0];
      z80.PC.b.h = sh.PC[1];
      z80.PC.b.l = sh.PC[0];
      _IM = sh.IM; // interrupt mode
      z80.AFx.b.h = sh.AFx[1];
      z80.AFx.b.l = sh.AFx[0];
      z80.BCx.b.h = sh.BCx[1];
      z80.BCx.b.l = sh.BCx[0];
      z80.DEx.b.h = sh.DEx[1];
      z80.DEx.b.l = sh.DEx[0];
      z80.HLx.b.h = sh.HLx[1];
      z80.HLx.b.l = sh.HLx[0];
// Gate Array
      port.b.h = 0x7f;
      for (n = 0; n < 17; n++) { // loop for all colours + border
         GateArray.pen = n;
         val = sh.ga_ink_values[n]; // GA palette entry
         z80_OUT_handler(port, val | (1<<6));
      }
      val = sh.ga_pen; // GA pen
      z80_OUT_handler(port, (val & 0x3f));
      val = sh.ga_ROM_config; // GA ROM configuration
      z80_OUT_handler(port, (val & 0x3f) | (2<<6));
      val = sh.ga_RAM_config; // GA RAM configuration
      z80_OUT_handler(port, (val & 0x3f) | (3<<6));
// CRTC
      port.b.h = 0xbd;
      for (n = 0; n < 18; n++) { // loop for all CRTC registers
         val = sh.crtc_registers[n];
         CRTC.reg_select = n;
         z80_OUT_handler(port, val);
      }
      port.b.h = 0xbc;
      val = sh.crtc_reg_select; // CRTC register select
      z80_OUT_handler(port, val);
// ROM select
      port.b.h = 0xdf;
      val = sh.upper_ROM; // upper ROM number
      z80_OUT_handler(port, val);
// PPI
      port.b.h = 0xf4; // port A
      z80_OUT_handler(port, sh.ppi_A);
      port.b.h = 0xf5; // port B
      z80_OUT_handler(port, sh.ppi_B);
      port.b.h = 0xf6; // port C
      z80_OUT_handler(port, sh.ppi_C);
      port.b.h = 0xf7; // control
      z80_OUT_handler(port, sh.ppi_control);
// PSG
      PSG.control = PPI.portC;
      PSG.reg_select = sh.psg_reg_select;
      for (n = 0; n < 16; n++) { // loop for all PSG registers
         SetAYRegister(n, sh.psg_registers[n]);
      }
 
      if (sh.version > 1) { // does the snapshot have version 2 data?
         dwModel = sh.cpc_model; // determine the model it was saved for
         if (dwModel != CPC.model) { // different from what we're currently running?
            if (dwModel > 2) { // not one of the known models?
               emulator_reset(false);
               return ERR_SNA_CPC_TYPE;
            }
            strncpy(chPath, CPC.rom_path, sizeof(chPath)-2);
            strcat(chPath, "/");
            strncat(chPath, chROMFile[dwModel], sizeof(chPath)-1 - strlen(chPath)); // path to the required ROM image
            if ((pfileObject = fopen(chPath, "rb")) != NULL) {
               n = fread(pbROMlo, 2*16384, 1, pfileObject);
               fclose(pfileObject);
               if (!n) {
                  emulator_reset(false);
                  return ERR_CPC_ROM_MISSING;
               }
               CPC.model = dwModel;
            } else { // ROM image load failed
               emulator_reset(false);
               return ERR_CPC_ROM_MISSING;
            }
         }
      }
      if (sh.version > 2) { // does the snapshot have version 3 data?
         FDC.motor = sh.fdc_motor;
         driveA.current_track = sh.drvA_current_track;
         driveB.current_track = sh.drvB_current_track;
         CPC.printer_port = sh.printer_data ^ 0x80; // invert bit 7 again
         PSG.AmplitudeEnv = sh.psg_env_step << 1; // multiply by 2 to bring it into the 0 - 30 range
         PSG.FirstPeriod = false;
         if (sh.psg_env_direction == 0x01) { // up
            switch (PSG.RegisterAY.EnvType)
            {
               case 4:
               case 5:
               case 6:
               case 7:
               case 13:
               case 14:
               case 15:
                  PSG.FirstPeriod = true;
                  break;
            }
         } else if (sh.psg_env_direction == 0xff) { // down
            switch (PSG.RegisterAY.EnvType)
            {
               case 0:
               case 1:
               case 2:
               case 3:
               case 9:
               case 10:
               case 11:
                  PSG.FirstPeriod = true;
                  break;
            }
         }
         CRTC.addr = sh.crtc_addr[0] + (sh.crtc_addr[1] * 256);
         VDU.scanline = sh.crtc_scanline[0] + (sh.crtc_scanline[1] * 256);
         if (VDU.scanline > MaxVSync) {
            VDU.scanline = MaxVSync; // limit to max value
         }
         CRTC.char_count = sh.crtc_char_count[0];
         CRTC.line_count = sh.crtc_line_count & 127;
         CRTC.raster_count = sh.crtc_raster_count & 31;
         CRTC.hsw_count = sh.crtc_hsw_count & 15;
         CRTC.vsw_count = sh.crtc_vsw_count & 15;
         dwFlags = sh.crtc_flags[0] + (sh.crtc_flags[1] * 256);
         CRTC.flag_invsync = dwFlags & 1 ? 1 : 0; // vsync active?
         if (dwFlags & 2) { // hsync active?
            flags1.inHSYNC = 0xff;
            CRTC.flag_hadhsync = 1;
            if ((CRTC.hsw_count >= 3) && (CRTC.hsw_count < 7)) {
               CRTC.flag_inmonhsync = 1;
            }
         }
         CRTC.flag_invta = dwFlags & 0x80 ? 1 : 0; // in vertical total adjust?
         GateArray.hs_count = sh.ga_int_delay & 3;
         GateArray.sl_count = sh.ga_sl_count;
         z80.int_pending = sh.z80_int_pending;
      }
   } else {
      return ERR_FILE_NOT_FOUND;
   }
 
/* char *pchTmpBuffer = new char[MAX_LINE_LEN];
   LoadString(hAppInstance, MSG_SNA_LOAD, chMsgBuffer, sizeof(chMsgBuffer));
   snprintf(pchTmpBuffer, _MAX_PATH-1, chMsgBuffer, CPC.snap_file);
   add_message(pchTmpBuffer);
   delete [] pchTmpBuffer; */
   return 0;
}
 
 
 
int snapshot_save (char *pchFileName)
{
   t_SNA_header sh;
   int n;
   dword dwFlags;
 
   memset(&sh, 0, sizeof(sh));
   strcpy(sh.id, "MV - SNA");
   sh.version = 3;
// Z80
   sh.AF[1] = _A;
   sh.AF[0] = _F;
   sh.BC[1] = _B;
   sh.BC[0] = _C;
   sh.DE[1] = _D;
   sh.DE[0] = _E;
   sh.HL[1] = _H;
   sh.HL[0] = _L;
   sh.R = (_R & 0x7f) | (_Rb7 & 0x80);
   sh.I = _I;
   if (_IFF1)
      sh.IFF0 = 1;
   if (_IFF2)
      sh.IFF1 = 1;
   sh.IX[1] = _IXh;
   sh.IX[0] = _IXl;
   sh.IY[1] = _IYh;
   sh.IY[0] = _IYl;
   sh.SP[1] = z80.SP.b.h;
   sh.SP[0] = z80.SP.b.l;
   sh.PC[1] = z80.PC.b.h;
   sh.PC[0] = z80.PC.b.l;
   sh.IM = _IM;
   sh.AFx[1] = z80.AFx.b.h;
   sh.AFx[0] = z80.AFx.b.l;
   sh.BCx[1] = z80.BCx.b.h;
   sh.BCx[0] = z80.BCx.b.l;
   sh.DEx[1] = z80.DEx.b.h;
   sh.DEx[0] = z80.DEx.b.l;
   sh.HLx[1] = z80.HLx.b.h;
   sh.HLx[0] = z80.HLx.b.l;
// Gate Array
   sh.ga_pen = GateArray.pen;
   for (n = 0; n < 17; n++) { // loop for all colours + border
      sh.ga_ink_values[n] = GateArray.ink_values[n];
   }
   sh.ga_ROM_config = GateArray.ROM_config;
   sh.ga_RAM_config = GateArray.RAM_config;
// CRTC
   sh.crtc_reg_select = CRTC.reg_select;
   for (n = 0; n < 18; n++) { // loop for all CRTC registers
      sh.crtc_registers[n] = CRTC.registers[n];
   }
// ROM select
   sh.upper_ROM = GateArray.upper_ROM;
// PPI
   sh.ppi_A = PPI.portA;
   sh.ppi_B = PPI.portB;
   sh.ppi_C = PPI.portC;
   sh.ppi_control = PPI.control;
// PSG
   sh.psg_reg_select = PSG.reg_select;
   for (n = 0; n < 16; n++) { // loop for all PSG registers
      sh.psg_registers[n] = PSG.RegisterAY.Index[n];
   }
 
   sh.ram_size[0] = CPC.ram_size & 0xff;
   sh.ram_size[1] = (CPC.ram_size >> 8) & 0xff;
// version 2 info
   sh.cpc_model = CPC.model;
// version 3 info
   sh.fdc_motor = FDC.motor;
   sh.drvA_current_track = driveA.current_track;
   sh.drvB_current_track = driveB.current_track;
   sh.printer_data = CPC.printer_port ^ 0x80; // invert bit 7 again
   sh.psg_env_step = PSG.AmplitudeEnv >> 1; // divide by 2 to bring it into the 0 - 15 range
   if (PSG.FirstPeriod) {
      switch (PSG.RegisterAY.EnvType)
      {
         case 0:
         case 1:
         case 2:
         case 3:
         case 8:
         case 9:
         case 10:
         case 11:
            sh.psg_env_direction = 0xff; // down
            break;
         case 4:
         case 5:
         case 6:
         case 7:
         case 12:
         case 13:
         case 14:
         case 15:
            sh.psg_env_direction = 0x01; // up
            break;
      }
   } else {
      switch (PSG.RegisterAY.EnvType)
      {
         case 0:
         case 1:
         case 2:
         case 3:
         case 4:
         case 5:
         case 6:
         case 7:
         case 9:
         case 11:
         case 13:
         case 15:
            sh.psg_env_direction = 0x00; // hold
            break;
         case 8:
         case 14:
            sh.psg_env_direction = 0xff; // down
            break;
         case 10:
         case 12:
            sh.psg_env_direction = 0x01; // up
            break;
      }
   }
   sh.crtc_addr[0] = CRTC.addr & 0xff;
   sh.crtc_addr[1] = (CRTC.addr >> 8) & 0xff;
   sh.crtc_scanline[0] = VDU.scanline & 0xff;
   sh.crtc_scanline[1] = (VDU.scanline >> 8) & 0xff;
   sh.crtc_char_count[0] = CRTC.char_count;
   sh.crtc_line_count = CRTC.line_count;
   sh.crtc_raster_count = CRTC.raster_count;
   sh.crtc_hsw_count = CRTC.hsw_count;
   sh.crtc_vsw_count = CRTC.vsw_count;
   dwFlags = 0;
   if (CRTC.flag_invsync) { // vsync active?
      dwFlags |= 1;
   }
   if (flags1.inHSYNC) { // hsync active?
      dwFlags |= 2;
   }
   if (CRTC.flag_invta) { // in vertical total adjust?
      dwFlags |= 0x80;
   }
   sh.crtc_flags[0] = dwFlags & 0xff;
   sh.crtc_flags[1] = (dwFlags >> 8) & 0xff;
   sh.ga_int_delay = GateArray.hs_count;
   sh.ga_sl_count = GateArray.sl_count;
   sh.z80_int_pending = z80.int_pending;
 
   if ((pfileObject = fopen(pchFileName, "wb")) != NULL) {
      if (fwrite(&sh, sizeof(sh), 1, pfileObject) != 1) { // write snapshot header
         fclose(pfileObject);
         return ERR_SNA_WRITE;
      }
      if (fwrite(pbRAM, CPC.ram_size*1024, 1, pfileObject) != 1) { // write memory contents to snapshot file
         fclose(pfileObject);
         return ERR_SNA_WRITE;
      }
      fclose(pfileObject);
   } else {
      return ERR_SNA_WRITE;
   }
 
/* char *pchTmpBuffer = new char[MAX_LINE_LEN];
   LoadString(hAppInstance, MSG_SNA_SAVE, chMsgBuffer, sizeof(chMsgBuffer));
   snprintf(pchTmpBuffer, _MAX_PATH-1, chMsgBuffer, CPC.snap_file);
   add_message(pchTmpBuffer);
   delete [] pchTmpBuffer; */
   return 0;
}
 
 
 
void dsk_eject (t_drive *drive)
{
   dword track, side;
 
   for (track = 0; track < DSK_TRACKMAX; track++) { // loop for all tracks
      for (side = 0; side < DSK_SIDEMAX; side++) { // loop for all sides
         if (drive->track[track][side].data) { // track is formatted?
            free(drive->track[track][side].data); // release memory allocated for this track
         }
      }
   }
   dword dwTemp = drive->current_track; // save the drive head position
   memset(drive, 0, sizeof(t_drive)); // clear drive info structure
   drive->current_track = dwTemp;
}
 
 
 
int dsk_load (char *pchFileName, t_drive *drive, char chID)
{
   int iRetCode;
   dword dwTrackSize, track, side, sector, dwSectorSize, dwSectors;
   byte *pbPtr, *pbDataPtr, *pbTempPtr, *pbTrackSizeTable;
 
   iRetCode = 0;
   dsk_eject(drive);
   if ((pfileObject = fopen(pchFileName, "rb")) != NULL) {
      fread(pbGPBuffer, 0x100, 1, pfileObject); // read DSK header
      pbPtr = pbGPBuffer;
 
      if (memcmp(pbPtr, "MV - CPC", 8) == 0) { // normal DSK image?
         drive->tracks = *(pbPtr + 0x30); // grab number of tracks
         if (drive->tracks > DSK_TRACKMAX) { // compare against upper limit
            drive->tracks = DSK_TRACKMAX; // limit to maximum
         }
         drive->sides = *(pbPtr + 0x31); // grab number of sides
         if (drive->sides > DSK_SIDEMAX) { // abort if more than maximum
            iRetCode = ERR_DSK_SIDES;
            goto exit;
         }
         dwTrackSize = (*(pbPtr + 0x32) + (*(pbPtr + 0x33) << 8)) - 0x100; // determine track size in bytes, minus track header
         drive->sides--; // zero base number of sides
         for (track = 0; track < drive->tracks; track++) { // loop for all tracks
            for (side = 0; side <= drive->sides; side++) { // loop for all sides
               fread(pbGPBuffer+0x100, 0x100, 1, pfileObject); // read track header
               pbPtr = pbGPBuffer + 0x100;
               if (memcmp(pbPtr, "Track-Info", 10) != 0) { // abort if ID does not match
                  iRetCode = ERR_DSK_INVALID;
                  goto exit;
               }
               dwSectorSize = 0x80 << *(pbPtr + 0x14); // determine sector size in bytes
               dwSectors = *(pbPtr + 0x15); // grab number of sectors
               if (dwSectors > DSK_SECTORMAX) { // abort if sector count greater than maximum
                  iRetCode = ERR_DSK_SECTORS;
                  goto exit;
               }
               drive->track[track][side].sectors = dwSectors; // store sector count
               drive->track[track][side].size = dwTrackSize; // store track size
               drive->track[track][side].data = (byte *)malloc(dwTrackSize); // attempt to allocate the required memory
               if (drive->track[track][side].data == NULL) { // abort if not enough
                  iRetCode = ERR_OUT_OF_MEMORY;
                  goto exit;
               }
               pbDataPtr = drive->track[track][side].data; // pointer to start of memory buffer
               pbTempPtr = pbDataPtr; // keep a pointer to the beginning of the buffer for the current track
               for (sector = 0; sector < dwSectors; sector++) { // loop for all sectors
                  memcpy(drive->track[track][side].sector[sector].CHRN, (pbPtr + 0x18), 4); // copy CHRN
                  memcpy(drive->track[track][side].sector[sector].flags, (pbPtr + 0x1c), 2); // copy ST1 & ST2
                  drive->track[track][side].sector[sector].size = dwSectorSize;
                  drive->track[track][side].sector[sector].data = pbDataPtr; // store pointer to sector data
                  pbDataPtr += dwSectorSize;
                  pbPtr += 8;
               }
               if (!fread(pbTempPtr, dwTrackSize, 1, pfileObject)) { // read entire track data in one go
                  iRetCode = ERR_DSK_INVALID;
                  goto exit;
               }
            }
         }
         drive->altered = 0; // disk is as yet unmodified
      } else {
         if (memcmp(pbPtr, "EXTENDED", 8) == 0) { // extended DSK image?
            drive->tracks = *(pbPtr + 0x30); // number of tracks
            if (drive->tracks > DSK_TRACKMAX) {  // limit to maximum possible
               drive->tracks = DSK_TRACKMAX;
            }
            drive->random_DEs = *(pbPtr + 0x31) & 0x80; // simulate random Data Errors?
            drive->sides = *(pbPtr + 0x31) & 3; // number of sides
            if (drive->sides > DSK_SIDEMAX) { // abort if more than maximum
               iRetCode = ERR_DSK_SIDES;
               goto exit;
            }
            pbTrackSizeTable = pbPtr + 0x34; // pointer to track size table in DSK header
            drive->sides--; // zero base number of sides
            for (track = 0; track < drive->tracks; track++) { // loop for all tracks
               for (side = 0; side <= drive->sides; side++) { // loop for all sides
                  dwTrackSize = (*pbTrackSizeTable++ << 8); // track size in bytes
                  if (dwTrackSize != 0) { // only process if track contains data
                     dwTrackSize -= 0x100; // compensate for track header
                     fread(pbGPBuffer+0x100, 0x100, 1, pfileObject); // read track header
                     pbPtr = pbGPBuffer + 0x100;
                     if (memcmp(pbPtr, "Track-Info", 10) != 0) { // valid track header?
                        iRetCode = ERR_DSK_INVALID;
                        goto exit;
                     }
                     dwSectors = *(pbPtr + 0x15); // number of sectors for this track
                     if (dwSectors > DSK_SECTORMAX) { // abort if sector count greater than maximum
                        iRetCode = ERR_DSK_SECTORS;
                        goto exit;
                     }
                     drive->track[track][side].sectors = dwSectors; // store sector count
                     drive->track[track][side].size = dwTrackSize; // store track size
                     drive->track[track][side].data = (byte *)malloc(dwTrackSize); // attempt to allocate the required memory
                     if (drive->track[track][side].data == NULL) { // abort if not enough
                        iRetCode = ERR_OUT_OF_MEMORY;
                        goto exit;
                     }
                     pbDataPtr = drive->track[track][side].data; // pointer to start of memory buffer
                     pbTempPtr = pbDataPtr; // keep a pointer to the beginning of the buffer for the current track
                     for (sector = 0; sector < dwSectors; sector++) { // loop for all sectors
                        memcpy(drive->track[track][side].sector[sector].CHRN, (pbPtr + 0x18), 4); // copy CHRN
                        memcpy(drive->track[track][side].sector[sector].flags, (pbPtr + 0x1c), 2); // copy ST1 & ST2
                        dwSectorSize = *(pbPtr + 0x1e) + (*(pbPtr + 0x1f) << 8); // sector size in bytes
                        drive->track[track][side].sector[sector].size = dwSectorSize;
                        drive->track[track][side].sector[sector].data = pbDataPtr; // store pointer to sector data
                        pbDataPtr += dwSectorSize;
                        pbPtr += 8;
                     }
                     if (!fread(pbTempPtr, dwTrackSize, 1, pfileObject)) { // read entire track data in one go
                        iRetCode = ERR_DSK_INVALID;
                        goto exit;
                     }
                  } else {
                     memset(&drive->track[track][side], 0, sizeof(t_track)); // track not formatted
                  }
               }
            }
            drive->altered = 0; // disk is as yet unmodified
         } else {
            iRetCode = ERR_DSK_INVALID; // file could not be identified as a valid DSK
         }
      }
/*    {
         char *pchTmpBuffer = new char[MAX_LINE_LEN];
         LoadString(hAppInstance, MSG_DSK_LOAD, chMsgBuffer, sizeof(chMsgBuffer));
         snprintf(pchTmpBuffer, _MAX_PATH-1, chMsgBuffer, chID, chID == 'A' ? CPC.drvA_file : CPC.drvB_file);
         add_message(pchTmpBuffer);
         delete [] pchTmpBuffer;
      } */
exit:
      fclose(pfileObject);
   } else {
      iRetCode = ERR_FILE_NOT_FOUND;
   }
 
   if (iRetCode != 0) { // on error, 'eject' disk from drive
      dsk_eject(drive);
   }
   return iRetCode;
}
 
 
 
int dsk_save (char *pchFileName, t_drive *drive, char chID)
{
   t_DSK_header dh;
   t_track_header th;
   dword track, side, pos, sector;
 
   if ((pfileObject = fopen(pchFileName, "wb")) != NULL) {
      memset(&dh, 0, sizeof(dh));
      strcpy(dh.id, "EXTENDED CPC DSK File\r\nDisk-Info\r\n");
      strcpy(dh.unused1, "Caprice32\r\n");
      dh.tracks = drive->tracks;
      dh.sides = (drive->sides+1) | (drive->random_DEs); // correct side count and indicate random DEs, if necessary
      pos = 0;
      for (track = 0; track < drive->tracks; track++) { // loop for all tracks
         for (side = 0; side <= drive->sides; side++) { // loop for all sides
            if (drive->track[track][side].size) { // track is formatted?
               dh.track_size[pos] = (drive->track[track][side].size + 0x100) >> 8; // track size + header in bytes
            }
            pos++;
         }
      }
      if (!fwrite(&dh, sizeof(dh), 1, pfileObject)) { // write header to file
         fclose(pfileObject);
         return ERR_DSK_WRITE;
      }
 
      memset(&th, 0, sizeof(th));
      strcpy(th.id, "Track-Info\r\n");
      for (track = 0; track < drive->tracks; track++) { // loop for all tracks
         for (side = 0; side <= drive->sides; side++) { // loop for all sides
            if (drive->track[track][side].size) { // track is formatted?
               th.track = track;
               th.side = side;
               th.bps = 2;
               th.sectors = drive->track[track][side].sectors;
               th.gap3 = 0x4e;
               th.filler = 0xe5;
               for (sector = 0; sector < th.sectors; sector++) {
                  memcpy(&th.sector[sector][0], drive->track[track][side].sector[sector].CHRN, 4); // copy CHRN
                  memcpy(&th.sector[sector][4], drive->track[track][side].sector[sector].flags, 2); // copy ST1 & ST2
                  th.sector[sector][6] = drive->track[track][side].sector[sector].size & 0xff;
                  th.sector[sector][7] = (drive->track[track][side].sector[sector].size >> 8) & 0xff; // sector size in bytes
               }
               if (!fwrite(&th, sizeof(th), 1, pfileObject)) { // write track header
                  fclose(pfileObject);
                  return ERR_DSK_WRITE;
               }
               if (!fwrite(drive->track[track][side].data, drive->track[track][side].size, 1, pfileObject)) { // write track data
                  fclose(pfileObject);
                  return ERR_DSK_WRITE;
               }
            }
         }
      }
      fclose(pfileObject);
   } else {
      return ERR_DSK_WRITE; // write attempt failed
   }
 
/* char *pchTmpBuffer = new char[MAX_LINE_LEN];
   LoadString(hAppInstance, MSG_DSK_SAVE, chMsgBuffer, sizeof(chMsgBuffer));
   snprintf(pchTmpBuffer, _MAX_PATH-1, chMsgBuffer, chID, chID == 'A' ? CPC.drvA_file : CPC.drvB_file);
   add_message(pchTmpBuffer);
   delete [] pchTmpBuffer; */
   return 0;
}
 
 
 
int dsk_format (t_drive *drive, int iFormat)
{
   int iRetCode = 0;
   drive->tracks = disk_format[iFormat].tracks;
   if (drive->tracks > DSK_TRACKMAX) { // compare against upper limit
      drive->tracks = DSK_TRACKMAX; // limit to maximum
   }
   drive->sides = disk_format[iFormat].sides;
   if (drive->sides > DSK_SIDEMAX) { // abort if more than maximum
      iRetCode = ERR_DSK_SIDES;
      goto exit;
   }
   drive->sides--; // zero base number of sides
   for (dword track = 0; track < drive->tracks; track++) { // loop for all tracks
      for (dword side = 0; side <= drive->sides; side++) { // loop for all sides
         dword dwSectorSize = 0x80 << disk_format[iFormat].sector_size; // determine sector size in bytes
         dword dwSectors = disk_format[iFormat].sectors;
         if (dwSectors > DSK_SECTORMAX) { // abort if sector count greater than maximum
            iRetCode = ERR_DSK_SECTORS;
            goto exit;
         }
         dword dwTrackSize = dwSectorSize * dwSectors; // determine track size in bytes, minus track header
         drive->track[track][side].sectors = dwSectors; // store sector count
         drive->track[track][side].size = dwTrackSize; // store track size
         drive->track[track][side].data = (byte *)malloc(dwTrackSize); // attempt to allocate the required memory
         if (drive->track[track][side].data == NULL) { // abort if not enough
            iRetCode = ERR_OUT_OF_MEMORY;
            goto exit;
         }
         byte *pbDataPtr = drive->track[track][side].data; // pointer to start of memory buffer
         byte *pbTempPtr = pbDataPtr; // keep a pointer to the beginning of the buffer for the current track
         byte CHRN[4];
         CHRN[0] = (byte)track;
         CHRN[1] = (byte)side;
         CHRN[3] = (byte)disk_format[iFormat].sector_size;
         for (dword sector = 0; sector < dwSectors; sector++) { // loop for all sectors
            CHRN[2] = disk_format[iFormat].sector_ids[side][sector];
            memcpy(drive->track[track][side].sector[sector].CHRN, CHRN, 4); // copy CHRN
            drive->track[track][side].sector[sector].size = dwSectorSize;
            drive->track[track][side].sector[sector].data = pbDataPtr; // store pointer to sector data
            pbDataPtr += dwSectorSize;
         }
         memset(pbTempPtr, disk_format[iFormat].filler_byte, dwTrackSize);
      }
   }
   drive->altered = 1; // flag disk as having been modified
 
exit:
   if (iRetCode != 0) { // on error, 'eject' disk from drive
      dsk_eject(drive);
   }
   return iRetCode;
}
 
 
 
void tape_eject (void)
{
   free(pbTapeImage);
   pbTapeImage = NULL;
}
 
 
 
int tape_insert (char *pchFileName)
{
   long lFileSize;
   int iBlockLength;
   byte bID;
   byte *pbPtr, *pbBlock;
 
   tape_eject();
   if ((pfileObject = fopen(pchFileName, "rb")) == NULL) {
      return ERR_FILE_NOT_FOUND;
   }
   fread(pbGPBuffer, 10, 1, pfileObject); // read CDT header
   pbPtr = pbGPBuffer;
   if (memcmp(pbPtr, "ZXTape!\032", 8) != 0) { // valid CDT file?
      fclose(pfileObject);
      return ERR_TAP_INVALID;
   }
   if (*(pbPtr + 0x08) != 1) { // major version must be 1
      fclose(pfileObject);
      return ERR_TAP_INVALID;
   }
   lFileSize = file_size(fileno(pfileObject)) - 0x0a;
   if (lFileSize <= 0) { // the tape image should have at least one block...
      fclose(pfileObject);
      return ERR_TAP_INVALID;
   }
   pbTapeImage = (byte *)malloc(lFileSize+6);
   *pbTapeImage = 0x20; // start off with a pause block
   *(word *)(pbTapeImage+1) = 2000; // set the length to 2 seconds
   fread(pbTapeImage+3, lFileSize, 1, pfileObject); // append the entire CDT file
   fclose(pfileObject);
   *(pbTapeImage+lFileSize+3) = 0x20; // end with a pause block
   *(word *)(pbTapeImage+lFileSize+3+1) = 2000; // set the length to 2 seconds
 
   #ifdef DEBUG_TAPE
   fputs("--- New Tape\r\n", pfoDebug);
   #endif
   pbTapeImageEnd = pbTapeImage + lFileSize+6;
   pbBlock = pbTapeImage;
   bool bolGotDataBlock = false;
   while (pbBlock < pbTapeImageEnd) {
      bID = *pbBlock++;
      switch(bID) {
         case 0x10: // standard speed data block
            iBlockLength = *(word *)(pbBlock+2) + 4;
            bolGotDataBlock = true;
            break;
         case 0x11: // turbo loading data block
            iBlockLength = (*(dword *)(pbBlock+0x0f) & 0x00ffffff) + 0x12;
            bolGotDataBlock = true;
            break;
         case 0x12: // pure tone
            iBlockLength = 4;
            bolGotDataBlock = true;
            break;
         case 0x13: // sequence of pulses of different length
            iBlockLength = *pbBlock * 2 + 1;
            bolGotDataBlock = true;
            break;
         case 0x14: // pure data block
            iBlockLength = (*(dword *)(pbBlock+0x07) & 0x00ffffff) + 0x0a;
            bolGotDataBlock = true;
            break;
         case 0x15: // direct recording
            iBlockLength = (*(dword *)(pbBlock+0x05) & 0x00ffffff) + 0x08;
            bolGotDataBlock = true;
            break;
         case 0x20: // pause
            if ((!bolGotDataBlock) && (pbBlock != pbTapeImage+1)) {
               *(word *)pbBlock = 0; // remove any pauses (execept ours) before the data starts
            }
            iBlockLength = 2;
            break;
         case 0x21: // group start
            iBlockLength = *pbBlock + 1;
            break;
         case 0x22: // group end
            iBlockLength = 0;
            break;
         case 0x23: // jump to block
   return ERR_TAP_UNSUPPORTED;
            iBlockLength = 2;
            break;
         case 0x24: // loop start
   return ERR_TAP_UNSUPPORTED;
            iBlockLength = 2;
            break;
         case 0x25: // loop end
   return ERR_TAP_UNSUPPORTED;
            iBlockLength = 0;
            break;
         case 0x26: // call sequence
   return ERR_TAP_UNSUPPORTED;
            iBlockLength = (*(word *)pbBlock * 2) + 2;
            break;
         case 0x27: // return from sequence
   return ERR_TAP_UNSUPPORTED;
            iBlockLength = 0;
            break;
         case 0x28: // select block
   return ERR_TAP_UNSUPPORTED;
            iBlockLength = *(word *)pbBlock + 2;
            break;
         case 0x30: // text description
            iBlockLength = *pbBlock + 1;
            break;
         case 0x31: // message block
            iBlockLength = *(pbBlock+1) + 2;
            break;
         case 0x32: // archive info
            iBlockLength = *(word *)pbBlock + 2;
            break;
         case 0x33: // hardware type
            iBlockLength = (*pbBlock * 3) + 1;
            break;
         case 0x34: // emulation info
            iBlockLength = 8;
            break;
         case 0x35: // custom info block
            iBlockLength = *(dword *)(pbBlock+0x10) + 0x14;
            break;
         case 0x40: // snapshot block
            iBlockLength = (*(dword *)(pbBlock+0x01) & 0x00ffffff) + 0x04;
            break;
         case 0x5A: // another tzx/cdt file
            iBlockLength = 9;
            break;
 
         default: // "extension rule"
            iBlockLength = *(dword *)pbBlock + 4;
      }
 
      #ifdef DEBUG_TAPE
      fprintf(pfoDebug, "%02x %d\r\n", bID, iBlockLength);
      #endif
 
      pbBlock += iBlockLength;
   }
   if (pbBlock != pbTapeImageEnd) {
      tape_eject();
      return ERR_TAP_INVALID;
   }
 
   Tape_Rewind();
 
/* char *pchTmpBuffer = new char[MAX_LINE_LEN];
   LoadString(hAppInstance, MSG_TAP_INSERT, chMsgBuffer, sizeof(chMsgBuffer));
   snprintf(pchTmpBuffer, _MAX_PATH-1, chMsgBuffer, CPC.tape_file);
   add_message(pchTmpBuffer);
   delete [] pchTmpBuffer; */
   return 0;
}
 
 
 
int tape_insert_voc (char *pchFileName)
{
   long lFileSize, lOffset, lInitialOffset, lSampleLength;
   int iBlockLength;
   byte *pbPtr, *pbTapeImagePtr, *pbVocDataBlock, *pbVocDataBlockPtr;
   bool bolDone;
 
   tape_eject();
   if ((pfileObject = fopen(pchFileName, "rb")) == NULL) {
      return ERR_FILE_NOT_FOUND;
   }
   fread(pbGPBuffer, 26, 1, pfileObject); // read VOC header
   pbPtr = pbGPBuffer;
   if (memcmp(pbPtr, "Creative Voice File\032", 20) != 0) { // valid VOC file?
      fclose(pfileObject);
      return ERR_TAP_BAD_VOC;
   }
   lOffset =
   lInitialOffset = *(word *)(pbPtr + 0x14);
   lFileSize = file_size(fileno(pfileObject));
   if ((lFileSize-26) <= 0) { // should have at least one block...
      fclose(pfileObject);
      return ERR_TAP_BAD_VOC;
   }
 
   #ifdef DEBUG_TAPE
   fputs("--- New Tape\r\n", pfoDebug);
   #endif
   iBlockLength = 0;
   lSampleLength = 0;
   byte bSampleRate = 0;
   bolDone = false;
   while ((!bolDone) && (lOffset < lFileSize)) {
      fseek(pfileObject, lOffset, SEEK_SET);
      fread(pbPtr, 16, 1, pfileObject); // read block ID + size
      #ifdef DEBUG_TAPE
      fprintf(pfoDebug, "%02x %d\r\n", *pbPtr, *(dword *)(pbPtr+0x01) & 0x00ffffff);
      #endif
      switch(*pbPtr) {
         case 0x0: // terminator
            bolDone = true;
            break;
         case 0x1: // sound data
            iBlockLength = (*(dword *)(pbPtr+0x01) & 0x00ffffff) + 4;
            lSampleLength += iBlockLength - 6;
            if ((bSampleRate) && (bSampleRate != *(pbPtr+0x04))) { // no change in sample rate allowed
               fclose(pfileObject);
               return ERR_TAP_BAD_VOC;
            }
            bSampleRate = *(pbPtr+0x04);
            if (*(pbPtr+0x05) != 0) { // must be 8 bits wide
               fclose(pfileObject);
               return ERR_TAP_BAD_VOC;
            }
            break;
         case 0x2: // sound continue
            iBlockLength = (*(dword *)(pbPtr+0x01) & 0x00ffffff) + 4;
            lSampleLength += iBlockLength - 4;
            break;
         case 0x3: // silence
            iBlockLength = 4;
            lSampleLength += *(word *)(pbPtr+0x01) + 1;
            if ((bSampleRate) && (bSampleRate != *(pbPtr+0x03))) { // no change in sample rate allowed
               fclose(pfileObject);
               return ERR_TAP_BAD_VOC;
            }
            bSampleRate = *(pbPtr+0x03);
            break;
         case 0x4: // marker
            iBlockLength = 3;
            break;
         case 0x5: // ascii
            iBlockLength = (*(dword *)(pbPtr+0x01) & 0x00ffffff) + 4;
            break;
         default:
            fclose(pfileObject);
            return ERR_TAP_BAD_VOC;
      }
      lOffset += iBlockLength;
   }
   #ifdef DEBUG_TAPE
   fprintf(pfoDebug, "--- %ld bytes\r\n", lSampleLength);
   #endif
 
   dword dwTapePulseCycles = 3500000L / (1000000L / (256 - bSampleRate)); // length of one pulse in ZX Spectrum T states
   dword dwCompressedSize = lSampleLength >> 3; // 8x data reduction
   if (dwCompressedSize > 0x00ffffff) { // we only support one direct recording block right now
      fclose(pfileObject);
      return ERR_TAP_BAD_VOC;
   }
   pbTapeImage = (byte *)malloc(dwCompressedSize+1+8+6);
   if (pbTapeImage == NULL) { // check if the memory allocation has failed
      fclose(pfileObject);
      return ERR_OUT_OF_MEMORY;
   }
   *pbTapeImage = 0x20; // start off with a pause block
   *(word *)(pbTapeImage+1) = 2000; // set the length to 2 seconds
 
   *(pbTapeImage+3) = 0x15; // direct recording block
   *(word *)(pbTapeImage+4) = (word)dwTapePulseCycles; // number of T states per sample
   *(word *)(pbTapeImage+6) = 0; // pause after block
   *(pbTapeImage+8) = lSampleLength & 7 ? lSampleLength & 7 : 8; // bits used in last byte
   *(dword *)(pbTapeImage+9) = dwCompressedSize & 0x00ffffff; // data length
   pbTapeImagePtr = pbTapeImage + 12;
 
   lOffset = lInitialOffset;
   bolDone = false;
   dword dwBit = 8;
   byte bByte = 0;
   while ((!bolDone) && (lOffset < lFileSize)) {
      fseek(pfileObject, lOffset, SEEK_SET);
      fread(pbPtr, 1, 1, pfileObject); // read block ID
      switch(*pbPtr) {
         case 0x0: // terminator
            bolDone = true;
            break;
         case 0x1: // sound data
            fread(pbPtr, 3+2, 1, pfileObject); // get block size and sound info
            iBlockLength = (*(dword *)(pbPtr) & 0x00ffffff) + 4;
            lSampleLength = iBlockLength - 6;
            pbVocDataBlock = (byte *)malloc(lSampleLength);
            if (pbVocDataBlock == NULL) {
               fclose(pfileObject);
               tape_eject();
               return ERR_OUT_OF_MEMORY;
            }
            fread(pbVocDataBlock, lSampleLength, 1, pfileObject);
            pbVocDataBlockPtr = pbVocDataBlock;
            for (int iBytePos = 0; iBytePos < lSampleLength; iBytePos++) {
               byte bVocSample = *pbVocDataBlockPtr++;
               dwBit--;
               if (bVocSample > VOC_THRESHOLD) {
                  bByte |= bit_values[dwBit];
               }
               if (!dwBit) { // got all 8 bits?
                  *pbTapeImagePtr++ = bByte;
                  dwBit = 8;
                  bByte = 0;
               }
            }
            free(pbVocDataBlock);
            break;
         case 0x2: // sound continue
            fread(pbPtr, 3, 1, pfileObject); // get block size
            iBlockLength = (*(dword *)(pbPtr) & 0x00ffffff) + 4;
            lSampleLength = iBlockLength - 4;
            pbVocDataBlock = (byte *)malloc(lSampleLength);
            if (pbVocDataBlock == NULL) {
               fclose(pfileObject);
               tape_eject();
               return ERR_OUT_OF_MEMORY;
            }
            fread(pbVocDataBlock, lSampleLength, 1, pfileObject);
            pbVocDataBlockPtr = pbVocDataBlock;
            for (int iBytePos = 0; iBytePos < lSampleLength; iBytePos++) {
               byte bVocSample = *pbVocDataBlockPtr++;
               dwBit--;
               if (bVocSample > VOC_THRESHOLD) {
                  bByte |= bit_values[dwBit];
               }
               if (!dwBit) { // got all 8 bits?
                  *pbTapeImagePtr++ = bByte;
                  dwBit = 8;
                  bByte = 0;
               }
            }
            free(pbVocDataBlock);
            break;
         case 0x3: // silence
            iBlockLength = 4;
            lSampleLength = *(word *)(pbPtr) + 1;
            for (int iBytePos = 0; iBytePos < lSampleLength; iBytePos++) {
               dwBit--;
               if (!dwBit) { // got all 8 bits?
                  *pbTapeImagePtr++ = bByte;
                  dwBit = 8;
                  bByte = 0;
               }
            }
            break;
         case 0x4: // marker
            iBlockLength = 3;
            break;
         case 0x5: // ascii
            iBlockLength = (*(dword *)(pbPtr) & 0x00ffffff) + 4;
            break;
      }
      lOffset += iBlockLength;
   }
   fclose(pfileObject);
 
   *pbTapeImagePtr = 0x20; // end with a pause block
   *(word *)(pbTapeImagePtr+1) = 2000; // set the length to 2 seconds
 
   pbTapeImageEnd = pbTapeImagePtr + 3;
/*
   #ifdef DEBUG_TAPE
   if ((pfileObject = fopen("./test.cdt", "wb")) != NULL) {
      fwrite(pbTapeImage, (int)((pbTapeImagePtr+3)-pbTapeImage), 1, pfileObject);
      fclose(pfileObject);
   }
   #endif
*/
   Tape_Rewind();
 
/* char *pchTmpBuffer = new char[MAX_LINE_LEN];
   LoadString(hAppInstance, MSG_TAP_INSERT, chMsgBuffer, sizeof(chMsgBuffer));
   snprintf(pchTmpBuffer, _MAX_PATH-1, chMsgBuffer, CPC.tape_file);
   add_message(pchTmpBuffer);
   delete [] pchTmpBuffer; */
   return 0;
}
 
 
 
 
 
int emulator_patch_ROM (void)
{
   char chPath[_MAX_PATH + 1];
   byte *pbPtr;
 
   strncpy(chPath, CPC.rom_path, sizeof(chPath)-2);
   strcat(chPath, "/");
   strncat(chPath, chROMFile[CPC.model], sizeof(chPath)-1 - strlen(chPath)); // determine the ROM image name for the selected model
   if ((pfileObject = fopen(chPath, "rb")) != NULL) { // load CPC OS + Basic
      fread(pbROMlo, 2*16384, 1, pfileObject);
      fclose(pfileObject);
   } else {
      return ERR_CPC_ROM_MISSING;
   }
 
   if (CPC.keyboard) {
      pbPtr = pbROMlo;
      switch(CPC.model) {
         case 0: // 464
            pbPtr += 0x1d69; // location of the keyboard translation table
            break;
         case 1: // 664
         case 2: // 6128
            pbPtr += 0x1eef; // location of the keyboard translation table
            break;
      }
      if (pbPtr != pbROMlo) {
         memcpy(pbPtr, cpc_keytrans[CPC.keyboard-1], 240); // patch the CPC OS ROM with the chosen keyboard layout
         pbPtr = pbROMlo + 0x3800;
         memcpy(pbPtr, cpc_charset[CPC.keyboard-1], 2048); // add the corresponding character set
      }
   }
 
   return 0;
}
 
 
 
void emulator_reset (bool bolMF2Reset)
{
   int n;
 
// Z80
   memset(&z80, 0, sizeof(z80)); // clear all Z80 registers and support variables
   _IX =
  _IY = 0xffff; // IX and IY are FFFF after a reset!
   _F = Zflag; // set zero flag
   z80.break_point = 0xffffffff; // clear break point
 
// CPC
   CPC.cycle_count = CYCLE_COUNT_INIT;
   memset(keyboard_matrix, 0xff, sizeof(keyboard_matrix)); // clear CPC keyboard matrix
   CPC.tape_motor = 0;
   CPC.tape_play_button = 0;
   CPC.printer_port = 0xff;
 
// VDU
   memset(&VDU, 0, sizeof(VDU)); // clear VDU data structure
   VDU.flag_drawing = 1;
 
// CRTC
   crtc_reset();
 
// Gate Array
   memset(&GateArray, 0, sizeof(GateArray)); // clear GA data structure
   GateArray.scr_mode =
   GateArray.requested_scr_mode = 1; // set to mode 1
   ga_init_banking();
 
// PPI
   memset(&PPI, 0, sizeof(PPI)); // clear PPI data structure
 
// PSG
   PSG.control = 0;
   ResetAYChipEmulation();
 
// FDC
   memset(&FDC, 0, sizeof(FDC)); // clear FDC data structure
   FDC.phase = CMD_PHASE;
   FDC.flags = STATUSDRVA_flag | STATUSDRVB_flag;
 
// memory
   if (bolMF2Reset) {
      memset(pbRAM, 0, 64*1024); // clear only the first 64K of CPC memory
   } else {
      memset(pbRAM, 0, CPC.ram_size*1024); // clear all memory used for CPC RAM
      if (pbMF2ROM) {
         memset(pbMF2ROM+8192, 0, 8192); // clear the MF2's RAM area
      }
   }
   for (n = 0; n < 4; n++) { // initialize active read/write bank configuration
      membank_read[n] = membank_config[0][n];
      membank_write[n] = membank_config[0][n];
   }
   membank_read[0] = pbROMlo; // 'page in' lower ROM
   membank_read[3] = pbROMhi; // 'page in' upper ROM
 
// Multiface 2
   dwMF2Flags = 0;
   dwMF2ExitAddr = 0xffffffff; // clear MF2 return address
   if ((pbMF2ROM) && (pbMF2ROMbackup)) {
      memcpy(pbMF2ROM, pbMF2ROMbackup, 8192); // copy the MF2 ROM to its proper place
   }
}
 
 
 
int emulator_init (void)
{
   int iErr, iRomNum;
   char chPath[_MAX_PATH + 1];
   char *pchRomData;
 
   pbGPBuffer = new byte [128*1024]; // attempt to allocate the general purpose buffer
   pbRAM = new byte [CPC.ram_size*1024]; // allocate memory for desired amount of RAM
   pbROMlo = new byte [32*1024]; // allocate memory for 32K of ROM
   if ((!pbGPBuffer) || (!pbRAM) || (!pbROMlo)) {
      return ERR_OUT_OF_MEMORY;
   }
   pbROMhi =
   pbExpansionROM = pbROMlo + 16384;
   memset(memmap_ROM, 0, sizeof(memmap_ROM[0]) * 256); // clear the expansion ROM map
   ga_init_banking(); // init the CPC memory banking map
   if ((iErr = emulator_patch_ROM())) {
      return iErr;
   }
 
   for (iRomNum = 0; iRomNum < 16; iRomNum++) { // loop for ROMs 0-15
      if (CPC.rom_file[iRomNum][0]) { // is a ROM image specified for this slot?
         pchRomData = new char [16384]; // allocate 16K
         memset(pchRomData, 0, 16384); // clear memory
         strncpy(chPath, CPC.rom_path, sizeof(chPath)-2);
         strcat(chPath, "/");
         strncat(chPath, CPC.rom_file[iRomNum], sizeof(chPath)-1 - strlen(chPath));
         if ((pfileObject = fopen(chPath, "rb")) != NULL) { // attempt to open the ROM image
            fread(pchRomData, 128, 1, pfileObject); // read 128 bytes of ROM data
            word checksum = 0;
            for (int n = 0; n < 0x43; n++) {
               checksum += pchRomData[n];
            }
            if (checksum == ((pchRomData[0x43] << 8) + pchRomData[0x44])) { // if the checksum matches, we got us an AMSDOS header
               fread(pchRomData, 128, 1, pfileObject); // skip it
            }
            if (!(pchRomData[0] & 0xfc)) { // is it a valid CPC ROM image (0 = forground, 1 = background, 2 = extension)?
               fread(pchRomData+128, 16384-128, 1, pfileObject); // read the rest of the ROM file
               memmap_ROM[iRomNum] = (byte *)pchRomData; // update the ROM map
            } else { // not a valid ROM file
               fprintf(stderr, "ERROR: %s is not a CPC ROM file - clearing ROM slot %d.\n", CPC.rom_file[iRomNum], iRomNum);
               delete [] pchRomData; // free memory on error
               CPC.rom_file[iRomNum][0] = 0;
            }
            fclose(pfileObject);
         } else { // file not found
            fprintf(stderr, "ERROR: The %s file is missing - clearing ROM slot %d.\n", CPC.rom_file[iRomNum], iRomNum);
            delete [] pchRomData; // free memory on error
            CPC.rom_file[iRomNum][0] = 0;
         }
      }
   }
   if (CPC.mf2) { // Multiface 2 enabled?
      if (!pbMF2ROM) {
         pbMF2ROM = new byte [16384]; // allocate the space needed for the Multiface 2: 8K ROM + 8K RAM
         pbMF2ROMbackup = new byte [8192]; // allocate the space needed for the backup of the MF2 ROM
         if ((!pbMF2ROM) || (!pbMF2ROMbackup)) {
            return ERR_OUT_OF_MEMORY;
         }
         memset(pbMF2ROM, 0, 16384); // clear memory
         strncpy(chPath, CPC.rom_path, sizeof(chPath)-2);
         strcat(chPath, "/");
         strncat(chPath, CPC.rom_mf2, sizeof(chPath)-1 - strlen(chPath)); // combine path and file name
         if ((pfileObject = fopen(chPath, "rb")) != NULL) { // attempt to open the ROM image
            fread(pbMF2ROMbackup, 8192, 1, pfileObject);
            if (memcmp(pbMF2ROMbackup+0x0d32, "MULTIFACE 2", 11) != 0) { // does it have the required signature?
               fprintf(stderr, "ERROR: The file selected as the MF2 ROM is either corrupt or invalid.\n");
               delete [] pbMF2ROMbackup;
               delete [] pbMF2ROM;
               pbMF2ROM = NULL;
               CPC.rom_mf2[0] = 0;
               CPC.mf2 = 0; // disable MF2 support
            }
            fclose(pfileObject);
         } else { // error opening file
            fprintf(stderr, "ERROR: The file selected as the MF2 ROM is either corrupt or invalid.\n");
            delete [] pbMF2ROMbackup;
            delete [] pbMF2ROM;
            pbMF2ROM = NULL;
            CPC.rom_mf2[0] = 0;
            CPC.mf2 = 0; // disable MF2 support
         }
      }
   }
 
   crtc_init();
   emulator_reset(false);
   CPC.paused &= ~1;
 
   return 0;
}
 
 
 
void emulator_shutdown (void)
{
   int iRomNum;
 
   delete [] pbMF2ROMbackup;
   delete [] pbMF2ROM;
   pbMF2ROM = NULL;
   for (iRomNum = 2; iRomNum < 16; iRomNum++) // loop for ROMs 2-15
   {
      if (memmap_ROM[iRomNum] != NULL) // was a ROM assigned to this slot?
         delete [] memmap_ROM[iRomNum]; // if so, release the associated memory
   }
 
   delete [] pbROMlo;
   delete [] pbRAM;
   delete [] pbGPBuffer;
}
 
 
 
int printer_start (void)
{
   if (!pfoPrinter) {
      if(!(pfoPrinter = fopen(CPC.printer_file, "wb"))) {
         return 0; // failed to open/create file
      }
   }
   return 1; // ready to capture printer output
}
 
 
 
void printer_stop (void)
{
   if (pfoPrinter) {
      fclose(pfoPrinter);
   }
   pfoPrinter = NULL;
}
 
 
 
void audio_update (void *userdata, byte *stream, int len)
{
   memcpy(stream, pbSndBuffer, len);
   dwSndBufferCopied = 1;
}
 
 
 
int audio_align_samples (int given)
{
   int actual = 1;
   while (actual < given) {
      actual <<= 1;
   }
   return actual; // return the closest match as 2^n
}
 
 
 
 
int audio_init (void)
{
   SDL_AudioSpec *desired, *obtained;
 
   if (!CPC.snd_enabled) {
      return 0;
   }
 
   desired = (SDL_AudioSpec *)malloc(sizeof(SDL_AudioSpec));
   obtained = (SDL_AudioSpec *)malloc(sizeof(SDL_AudioSpec));
 
   desired->freq = freq_table[CPC.snd_playback_rate];
   desired->format = CPC.snd_bits ? AUDIO_S16LSB : AUDIO_S8;
   desired->channels = CPC.snd_stereo+1;
   desired->samples = audio_align_samples(desired->freq / 50); // desired is 20ms at the given frequency
   desired->callback = audio_update;
   desired->userdata = NULL;
 
   if (SDL_OpenAudio(desired, obtained) < 0) {
      fprintf(stderr, "Could not open audio: %s\n", SDL_GetError());
      return 1;
   }
 
   free(desired);
   audio_spec = obtained;
 
   CPC.snd_buffersize = audio_spec->size; // size is samples * channels * bytes per sample (1 or 2)
   pbSndBuffer = (byte *)malloc(CPC.snd_buffersize); // allocate the sound data buffer
   pbSndBufferEnd = pbSndBuffer + CPC.snd_buffersize;
   memset(pbSndBuffer, 0, CPC.snd_buffersize);
   CPC.snd_bufferptr = pbSndBuffer; // init write cursor
 
   InitAY();
 
   for (int n = 0; n < 16; n++) {
      SetAYRegister(n, PSG.RegisterAY.Index[n]); // init sound emulation with valid values
   }
 
   return 0;
}
 
 
 
void audio_shutdown (void)
{
   SDL_CloseAudio();
   if (pbSndBuffer) {
      free(pbSndBuffer);
   }
   if (audio_spec) {
      free(audio_spec);
   }
}
 
 
 
void audio_pause (void)
{
   if (CPC.snd_enabled) {
      SDL_PauseAudio(1);
   }
}
 
 
 
void audio_resume (void)
{
   if (CPC.snd_enabled) {
      SDL_PauseAudio(0);
   }
}
 
 
 
int video_set_palette (void)
{
            int n;
 
         if (!CPC.scr_tube) {
            int n;
            for (n = 0; n < 32; n++) {
               dword red = (dword)(colours_rgb[n][0] * (CPC.scr_intensity / 10.0) * 255);
               if (red > 255) { // limit to the maximum
                  red = 255;
               }
               dword green = (dword)(colours_rgb[n][1] * (CPC.scr_intensity / 10.0) * 255);
               if (green > 255) {
                  green = 255;
               }
               dword blue = (dword)(colours_rgb[n][2] * (CPC.scr_intensity / 10.0) * 255);
               if (blue > 255) {
                  blue = 255;
               }
               colours[n].r = red;
               colours[n].g = green;
               colours[n].b = blue;
            }
         } else {
            int n;
            for (n = 0; n < 32; n++) {
               dword green = (dword)(colours_green[n] * (CPC.scr_intensity / 10.0) * 255);
               if (green > 255) {
                  green = 255;
               }
               colours[n].r = 0;
               colours[n].g = green;
               colours[n].b = 0;
         }
         }
 
	vid_plugin->set_palette(colours);
 
   for (n = 0; n < 17; n++) { // loop for all colours + border
		int i=GateArray.ink_values[n];
		GateArray.palette[n] = SDL_MapRGB(back_surface->format,colours[i].r,colours[i].g,colours[i].b);
   }
 
   return 0;
}
 
 
 
void video_set_style (void)
{
   if (vid_plugin->half_pixels)
   {
      dwXScale = 1;
      dwYScale = 1;
   }
   else
   {
      dwXScale = 2;
      dwYScale = 1;
   }
   switch (dwXScale) {
      case 1:
         CPC.scr_prerendernorm = (void(*)(void))prerender_normal_half;
         CPC.scr_prerenderbord = (void(*)(void))prerender_border_half;
         CPC.scr_prerendersync = (void(*)(void))prerender_sync_half;
         break;
      case 2:
         CPC.scr_prerendernorm = (void(*)(void))prerender_normal;
         CPC.scr_prerenderbord = (void(*)(void))prerender_border;
         CPC.scr_prerendersync = (void(*)(void))prerender_sync;
         break;
   }
 
   switch(CPC.scr_bpp)
   {
      case 32:
               CPC.scr_render = (void(*)(void))render32bpp;
               break;
 
      case 24:
               CPC.scr_render = (void(*)(void))render24bpp;
               break;
 
      case 16:
      case 15:
               CPC.scr_render = (void(*)(void))render16bpp;
               break;
 
      case 8:
               CPC.scr_render = (void(*)(void))render8bpp;
               break;
   }
}
 
 
int video_init (void)
{
   if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { // initialize the video subsystem
      return ERR_VIDEO_INIT;
   }
 
   vid_plugin=&video_plugin_list[CPC.scr_style];
 
   back_surface=vid_plugin->init(CPC.scr_fs_width, CPC.scr_fs_height, CPC.scr_fs_bpp, CPC.scr_window==0);
   if (!back_surface) { // attempt to set the required video mode
      fprintf(stderr, "Could not set requested video mode: %s\n", SDL_GetError());
      return ERR_VIDEO_SET_MODE;
   }
 
   CPC.scr_bpp = back_surface->format->BitsPerPixel; // bit depth of the surface
   video_set_style(); // select rendering style
 
   int iErrCode = video_set_palette(); // init CPC colours
   if (iErrCode) {
      return iErrCode;
   }
 
   vid_plugin->lock();
 
   CPC.scr_bps = back_surface->pitch / 4; // rendered screen line length (changing bytes to dwords)
   CPC.scr_line_offs = CPC.scr_bps * dwYScale;
   CPC.scr_pos =
   CPC.scr_base = (dword *)back_surface->pixels; // memory address of back buffer
 
   vid_plugin->unlock();
 
   SDL_ShowCursor(SDL_DISABLE); // hide the mouse cursor
 
   SDL_WM_SetCaption("Caprice32 " VERSION_STRING, "Caprice32");
 
   return 0;
}
 
 
 
void video_shutdown (void)
{
   if (back_surface) {
      vid_plugin->unlock();
   }
   vid_plugin->close();
   SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
 
 
 
void video_display (void)
{
   vid_plugin->flip();
}
 
 
 
void input_swap_joy (void) {
   dword n, pc_idx, val;
 
   for (n = 0; n < 6; n++) {
      pc_idx = joy_layout[n][1]; // get the PC key to change the assignment for
      if (pc_idx) {
         val = keyboard_normal[pc_idx]; // keep old value
         keyboard_normal[pc_idx] = cpc_kbd[CPC.keyboard][joy_layout[n][0]]; // assign new function
         cpc_kbd[CPC.keyboard][joy_layout[n][0]] = val; // store old value
      }
   }
}
 
 
 
int input_init (void)
{
   dword n, pc_key, pc_idx, cpc_idx, cpc_key;
 
   memset(keyboard_normal, 0xff, sizeof(keyboard_normal));
   memset(keyboard_shift, 0xff, sizeof(keyboard_shift));
   memset(keyboard_ctrl, 0xff, sizeof(keyboard_ctrl));
   memset(keyboard_mode, 0xff, sizeof(keyboard_mode));
 
   for (n = 0; n < KBD_MAX_ENTRIES; n++) {
      pc_key = kbd_layout[CPC.kbd_layout][n][1]; // PC key assigned to CPC key
      if (pc_key) {
         pc_idx = pc_key & 0xffff; // strip off modifier
         cpc_idx = kbd_layout[CPC.kbd_layout][n][0];
         if (cpc_idx & MOD_EMU_KEY) {
            cpc_key = cpc_idx;
         } else {
            cpc_key = cpc_kbd[CPC.keyboard][cpc_idx];
         }
         if (pc_key & MOD_PC_SHIFT) { // key + SHIFT?
            keyboard_shift[pc_idx] = cpc_key; // copy CPC key matrix value to SHIFT table
         } else if (pc_key & MOD_PC_CTRL) { // key + CTRL?
            keyboard_ctrl[pc_idx] = cpc_key; // copy CPC key matrix value to CTRL table
         } else if (pc_key & MOD_PC_MODE) { // key + AltGr?
            keyboard_mode[pc_idx] = cpc_key; // copy CPC key matrix value to AltGr table
         } else {
            keyboard_normal[pc_idx] = cpc_key; // copy CPC key matrix value to normal table
            if (!(cpc_key & MOD_EMU_KEY)) { // not an emulator function key?
               if (keyboard_shift[pc_idx] == 0xffffffff) { // SHIFT table entry has no value yet?
                  keyboard_shift[pc_idx] = cpc_key; // duplicate entry in SHIFT table
               }
               if (keyboard_ctrl[pc_idx] == 0xffffffff) { // CTRL table entry has no value yet?
                  keyboard_ctrl[pc_idx] = cpc_key | MOD_CPC_CTRL; // duplicate entry in CTRL table
               }
               if (keyboard_mode[pc_idx] == 0xffffffff) { // AltGr table entry has no value yet?
                  keyboard_mode[pc_idx] = cpc_key; // duplicate entry in AltGr table
               }
            }
         }
      }
   }
 
   if (CPC.joysticks) { // enable keyboard joystick emulation?
      input_swap_joy();
   }
 
   return 0;
}
 
 
 
int getConfigValueInt (char* pchFileName, char* pchSection, char* pchKey, int iDefaultValue)
{
   FILE* pfoConfigFile;
   char chLine[MAX_LINE_LEN + 1];
   char* pchToken;
 
   if ((pfoConfigFile = fopen(pchFileName, "r")) != NULL) { // open the config file
      while(fgets(chLine, MAX_LINE_LEN, pfoConfigFile) != NULL) { // grab one line
         pchToken = strtok(chLine, "[]"); // check if there's a section key
         if((pchToken != NULL) && (pchToken[0] != '#') && (strcmp(pchToken, pchSection) == 0)) {
            while(fgets(chLine, MAX_LINE_LEN, pfoConfigFile) != NULL) { // get the next line
               pchToken = strtok(chLine, "\t =\n\r"); // check if it has a key=value pair
               if((pchToken != NULL) && (pchToken[0] != '#') && (strcmp(pchToken, pchKey) == 0)) {
                  char* pchPtr = strtok(NULL, "\t =#\n\r"); // get the value if it matches our key
                  if (pchPtr != NULL) {
                     return (strtol(pchPtr, NULL, 0)); // return as integer
                  } else {
                     return iDefaultValue; // no value found
                  }
               }
            }
         }
      }
      fclose(pfoConfigFile);
   }
   return iDefaultValue; // no value found
}
 
 
 
void getConfigValueString (char* pchFileName, char* pchSection, char* pchKey, char* pchValue, int iSize, char* pchDefaultValue)
{
   FILE* pfoConfigFile;
   char chLine[MAX_LINE_LEN + 1];
   char* pchToken;
 
   if ((pfoConfigFile = fopen(pchFileName, "r")) != NULL) { // open the config file
      while(fgets(chLine, MAX_LINE_LEN, pfoConfigFile) != NULL) { // grab one line
         pchToken = strtok(chLine, "[]"); // check if there's a section key
         if((pchToken != NULL) && (pchToken[0] != '#') && (strcmp(pchToken, pchSection) == 0)) {
            while(fgets(chLine, MAX_LINE_LEN, pfoConfigFile) != NULL) { // get the next line
               pchToken = strtok(chLine, "\t =\n\r"); // check if it has a key=value pair
               if((pchToken != NULL) && (pchToken[0] != '#') && (strcmp(pchToken, pchKey) == 0)) {
                  char* pchPtr = strtok(NULL, "\t=#\n\r"); // get the value if it matches our key
                  if (pchPtr != NULL) {
                     strncpy(pchValue, pchPtr, iSize); // copy to destination
                     return;
                  } else {
                     strncpy(pchValue, pchDefaultValue, iSize); // no value found, return the default
                     return;
                  }
               }
            }
         }
      }
      fclose(pfoConfigFile);
   }
   strncpy(pchValue, pchDefaultValue, iSize); // no value found, return the default
}
 
 
 
void loadConfiguration (void)
{
   char chFileName[_MAX_PATH + 1];
   char chPath[_MAX_PATH + 1];
 
   strncpy(chFileName, chAppPath, sizeof(chFileName)-10);
   strcat(chFileName, "/cap32.cfg");
 
   memset(&CPC, 0, sizeof(CPC));
   CPC.model = getConfigValueInt(chFileName, "system", "model", 2); // CPC 6128
   if (CPC.model > 2) {
      CPC.model = 2;
   }
   CPC.jumpers = getConfigValueInt(chFileName, "system", "jumpers", 0x1e) & 0x1e; // OEM is Amstrad, video refresh is 50Hz
   CPC.ram_size = getConfigValueInt(chFileName, "system", "ram_size", 128) & 0x02c0; // 128KB RAM
   if (CPC.ram_size > 576) {
      CPC.ram_size = 576;
   } else if ((CPC.model == 2) && (CPC.ram_size < 128)) {
      CPC.ram_size = 128; // minimum RAM size for CPC 6128 is 128KB
   }
   CPC.speed = getConfigValueInt(chFileName, "system", "speed", DEF_SPEED_SETTING); // original CPC speed
   if ((CPC.speed < MIN_SPEED_SETTING) || (CPC.speed > MAX_SPEED_SETTING)) {
      CPC.speed = DEF_SPEED_SETTING;
   }
   CPC.limit_speed = 1;
   CPC.auto_pause = getConfigValueInt(chFileName, "system", "auto_pause", 1) & 1;
   CPC.printer = getConfigValueInt(chFileName, "system", "printer", 0) & 1;
   CPC.mf2 = getConfigValueInt(chFileName, "system", "mf2", 0) & 1;
   CPC.keyboard = getConfigValueInt(chFileName, "system", "keyboard", 0);
   if (CPC.keyboard > MAX_ROM_MODS) {
      CPC.keyboard = 0;
   }
   CPC.joysticks = getConfigValueInt(chFileName, "system", "joysticks", 0) & 1;
 
   CPC.scr_fs_width = getConfigValueInt(chFileName, "video", "scr_width", 800);
   CPC.scr_fs_height = getConfigValueInt(chFileName, "video", "scr_height", 600);
   CPC.scr_fs_bpp = getConfigValueInt(chFileName, "video", "scr_bpp", 8);
   CPC.scr_style = getConfigValueInt(chFileName, "video", "scr_style", 0);
   unsigned i=0;
   while((video_plugin_list[i+1].name!=NULL)&&(i<CPC.scr_style))
   {
      i++;
   }
   if (i!=CPC.scr_style) {
      CPC.scr_style = 0;
   }
   CPC.scr_oglfilter = getConfigValueInt(chFileName, "video", "scr_oglfilter", 1) & 1;
   CPC.scr_vsync = getConfigValueInt(chFileName, "video", "scr_vsync", 1) & 1;
   CPC.scr_led = getConfigValueInt(chFileName, "video", "scr_led", 1) & 1;
   CPC.scr_fps = getConfigValueInt(chFileName, "video", "scr_fps", 0) & 1;
   CPC.scr_tube = getConfigValueInt(chFileName, "video", "scr_tube", 0) & 1;
   CPC.scr_intensity = getConfigValueInt(chFileName, "video", "scr_intensity", 10);
   CPC.scr_remanency = getConfigValueInt(chFileName, "video", "scr_remanency", 0) & 1;
   if ((CPC.scr_intensity < 5) || (CPC.scr_intensity > 15)) {
      CPC.scr_intensity = 10;
   }
   CPC.scr_window = getConfigValueInt(chFileName, "video", "scr_window", 0) & 1;
 
   CPC.snd_enabled = getConfigValueInt(chFileName, "sound", "enabled", 1) & 1;
   CPC.snd_playback_rate = getConfigValueInt(chFileName, "sound", "playback_rate", 2);
   if (CPC.snd_playback_rate > (MAX_FREQ_ENTRIES-1)) {
      CPC.snd_playback_rate = 2;
   }
   CPC.snd_bits = getConfigValueInt(chFileName, "sound", "bits", 1) & 1;
   CPC.snd_stereo = getConfigValueInt(chFileName, "sound", "stereo", 1) & 1;
   CPC.snd_volume = getConfigValueInt(chFileName, "sound", "volume", 80);
   if ((CPC.snd_volume < 0) || (CPC.snd_volume > 100)) {
      CPC.snd_volume = 80;
   }
   CPC.snd_pp_device = getConfigValueInt(chFileName, "sound", "pp_device", 0) & 1;
 
   CPC.kbd_layout = getConfigValueInt(chFileName, "control", "kbd_layout", 0);
   if (CPC.kbd_layout > 3) {
      CPC.kbd_layout = 0;
   }
 
   CPC.max_tracksize = getConfigValueInt(chFileName, "file", "max_track_size", 6144-154);
   strncpy(chPath, chAppPath, sizeof(chPath)-7);
   strcat(chPath, "/snap");
   getConfigValueString(chFileName, "file", "snap_path", CPC.snap_path, sizeof(CPC.snap_path)-1, chPath);
   if (CPC.snap_path[0] == '\0') {
      strcpy(CPC.snap_path, chPath);
   }
   getConfigValueString(chFileName, "file", "snap_file", CPC.snap_file, sizeof(CPC.snap_file)-1, "");
   CPC.snap_zip = getConfigValueInt(chFileName, "file", "snap_zip", 0) & 1;
   strncpy(chPath, chAppPath, sizeof(chPath)-7);
   strcat(chPath, "/disk");
   getConfigValueString(chFileName, "file", "drvA_path", CPC.drvA_path, sizeof(CPC.drvA_path)-1, chPath);
   if (CPC.drvA_path[0] == '\0') {
      strcpy(CPC.drvA_path, chPath);
   }
   getConfigValueString(chFileName, "file", "drvA_file", CPC.drvA_file, sizeof(CPC.drvA_file)-1, "");
   CPC.drvA_zip = getConfigValueInt(chFileName, "file", "drvA_zip", 0) & 1;
   CPC.drvA_format = getConfigValueInt(chFileName, "file", "drvA_format", DEFAULT_DISK_FORMAT);
   getConfigValueString(chFileName, "file", "drvB_path", CPC.drvB_path, sizeof(CPC.drvB_path)-1, chPath);
   if (CPC.drvB_path[0] == '\0') {
      strcpy(CPC.drvB_path, chPath);
   }
   getConfigValueString(chFileName, "file", "drvB_file", CPC.drvB_file, sizeof(CPC.drvB_file)-1, "");
   CPC.drvB_zip = getConfigValueInt(chFileName, "file", "drvB_zip", 0) & 1;
   CPC.drvB_format = getConfigValueInt(chFileName, "file", "drvB_format", DEFAULT_DISK_FORMAT);
   strncpy(chPath, chAppPath, sizeof(chPath)-7);
   strcat(chPath, "/tape");
   getConfigValueString(chFileName, "file", "tape_path", CPC.tape_path, sizeof(CPC.tape_path)-1, chPath);
   if (CPC.tape_path[0] == '\0') {
      strcpy(CPC.tape_path, chPath);
   }
   getConfigValueString(chFileName, "file", "tape_file", CPC.tape_file, sizeof(CPC.tape_file)-1, "");
   CPC.tape_zip = getConfigValueInt(chFileName, "file", "tape_zip", 0) & 1;
 
   int iFmt = FIRST_CUSTOM_DISK_FORMAT;
   for (int i = iFmt; i < MAX_DISK_FORMAT; i++) { // loop through all user definable disk formats
      dword dwVal;
      char *pchTail;
      char chFmtId[14];
      disk_format[iFmt].label[0] = 0; // clear slot
      sprintf(chFmtId, "fmt%02d", i); // build format ID
      char chFmtStr[256];
      getConfigValueString(chFileName, "file", chFmtId, chFmtStr, sizeof(chFmtStr)-1, "");
      if (chFmtStr[0] != 0) { // found format definition for this slot?
         char chDelimiters[] = ",";
         char *pchToken;
         pchToken = strtok(chFmtStr, chDelimiters); // format label
         strncpy((char *)disk_format[iFmt].label, pchToken, sizeof(disk_format[iFmt].label)-1);
         pchToken = strtok(NULL, chDelimiters); // number of tracks
         if (pchToken == NULL) { // value missing?
            continue;
         }
         dwVal = strtoul(pchToken, &pchTail, 0);
         if ((dwVal < 1) || (dwVal > DSK_TRACKMAX)) { // invalid value?
            continue;
         }
         disk_format[iFmt].tracks = dwVal;
         pchToken = strtok(NULL, chDelimiters); // number of sides
         if (pchToken == NULL) { // value missing?
            continue;
         }
         dwVal = strtoul(pchToken, &pchTail, 0);
         if ((dwVal < 1) || (dwVal > DSK_SIDEMAX)) { // invalid value?
            continue;
         }
         disk_format[iFmt].sides = dwVal;
         pchToken = strtok(NULL, chDelimiters); // number of sectors
         if (pchToken == NULL) { // value missing?
            continue;
         }
         dwVal = strtoul(pchToken, &pchTail, 0);
         if ((dwVal < 1) || (dwVal > DSK_SECTORMAX)) { // invalid value?
            continue;
         }
         disk_format[iFmt].sectors = dwVal;
         pchToken = strtok(NULL, chDelimiters); // sector size as N value
         if (pchToken == NULL) { // value missing?
            continue;
         }
         dwVal = strtoul(pchToken, &pchTail, 0);
         if ((dwVal < 1) || (dwVal > 6)) { // invalid value?
            continue;
         }
         disk_format[iFmt].sector_size = dwVal;
         pchToken = strtok(NULL, chDelimiters); // gap#3 length
         if (pchToken == NULL) { // value missing?
            continue;
         }
         dwVal = strtoul(pchToken, &pchTail, 0);
         if ((dwVal < 1) || (dwVal > 255)) { // invalid value?
            continue;
         }
         disk_format[iFmt].gap3_length = dwVal;
         pchToken = strtok(NULL, chDelimiters); // filler byte
         if (pchToken == NULL) { // value missing?
            continue;
         }
         dwVal = strtoul(pchToken, &pchTail, 0);
         disk_format[iFmt].filler_byte = (byte)dwVal;
         for (int iSide = 0; iSide < (int)disk_format[iFmt].sides; iSide++) {
            for (int iSector = 0; iSector < (int)disk_format[iFmt].sectors; iSector++) {
               pchToken = strtok(NULL, chDelimiters); // sector ID
               if (pchToken == NULL) { // value missing?
                  dwVal = iSector+1;
               } else {
                  dwVal = strtoul(pchToken, &pchTail, 0);
               }
               disk_format[iFmt].sector_ids[iSide][iSector] = (byte)dwVal;
            }
         }
         iFmt++; // entry is valid
      }
   }
   strncpy(chPath, chAppPath, sizeof(chPath)-13);
   strcat(chPath, "/printer.dat");
   getConfigValueString(chFileName, "file", "printer_file", CPC.printer_file, sizeof(CPC.printer_file)-1, chPath);
   if (CPC.printer_file[0] == '\0') {
      strcpy(CPC.printer_file, chPath);
   }
   strncpy(chPath, chAppPath, sizeof(chPath)-12);
   strcat(chPath, "/screen.png");
   getConfigValueString(chFileName, "file", "sdump_file", CPC.sdump_file, sizeof(CPC.sdump_file)-1, chPath);
   if (CPC.sdump_file[0] == '\0') {
      strcpy(CPC.sdump_file, chPath);
   }
 
   strncpy(chPath, chAppPath, sizeof(chPath)-5);
   strcat(chPath, "/rom");
   getConfigValueString(chFileName, "rom", "rom_path", CPC.rom_path, sizeof(CPC.rom_path)-1, chPath);
   for (int iRomNum = 0; iRomNum < 16; iRomNum++) { // loop for ROMs 0-15
      char chRomId[14];
      sprintf(chRomId, "slot%02d", iRomNum); // build ROM ID
      getConfigValueString(chFileName, "rom", chRomId, CPC.rom_file[iRomNum], sizeof(CPC.rom_file[iRomNum])-1, "");
   }
   if (CPC.rom_path[0] == '\0') { // if the path is empty, set it to the default
      strcpy(CPC.rom_path, chPath);
   }
   if ((pfileObject = fopen(chFileName, "rt")) == NULL) {
      strcpy(CPC.rom_file[7], "amsdos.rom"); // insert AMSDOS in slot 7 if the config file does not exist yet
   } else {
      fclose(pfileObject);
   }
   getConfigValueString(chFileName, "rom", "rom_mf2", CPC.rom_mf2, sizeof(CPC.rom_mf2)-1, "");
}
 
 
 
void splitPathFileName(char *pchCombined, char *pchPath, char *pchFile)
{
   char *pchPtr;
 
   pchPtr = strrchr(pchCombined, '/'); // start from the end and find the first path delimiter
   if (!pchPtr) {
      pchPtr = strrchr(pchCombined, '\\'); // try again with the alternate form
   }
   if (pchPtr) {
      pchPtr++; // advance the pointer to the next character
      if (pchFile) {
         strcpy(pchFile, pchPtr); // copy the filename
      }
      char chOld = *pchPtr;
      *pchPtr = 0; // cut off the filename part
      if (pchPath != pchCombined) {
         if (pchPath) {
            strcpy(pchPath, pchCombined); // copy the path
         }
         *pchPtr = chOld; // restore original string
      }
   } else {
      if (pchFile) {
         *pchFile = 0; // no filename found
      }
      if (pchPath != pchCombined) {
         if (pchPath) {
            strcpy(pchPath, pchCombined); // copy the path
         }
      }
   }
}
 
 
 
void doCleanUp (void)
{
   printer_stop();
   emulator_shutdown();
 
   dsk_eject(&driveA);
   dsk_eject(&driveB);
   tape_eject();
   if (zip_info.pchFileNames) {
      free(zip_info.pchFileNames);
   }
 
   audio_shutdown();
   video_shutdown();
 
   #ifdef DEBUG
   fclose(pfoDebug);
   #endif
 
   SDL_Quit();
}
 
 
 
int main (int argc, char **argv)
{
   dword dwOffset;
   int iExitCondition;
   bool bolDone;
   SDL_Event event;
 
   if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE) < 0) { // initialize SDL
      fprintf(stderr, "SDL_Init() failed: %s\n", SDL_GetError());
      exit(-1);
   }
   atexit(doCleanUp); // install the clean up routine
 
   getcwd(chAppPath, sizeof(chAppPath)-1); // get the location of the executable
 
   loadConfiguration(); // retrieve the emulator configuration
   if (CPC.printer) {
      if (!printer_start()) { // start capturing printer output, if enabled
         CPC.printer = 0;
      }
   }
 
   z80_init_tables(); // init Z80 emulation
 
   if (input_init()) {
      fprintf(stderr, "input_init() failed. Aborting.\n");
      exit(-1);
   }
 
   if (video_init()) {
      fprintf(stderr, "video_init() failed. Aborting.\n");
      exit(-1);
   }
 
   if (audio_init()) {
      fprintf(stderr, "audio_init() failed. Disabling sound.\n");
      CPC.snd_enabled = 0; // disable sound emulation
   }
 
   if (emulator_init()) {
      fprintf(stderr, "emulator_init() failed. Aborting.\n");
      exit(-1);
   }
 
   #ifdef DEBUG
   pfoDebug = fopen("./debug.txt", "wt");
   #endif
 
   bool have_DSK = false;
   bool have_SNA = false;
   bool have_TAP = false;
   memset(&driveA, 0, sizeof(t_drive)); // clear disk drive A data structure
   for (int i = 1; i < argc; i++) { // loop for all command line arguments
      int length = strlen(argv[i]);
      if (length > 5) { // minumum for a valid filename
         char path[_MAX_PATH + 1];
         char extension[5];
         if (argv[i][0] == '"') { // argument passed with quotes?
            length -= 2;
            strncpy(path, &argv[i][1], length); // strip the quotes
         } else {
            strncpy(path, &argv[i][0], sizeof(path)-1); // take it as is
         }
         int pos = length - 4;
         if (pos > 0) {
            char file_name[_MAX_PATH + 1];
            bool zip = false;
            strncpy(extension, &path[pos], 4); // grab the extension
            extension[4] = '\0'; // zero terminate string
            if (strcasecmp(extension, ".zip") == 0) { // are we dealing with a zip archive?
               zip_info.pchZipFile = path;
               zip_info.pchExtension = ".dsk.sna.cdt.voc";
               if (zip_dir(&zip_info)) {
                  continue; // error or nothing relevant found
               } else {
                  strncpy(file_name, zip_info.pchFileNames, sizeof(file_name)-1); // name of the 1st file in the archive
                  pos = strlen(file_name) - 4;
                  strncpy(extension, &file_name[pos], 4); // grab the extension
                  zip = true;
               }
            } else {
               splitPathFileName(path, path, file_name); // split into components
            }
            if ((!have_DSK) && (strcasecmp(extension, ".dsk") == 0)) { // a disk image?
               if (zip) {
                  char chFileName[_MAX_PATH + 1];
                  char *pchPtr = zip_info.pchFileNames;
                  zip_info.dwOffset = *(dword *)(pchPtr + (strlen(pchPtr)+1)); // get the offset into the zip archive
                  if (!zip_extract(path, chFileName, zip_info.dwOffset)) {
                     if (!dsk_load(chFileName, &driveA, 'A')) {
                        strcpy(CPC.drvA_path, path); // if the image loads, copy the infos to the config structure
                        strcpy(CPC.drvA_file, file_name);
                        CPC.drvA_zip = 1;
                        have_DSK = true;
                     }
                     remove(chFileName);
                  }
               } else {
                  strcat(path, file_name);
                  if(!dsk_load(path, &driveA, 'A')) {
                     strcpy(CPC.drvA_path, path);
                     strcpy(CPC.drvA_file, file_name);
                     CPC.drvA_zip = 0;
                     have_DSK = true;
                  }
               }
            }
            if ((!have_SNA) && (strcasecmp(extension, ".sna") == 0)) {
               if (zip) {
                  char chFileName[_MAX_PATH + 1];
                  char *pchPtr = zip_info.pchFileNames;
                  zip_info.dwOffset = *(dword *)(pchPtr + (strlen(pchPtr)+1));
                  if (!zip_extract(path, chFileName, zip_info.dwOffset)) {
                     if (!snapshot_load(chFileName)) {
                        strcpy(CPC.snap_path, path);
                        strcpy(CPC.snap_file, file_name);
                        CPC.snap_zip = 1;
                        have_SNA = true;
                     }
                     remove(chFileName);
                  }
               } else {
                  strcat(path, file_name);
                  if(!snapshot_load(path)) {
                     strcpy(CPC.snap_path, path);
                     strcpy(CPC.snap_file, file_name);
                     CPC.snap_zip = 0;
                     have_SNA = true;
                  }
               }
            }
            if ((!have_TAP) && (strcasecmp(extension, ".cdt") == 0)) {
               if (zip) {
                  char chFileName[_MAX_PATH + 1];
                  char *pchPtr = zip_info.pchFileNames;
                  zip_info.dwOffset = *(dword *)(pchPtr + (strlen(pchPtr)+1));
                  if (!zip_extract(path, chFileName, zip_info.dwOffset)) {
                     if (!tape_insert(chFileName)) {
                        strcpy(CPC.tape_path, path);
                        strcpy(CPC.tape_file, file_name);
                        CPC.tape_zip = 1;
                        have_TAP = true;
                     }
                     remove(chFileName);
                  }
               } else {
                  strcat(path, file_name);
                  if(!tape_insert(path)) {
                     strcpy(CPC.tape_path, path);
                     strcpy(CPC.tape_file, file_name);
                     CPC.tape_zip = 0;
                     have_TAP = true;
                  }
               }
            }
            if ((!have_TAP) && (strcasecmp(extension, ".voc") == 0)) {
               if (zip) {
                  char chFileName[_MAX_PATH + 1];
                  char *pchPtr = zip_info.pchFileNames;
                  zip_info.dwOffset = *(dword *)(pchPtr + (strlen(pchPtr)+1));
                  if (!zip_extract(path, chFileName, zip_info.dwOffset)) {
                     if (!tape_insert_voc(chFileName)) {
                        strcpy(CPC.tape_path, path);
                        strcpy(CPC.tape_file, file_name);
                        CPC.tape_zip = 1;
                        have_TAP = true;
                     }
                     remove(chFileName);
                  }
               } else {
                  strcat(path, file_name);
                  if(!tape_insert_voc(path)) {
                     strcpy(CPC.tape_path, path);
                     strcpy(CPC.tape_file, file_name);
                     CPC.tape_zip = 0;
                     have_TAP = true;
                  }
               }
            }
         }
      }
   }
 
   if ((!have_DSK) && (CPC.drvA_file[0])) { // insert disk in drive A?
      char chFileName[_MAX_PATH + 1];
      char *pchPtr;
 
      if (CPC.drvA_zip) { // compressed image?
         zip_info.pchZipFile = CPC.drvA_path; // pchPath already has path and zip file combined
         zip_info.pchExtension = ".dsk";
         if (!zip_dir(&zip_info)) { // parse the zip for relevant files
            dword n;
            pchPtr = zip_info.pchFileNames;
            for (n = zip_info.iFiles; n; n--) { // loop through all entries
               if (!strcasecmp(CPC.drvA_file, pchPtr)) { // do we have a match?
                  break;
               }
               pchPtr += strlen(pchPtr) + 5; // skip offset
            }
            if (n) {
               zip_info.dwOffset = *(dword *)(pchPtr + (strlen(pchPtr)+1)); // get the offset into the zip archive
               if (!zip_extract(CPC.drvA_path, chFileName, zip_info.dwOffset)) {
                  dsk_load(chFileName, &driveA, 'A');
                  remove(chFileName);
               }
            }
         } else {
            CPC.drvA_zip = 0;
         }
      } else {
         strncpy(chFileName, CPC.drvA_path, sizeof(chFileName)-1);
         strncat(chFileName, CPC.drvA_file, sizeof(chFileName)-1 - strlen(chFileName));
         dsk_load(chFileName, &driveA, 'A');
      }
   }
   memset(&driveB, 0, sizeof(t_drive)); // clear disk drive B data structure
   if (CPC.drvB_file[0]) { // insert disk in drive B?
      char chFileName[_MAX_PATH + 1];
      char *pchPtr;
 
      if (CPC.drvB_zip) { // compressed image?
         zip_info.pchZipFile = CPC.drvB_path; // pchPath already has path and zip file combined
         zip_info.pchExtension = ".dsk";
         if (!zip_dir(&zip_info)) { // parse the zip for relevant files
            dword n;
            pchPtr = zip_info.pchFileNames;
            for (n = zip_info.iFiles; n; n--) { // loop through all entries
               if (!strcasecmp(CPC.drvB_file, pchPtr)) { // do we have a match?
                  break;
               }
               pchPtr += strlen(pchPtr) + 5; // skip offset
            }
            if (n) {
               zip_info.dwOffset = *(dword *)(pchPtr + (strlen(pchPtr)+1)); // get the offset into the zip archive
               if (!zip_extract(CPC.drvB_path, chFileName, zip_info.dwOffset)) {
                  dsk_load(chFileName, &driveB, 'B');
                  remove(chFileName);
               }
            }
         }
         else {
            CPC.drvB_zip = 0;
         }
      }
      else {
         strncpy(chFileName, CPC.drvB_path, sizeof(chFileName)-1);
         strncat(chFileName, CPC.drvB_file, sizeof(chFileName)-1 - strlen(chFileName));
         dsk_load(chFileName, &driveB, 'B');
      }
   }
   if ((!have_TAP) && (CPC.tape_file[0])) { // insert a tape?
      int iErrorCode = 0;
      char chFileName[_MAX_PATH + 1];
      char *pchPtr;
 
      if (CPC.tape_zip) { // compressed image?
         zip_info.pchZipFile = CPC.tape_path; // pchPath already has path and zip file combined
         zip_info.pchExtension = ".cdt.voc";
         iErrorCode = zip_dir(&zip_info);
         if (!iErrorCode) { // parse the zip for relevant files
            dword n;
            pchPtr = zip_info.pchFileNames;
            for (n = zip_info.iFiles; n; n--) { // loop through all entries
               if (!strcasecmp(CPC.tape_file, pchPtr)) { // do we have a match?
                  break;
               }
               pchPtr += strlen(pchPtr) + 5; // skip offset
            }
            if (n) {
               zip_info.dwOffset = *(dword *)(pchPtr + (strlen(pchPtr)+1)); // get the offset into the zip archive
               iErrorCode = zip_extract(CPC.tape_path, chFileName, zip_info.dwOffset);
            }
            else {
               CPC.tape_zip = 0;
               iErrorCode = 1; // file not found
            }
         }
         else {
            CPC.tape_zip = 0;
         }
      }
      else {
         strncpy(chFileName, CPC.tape_path, sizeof(chFileName)-1);
         strncat(chFileName, CPC.tape_file, sizeof(chFileName)-1 - strlen(chFileName));
      }
      if (!iErrorCode) {
         int iOffset = strlen(CPC.tape_file) - 3;
         char *pchExt = &CPC.tape_file[iOffset > 0 ? iOffset : 0]; // pointer to the extension
         if (strncasecmp(pchExt, "cdt", 3) == 0) { // is it a cdt file?
            iErrorCode = tape_insert(chFileName);
         }
         else if (strncasecmp(pchExt, "voc", 3) == 0) { // is it a voc file?
            iErrorCode = tape_insert_voc(chFileName);
         }
         if (CPC.tape_zip) {
            remove(chFileName); // dispose of the temp file
         }
      }
   }
 
// ----------------------------------------------------------------------------
 
   dwTicksOffset = (int)(20.0 / (double)((CPC.speed * 25) / 100.0));
   dwTicksTarget = SDL_GetTicks();
   dwTicksTargetFPS = dwTicksTarget;
   dwTicksTarget += dwTicksOffset;
 
   audio_resume();
 
   iExitCondition = EC_FRAME_COMPLETE;
   bolDone = false;
 
   while (!bolDone) {
      while (SDL_PollEvent(&event)) {
         switch (event.type) {
            case SDL_KEYDOWN:
               {
                  dword cpc_key;
                  if (event.key.keysym.mod & KMOD_SHIFT) { // PC SHIFT key held down?
                     cpc_key = keyboard_shift[event.key.keysym.sym]; // consult the SHIFT table
                  } else if (event.key.keysym.mod & KMOD_CTRL) { // PC CTRL key held down?
                     cpc_key = keyboard_ctrl[event.key.keysym.sym]; // consult the CTRL table
                  } else if (event.key.keysym.mod & KMOD_MODE) { // PC AltGr key held down?
                     cpc_key = keyboard_mode[event.key.keysym.sym]; // consult the AltGr table
                  } else {
                     cpc_key = keyboard_normal[event.key.keysym.sym]; // consult the normal table
                  }
                  if ((!(cpc_key & MOD_EMU_KEY)) && (!CPC.paused) && ((byte)cpc_key != 0xff)) {
                     keyboard_matrix[(byte)cpc_key >> 4] &= ~bit_values[(byte)cpc_key & 7]; // key is being held down
                     if (cpc_key & MOD_CPC_SHIFT) { // CPC SHIFT key required?
                        keyboard_matrix[0x25 >> 4] &= ~bit_values[0x25 & 7]; // key needs to be SHIFTed
                     } else {
                        keyboard_matrix[0x25 >> 4] |= bit_values[0x25 & 7]; // make sure key is unSHIFTed
                     }
                     if (cpc_key & MOD_CPC_CTRL) { // CPC CONTROL key required?
                        keyboard_matrix[0x27 >> 4] &= ~bit_values[0x27 & 7]; // CONTROL key is held down
                     } else {
                        keyboard_matrix[0x27 >> 4] |= bit_values[0x27 & 7]; // make sure CONTROL key is released
                     }
                  }
               }
               break;
 
            case SDL_KEYUP:
               {
                  dword cpc_key;
                  if (event.key.keysym.mod & KMOD_SHIFT) { // PC SHIFT key held down?
                     cpc_key = keyboard_shift[event.key.keysym.sym]; // consult the SHIFT table
                  } else if (event.key.keysym.mod & KMOD_CTRL) { // PC CTRL key held down?
                     cpc_key = keyboard_ctrl[event.key.keysym.sym]; // consult the CTRL table
                  } else if (event.key.keysym.mod & KMOD_MODE) { // PC AltGr key held down?
                     cpc_key = keyboard_mode[event.key.keysym.sym]; // consult the AltGr table
                  } else {
                     cpc_key = keyboard_normal[event.key.keysym.sym]; // consult the normal table
                  }
                  if (!(cpc_key & MOD_EMU_KEY)) { // a key of the CPC keyboard?
                     if ((!CPC.paused) && ((byte)cpc_key != 0xff)) {
                        keyboard_matrix[(byte)cpc_key >> 4] |= bit_values[(byte)cpc_key & 7]; // key has been released
                        keyboard_matrix[0x25 >> 4] |= bit_values[0x25 & 7]; // make sure key is unSHIFTed
                        keyboard_matrix[0x27 >> 4] |= bit_values[0x27 & 7]; // make sure CONTROL key is not held down
                     }
                  } else { // process emulator specific keys
                     switch (cpc_key) {
 
                        case CAP32_FULLSCRN:
                           audio_pause();
                           SDL_Delay(20);
                           video_shutdown();
                           CPC.scr_window = CPC.scr_window ? 0 : 1;
                           if (video_init()) {
                              fprintf(stderr, "video_init() failed. Aborting.\n");
                              exit(-1);
                           }
                           audio_resume();
                           break;
 
                        case CAP32_TAPEPLAY:
                           if (pbTapeImage) {
                              if (CPC.tape_play_button) {
                                 CPC.tape_play_button = 0;
                              } else {
                                 CPC.tape_play_button = 0x10;
                              }
                           }
                           break;
 
                        case CAP32_RESET:
                           emulator_reset(false);
                           break;
 
                        case CAP32_JOY:
                           CPC.joysticks = CPC.joysticks ? 0 : 1;
                           input_swap_joy();
                           break;
 
                        case CAP32_EXIT:
                           exit (0);
                           break;
 
                        case CAP32_FPS:
                           CPC.scr_fps = CPC.scr_fps ? 0 : 1; // toggle fps display on or off
                           break;
 
                        case CAP32_SPEED:
                           CPC.limit_speed = CPC.limit_speed ? 0 : 1;
                           break;
 
                        #ifdef DEBUG
                        case DEBUG_KEY:
                           dwDebugFlag = dwDebugFlag ? 0 : 1;
                           #ifdef DEBUG_CRTC
                           if (dwDebugFlag) {
                              for (int n = 0; n < 14; n++) {
                                 fprintf(pfoDebug, "%02x = %02x\r\n", n, CRTC.registers[n]);
                              }
                           }
                           #endif
                           break;
                        #endif
                     }
                  }
               }
               break;
 
            case SDL_QUIT:
               exit(0);
         }
      }
 
      if (!CPC.paused) { // run the emulation, as long as the user doesn't pause it
         dwTicks = SDL_GetTicks();
         if (dwTicks >= dwTicksTargetFPS) { // update FPS counter?
            dwFPS = dwFrameCount;
            dwFrameCount = 0;
            dwTicksTargetFPS = dwTicks + 1000; // prep counter for the next run
         }
 
         if (CPC.limit_speed) { // limit to original CPC speed?
            if (CPC.snd_enabled) {
               if (iExitCondition == EC_SOUND_BUFFER) {
                  if (!dwSndBufferCopied) { // limit speed?
                     continue; // delay emulation
                  }
                  dwSndBufferCopied = 0;
               }
            } else if (iExitCondition == EC_CYCLE_COUNT) {
               dwTicks = SDL_GetTicks();
               if (dwTicks < dwTicksTarget) { // limit speed?
                  continue; // delay emulation
               }
               dwTicksTarget = dwTicks + dwTicksOffset; // prep counter for the next run
            }
         }
 
         if (!vid_plugin->lock()) { // lock the video buffer
               continue; // skip the emulation if we can't get a lock
            }
         dwOffset = CPC.scr_pos - CPC.scr_base; // offset in current surface row
         if (VDU.scrln > 0) {
            CPC.scr_base = (dword *)back_surface->pixels + (VDU.scrln * CPC.scr_line_offs); // determine current position
         } else {
            CPC.scr_base = (dword *)back_surface->pixels; // reset to surface start
         }
         CPC.scr_pos = CPC.scr_base + dwOffset; // update current rendering position
 
         iExitCondition = z80_execute(); // run the emulation until an exit condition is met
 
         if (iExitCondition == EC_FRAME_COMPLETE) { // emulation finished rendering a complete frame?
            dwFrameCount++;
            if (CPC.scr_fps) {
               char chStr[15];
               sprintf(chStr, "%3dFPS %3d%%", (int)dwFPS, (int)dwFPS * 100 / 50);
               print((dword *)back_surface->pixels + CPC.scr_line_offs, chStr, true); // display the frames per second counter
            }
            vid_plugin->unlock();
            video_display(); // update PC display
         } else {
            vid_plugin->unlock();
         }
      }
   }
 
   exit(0);
}
 

Compare with Previous | Blame | View Log