/** ADAMEm: Coleco ADAM emulator ********************************************/ /** **/ /** Coleco.c **/ /** **/ /** This file contains the Coleco-specific emulation code **/ /** **/ /** Copyright (C) Marcel de Kogel 1996,1997,1998,1999 **/ /** You are not allowed to distribute this software commercially **/ /** Please, notify me, if you make any changes to this file **/ /****************************************************************************/ #include "Coleco.h" #include #include #include #include #include #ifdef ZLIB #include #endif /* #define PRINT_IO */ /* Print accesses to unused I/O ports */ /* #define PRINT_MEM */ /* Print writes to ROM */ VDP_t VDP; /* VDP parameters */ int Z80_IRQ=Z80_IGNORE_INT; /* Z80 IRQ line status */ int EmuMode=0; /* 0=ColecoVision, 1=ADAM */ int UPeriod; /* Number of interrupts/screen update */ int IFreq; /* VDP Interrupt frequency */ int Verbose = 1; /* Debug msgs ON/OFF */ int JoyState[2]; /* Joystick status */ int SpinnerPosition[2]; /* Spinner positions [0..500] */ int PrnType=1; /* Type of printer attached */ int LastSprite[256]; /* Last sprite to be displayed in a row */ int Support5thSprite=0; /* Show only 4 sprites per row */ int RAMPages=1; /* Number of 64K expansion RAM pages */ int SaveSnapshot=0; /* If 1, auto-save snapshot */ int Cheats[16]; /* Cheats to patch into game ROM */ int CheatCount; /* Number of cheats */ int DiskSpeed=100; /* Time in ms it takes to read one... */ int TapeSpeed=100; /* ... block */ char *CartName = "CART.rom"; /* Cartridge ROM file */ char *OS7File = "OS7.rom"; /* ColecoVision ROM file */ char *EOSFile = "EOS.rom"; /* EOS ROM file */ char *WPFile = "WP.rom"; /* SmartWriter ROM file */ char *SoundName; /* Sound log file */ char *DiskName[4]; /* Disk images */ char *TapeName[4]; /* Tape images */ char *SnapshotName; /* Snapshot file name */ char *PrnName; /* Printer log file */ char *LPTName; /* Parallel port log file */ byte *RAM; /* Main and expansion RAM */ byte *ROM,*CART,*OS7,*EOS,*WP; /* ROM pointers */ byte *AddrTabl[256]; /* Currently mapped in pages */ byte *WriteAddrTabl[256]; /* Used to write protect ROM */ byte PCBTable[65536]; /* 1 if address is part of PCB */ static byte DummyWriteTabl[256]; /* ROM */ static byte DummyReadTabl[256]; /* Non-existent memory */ static byte CurrMemSel=0; /* Current memory bank selection */ static byte ADAMNet=0; /* Value last written to ADAMNet */ static int RAMMask; /* Mask for expansion RAM pages */ static int RAMPage; /* Current expansion RAM page selected */ static int SpinnerJoyState[2]; /* Current spinner status */ static int SpinnerRecur[2]; /* Generate INT when >=0x10000 */ static int SpinnerParam[2]; /* Value to add to SpinnerRecur */ static int JoyMode=0; /* Joyst. controller mode */ static int RAMSize; /* Size of RAM in KB */ static int ROMSize; /* Size of ROM in KB */ static int PCB=65216; /* Current PCB address */ static int UsePCB=0; /* Set to 1 after ADAMNet has been reset */ static int NMI_Line=0; /* Set to 1 if NMI is high */ static int numVDPInts; /* Incremented every VDP interrupt */ static int IFreqRecur=0; /* Generate VDP int when this overflows */ static int IFreqParam=0; /* Add this to IFreqRecur every interrupt */ static FILE *PrnStream; /* Printer stream */ static FILE *LPTStream; /* Parallel port stream */ static FILE *DiskStream[4]; /* Disk image streams */ static FILE *TapeStream[4]; /* Tape image streams */ static FILE *SoundStream; /* Sound log stream */ static char tempfiles[8][L_tmpnam]; /* Used for GZIPped disk/tape support */ static int DiskChanged[4]; /* Set to 1 if disk is written to */ static int TapeChanged[4]; /* Set to 1 if tape is written to */ static int DiskSize[4]; /* Size of disk images in kilobytes */ static int TapeSize[4]; /* Size of tape images in kilobytes */ static int DiskMask[4]; /* Mask for disk block nr */ static int TapeMask[4]; /* Mask for tape block nr */ static byte disk_newstatus[4]= /* Next status byte for disk drives */ { 0x80,0x80,0x80,0x80 }; static byte disk_status[4]= /* Current status byte. Set to newstatus */ { 0x80,0x80,0x80,0x80 }; /* when requesting any DCB operation */ static int disk_lastblock[4]= /* Last block read from disk drives */ { 0,0,0,0 }; static byte tape_newstatus[4]= /* Next status byte for tape drives */ { 0x80,0x80,0x80,0x80 }; static byte tape_status[4]= /* Current status byte. Set to newstatus */ { 0x80,0x80,0x80,0x80 }; /* when requesting any DCB operation */ static int tape_lastblock[4]= /* Last block read from tape drives */ { 0,0,0,0 }; static int disk_timeout[4]= /* Number of ms the disk device is busy */ { 0,0,0,0 }; static int tape_timeout[4]= /* Number of ms the tape device is busy */ { 0,0,0,0 }; static int sound_reg[8]= /* Sound chip registers */ { -1,-1,-1,-1,-1,-1,-1,-1 }; static int sound_data; /* Last value written to sound chip */ int PalNum; /* Palette number */ /* Several palettes */ byte Palettes[NR_PALETTES][16*3] = { /* Coleco ADAM's TMS9918 palette */ { 0, 0, 0, 0, 0, 0, 71,183, 59, 124,207,111, 93, 78,255, 128,114,255, 182, 98, 71, 93,200,237, 215,107, 72, 251,143,108, 195,205, 65, 211,218,118, 62,159, 47, 182,100,199, 204,204,204, 255,255,255 }, /* Default V9938 palette */ { 0, 0, 0, 0, 0, 0, 32,192, 32, 96,224, 96, 32, 32,224, 64, 96,224, 160, 32, 32, 64,192,224, 224, 32, 32, 224, 96, 96, 192,192, 32, 192,192,128, 32,128, 32, 192, 64,160, 160,160,160, 224,224,224 }, /* TMS9918 Black & White palette */ { 0, 0, 0, 0, 0, 0, 136,136,136, 172,172,172, 102,102,102, 134,134,134, 120,120,120, 172,172,172, 136,136,136, 172,172,172, 187,187,187, 205,205,205, 118,118,118, 135,135,135, 204,204,204, 255,255,255 }, /* V9938 Black & White palette */ { 0, 0, 0, 0, 0, 0, 144,144,144, 195,195,195, 60, 60, 60, 115,115,115, 80, 80, 80, 178,178,178, 102,102,102, 153,153,153, 198,198,198, 211,211,211, 100,100,100, 129,129,129, 182,182,182, 255,255,255 } }; #define DEFAULT_DISK_SIZE (1440*1024) #define DEFAULT_TAPE_SIZE (256*1024) #define MIN_DISK_SIZE (160*1024) #define MIN_TAPE_SIZE (256*1024) /* Table to convert key values to Coleco codes */ static const byte KeyCodes[16] = { 0x0A,0x0D,0x07,0x0C,0x02,0x03,0x0E,0x05, 0x01,0x0B,0x06,0x09,0x08,0x04,0x0F,0x0F }; static void ResetPCB (void); /* Reset PCB table */ static void PrintChar (char c); /* Send a character to the printer */ void ReadPCB (dword A); /* Called when is read from */ void WritePCB (dword A); /* Called when PCB is written to */ /****************************************************************************/ /*** This function is called when the PCB is relocated or ADAMnet is ***/ /*** reset to initialise the PCB lookup table ***/ /****************************************************************************/ static void SetPCBTable (unsigned oldPCB) { int i; PCBTable[oldPCB]=0; for (i=0;i<15;++i) PCBTable[(oldPCB+4+i*21)&0xFFFF]=0; PCBTable[PCB]=1; for (i=0;i<15;++i) PCBTable[(PCB+4+i*21)&0xFFFF]=i+1; } /****************************************************************************/ /*** Function to read/write memory. They check for memory protection and ***/ /*** reads and writes to the PCB/DCB area ***/ /****************************************************************************/ unsigned Z80_RDMEM (dword a) { int retval=AddrTabl[a>>8][a&0xFF]; if (PCBTable[a]) ReadPCB (a); return retval; } void Z80_WRMEM (dword a,byte v) { WriteAddrTabl[a>>8][a&0xFF]=v; if (PCBTable[a]) WritePCB (a); #ifdef PRINT_MEM if (WriteAddrTabl[a>>8]==DummyWriteTabl) printf ("Wrote %02X to %04X at %04X\n",v,a,Z80_GetPC()); #endif } /****************************************************************************/ /*** This function is called when ED FE occurs ***/ /****************************************************************************/ void Z80_Patch(Z80_Regs *R) { #ifdef DEBUG printf ("Unknown patch called at %04X\n",(R->PC.W.l-2)&0xFFFF); Z80_RegisterDump (); #endif } /****************************************************************************/ /*** This function is called when a sound register is written to. It ***/ /*** writes a new stamp to the sound log file if necessary and calles the ***/ /*** machine-dependent Sound() function ***/ /****************************************************************************/ static void DoSound (byte R,word V) { sound_reg[R]=V; Sound (R,V); } static void WriteIntStamp (void) { if (numVDPInts) { if (numVDPInts==1) fputc (0xFF,SoundStream); else if (numVDPInts<256) { fputc (0xFE,SoundStream); fputc (numVDPInts&0xFF,SoundStream); } else { fputc (0xFD,SoundStream); fputc (numVDPInts&0xFF,SoundStream); fputc (numVDPInts>>8,SoundStream); } numVDPInts=0; } } void UpdateSoundStream (void) { static int old_sound_reg[8]= { -1,-1,-1,-1,-1,-1,-1,-1 }; int i; for (i=7;i>=0;--i) { if (old_sound_reg[i]!=sound_reg[i]) { old_sound_reg[i]=sound_reg[i]; WriteIntStamp (); if (i==0 || i==2 || i==4) { fputc ((i<<4)|((sound_reg[i]&0xF00)>>8),SoundStream); fputc (sound_reg[i]&0xFF,SoundStream); } else fputc ((i<<4)|(sound_reg[i]&0xF),SoundStream); } } } /****************************************************************************/ /*** Functions that write to I/O ports. They are only mapped in if ***/ /*** necessary ***/ /****************************************************************************/ static void OutNo (byte Val) { } static void Out20 (byte Val) { int i; if (Verbose&4) printf ("0x%02X written to ADAMNet at PC=%04X\n",Val,Z80_GetPC()); if (Val==15) ResetPCB (); if ((CurrMemSel&3)==0 && (Val&2)!=(ADAMNet&2)) { if (Val&2) { if (Verbose&4) puts (" Lower 32K:EOS"); for (i=0;i<16384;i+=256) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } for (i=16384;i<24576;i+=256) { AddrTabl[i>>8]=AddrTabl[(i+8192)>>8]=&EOS[i-16384]; WriteAddrTabl[i>>8]=WriteAddrTabl[(i+8192)>>8]=DummyWriteTabl; } } else { if (Verbose &4) puts (" Lower 32K:SmartWriter"); for (i=0;i<32768;i+=256) { AddrTabl[i>>8]=&WP[i]; WriteAddrTabl[i>>8]=DummyWriteTabl; } } } ADAMNet=Val; } static void Out40 (byte Val) { if (LPTStream) { fputc (Val,LPTStream); if (Val==10) fflush (LPTStream); } } static void Out42 (byte Val) { int a,i; a=Val&RAMMask; if (a>=RAMPages) a=-1; if (a!=RAMPage) { RAMPage=a; if (Verbose&4) printf ("Expansion RAM page %d selected\n",RAMPage); if ((CurrMemSel&3)==2) { if (RAMPage>=0) { for (i=0;i<32768;i+=256) { AddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; WriteAddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; } } else { for (i=0;i<32768;i+=256) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } } } if ((CurrMemSel&12)==8) { if (RAMPage>=0) { for (i=32768;i<65536;i+=256) { AddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; WriteAddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; } } else { for (i=32768;i<65536;i+=256) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } } } } } static void Out5E (byte Val) { ModemOut (0,Val); } static void Out5F (byte Val) { ModemOut (1,Val); } static void Out60 (byte Val) { int i; if (Verbose&4) printf ("0x%02X written to bank selector at PC=%04X\n",Val,Z80_GetPC()); if ((Val&3)!=(CurrMemSel&3)) { switch (Val&3) { case 0: if (ADAMNet&2) { if (Verbose&4) puts (" Lower 32K:EOS"); for (i=0;i<16384;i+=256) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } for (i=16384;i<24576;i+=256) { AddrTabl[i>>8]=AddrTabl[(i+8192)>>8]=&EOS[i-16384]; WriteAddrTabl[i>>8]=WriteAddrTabl[(i+8192)>>8]=DummyWriteTabl; } } else { if (Verbose&4) puts (" Lower 32K:SmartWriter"); for (i=0;i<32768;i+=256) { AddrTabl[i>>8]=&WP[i]; WriteAddrTabl[i>>8]=DummyWriteTabl; } } break; case 1: if (Verbose&4) puts (" Lower 32K:RAM"); for (i=0;i<32768;i+=256) { AddrTabl[i>>8]=&RAM[i]; WriteAddrTabl[i>>8]=&RAM[i]; } break; case 2: if (Verbose&4) puts (" Lower 32K:Expansion RAM"); if (RAMPage>=0) { for (i=0;i<32768;i+=256) { AddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; WriteAddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; } } else { for (i=0;i<32768;i+=256) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } } break; case 3: if (Verbose&4) puts (" Lower 32K:OS-7 plus 24K RAM"); for (i=0;i<8192;i+=256) { AddrTabl[i>>8]=&OS7[i]; WriteAddrTabl[i>>8]=DummyWriteTabl; } for (i=8192;i<32768;i+=256) { AddrTabl[i>>8]=&RAM[i]; WriteAddrTabl[i>>8]=&RAM[i]; } break; } } if ((Val&12)!=(CurrMemSel&12)) { switch (Val&12) { case 0: if (Verbose&4) puts (" Upper 32K:RAM"); for (i=32768;i<65536;i+=256) { AddrTabl[i>>8]=&RAM[i]; WriteAddrTabl[i>>8]=&RAM[i]; } break; case 4: if (Verbose&4) puts (" Upper 32K:Expansion ROM"); for (i=32768;i<65536;i+=256) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } break; case 8: if (Verbose&4) puts (" Upper 32K:Expansion RAM"); if (RAMPage>=0) { for (i=32768;i<65536;i+=256) { AddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; WriteAddrTabl[i>>8]=&RAM[i+65536+RAMPage*65536]; } } else { for (i=32768;i<65536;i+=256) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } } break; case 12: if (Verbose&4) puts (" Upper 32K:Cartridge ROM"); for (i=32768;i<65536;i+=256) { AddrTabl[i>>8]=&CART[i-32768]; WriteAddrTabl[i>>8]=DummyWriteTabl; } break; } } CurrMemSel=Val; } static void Out80 (byte Val) { JoyMode=0; } static void OutA0 (byte Val) { if (!VDP.Mode) ++VDP.Addr; if (VDP.VRAM[VDP.Addr&VDP.VRAMSize]!=Val) { VDP.VRAM[VDP.Addr&VDP.VRAMSize]=Val; VDP.ScreenChanged=1; } VDP.Addr++; VDP.VKey=1; } static void OutA1 (byte Val) { if (VDP.VKey) { VDP.VR=Val; VDP.VKey=0; } else { VDP.VKey=1; switch(Val&0xC0) { case 0x80: Val&=7; if (Verbose&2) printf ("VDPOut(%02X,%02X) - PC=%04X\n",Val,VDP.VR,Z80_GetPC()); VDP.ScreenChanged=1; switch (Val) { case 1: if ((VDP.VR&0x20)==0) NMI_Line=0; VDP.VRAMSize=(VDP.VR&0x80)? 0x3FFF:0x0FFF; break; case 7: VDP.FGColour=VDP.VR>>4; VDP.BGColour=VDP.VR&0x0F; ColourScreen (); break; } VDP.Reg[Val]=VDP.VR; break; case 0x40: VDP.Addr=VDP.VR+(Val&0x3F)*256; VDP.Mode=1; break; case 0x00: VDP.Addr=VDP.VR+(Val&0x3F)*256; VDP.Mode=0; break; } } } static void OutC0 (byte Val) { JoyMode=1; } static void OutE0 (byte Val) { byte Port; if(Val&0x80) { Port=Val&0x70; if((Port==0x00)||(Port==0x20)||(Port==0x40)) { sound_data=Val; DoSound(Port>>4,(Val&0x0F)|(sound_reg[Port>>4]&0x3F0)); } else DoSound(Port>>4,Val&0x0F); } else DoSound((sound_data>>4)&0x07,(sound_data&0x0F)|((word)(Val&0x3F)<<4)); } typedef void (*OutPortFn) (byte Val); OutPortFn OutPort[256]= { OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo,OutNo, Out80,Out80,Out80,Out80,Out80,Out80,Out80,Out80, Out80,Out80,Out80,Out80,Out80,Out80,Out80,Out80, Out80,Out80,Out80,Out80,Out80,Out80,Out80,Out80, Out80,Out80,Out80,Out80,Out80,Out80,Out80,Out80, OutA0,OutA1,OutA0,OutA1,OutA0,OutA1,OutA0,OutA1, OutA0,OutA1,OutA0,OutA1,OutA0,OutA1,OutA0,OutA1, OutA0,OutA1,OutA0,OutA1,OutA0,OutA1,OutA0,OutA1, OutA0,OutA1,OutA0,OutA1,OutA0,OutA1,OutA0,OutA1, OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0, OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0, OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0, OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0,OutC0, OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0, OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0, OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0, OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0,OutE0 }; FASTCALL void Z80_Out (unsigned Port,byte Val) { OutPortFn fn; Port&=0xff; fn=OutPort[Port]; #ifdef PRINT_IO if (fn==OutNo) { printf ("Attempt to write %02X to port %02X at %04X\n",Val,Port,Z80_GetPC()); Z80_RegisterDump (); } #endif (*fn)(Val); } /****************************************************************************/ /*** Functions that read I/O ports. They are only mapped in if necessary ***/ /****************************************************************************/ static byte InpNo (void) { return Z80_RDMEM((Z80_GetPC()-1)&65535); } static byte Inp20 (void) { return ADAMNet; } static byte Inp40 (void) { return 0x41; } static byte Inp5E (void) { return ModemIn (0); } static byte Inp5F (void) { return ModemIn (1); } static byte Inp60 (void) { return CurrMemSel; } static byte InpA0 (void) { byte retval; retval=VDP.VRAM[VDP.Addr&VDP.VRAMSize]; VDP.Addr++; VDP.VKey=1; return retval; } static byte InpA1 (void) { byte retval; retval=VDP.Status; if (VDP.Status & 0x80) VDP.Status&=0x3F; else VDP.Status=0x1F; VDP.VKey=1; NMI_Line=0; return retval; } static byte InpE0 (void) { byte retval; if (JoyMode) { retval=JoyState[0]>>8; if ((retval&0x0A)==0) retval|=0x02; if ((retval&0x05)==0) retval|=0x01; return retval; } else { retval=JoyState[0]&0xFF; return (retval&0xF0) |KeyCodes[retval&0x0F]; } } static byte InpE2 (void) { byte retval; if (JoyMode) { retval=JoyState[1]>>8; if ((retval&0x0A)==0) retval|=0x02; if ((retval&0x05)==0) retval|=0x01; return retval; } else { retval=JoyState[1]&0xFF; return (retval&0xF0) |KeyCodes[retval&0x0F]; } } typedef byte (*InPortFn) (void); InPortFn InPort[256]= { InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpA0,InpA1,InpA0,InpA1,InpA0,InpA1,InpA0,InpA1, InpA0,InpA1,InpA0,InpA1,InpA0,InpA1,InpA0,InpA1, InpA0,InpA1,InpA0,InpA1,InpA0,InpA1,InpA0,InpA1, InpA0,InpA1,InpA0,InpA1,InpA0,InpA1,InpA0,InpA1, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo,InpNo, InpE0,InpE0,InpE2,InpE2,InpE0,InpE0,InpE2,InpE2, InpE0,InpE0,InpE2,InpE2,InpE0,InpE0,InpE2,InpE2, InpE0,InpE0,InpE2,InpE2,InpE0,InpE0,InpE2,InpE2, InpE0,InpE0,InpE2,InpE2,InpE0,InpE0,InpE2,InpE2 }; FASTCALL byte Z80_In (unsigned Port) { InPortFn fn; Port&=0xff; fn=InPort[Port]; #ifdef PRINT_IO if (fn==InpNo) { printf ("Attempt to read port %02X at %04X\n",Port,Z80_GetPC()); Z80_RegisterDump (); } #endif return (*fn)(); } /****************************************************************************/ /*** Reset the Coleco ***/ /****************************************************************************/ void ResetColeco (int mode) { static byte VDPInit[8] = { 0x00,0x80,0x06,0x80,0x00,0x36,0x07,0x00 }; int i; if (mode==1) { Z80_Reset (); /* Reset the Z80 emulator */ Z80_Out (0x42,0); /* Select expansion RAM page 0 */ Z80_Out (0x7F,(mode==1)? 15:0); /* Select either WP or OS-7 */ Z80_Out (0x3F,0); return; } for (i=0;i<8;++i) VDP.Reg[i]=VDPInit[i]; VDP.VRAMSize=0x3FFF; VDP.VKey=1; VDP.Status=0; VDP.FGColour=VDP.BGColour=0; VDP.Addr=0; if (mode!=2) ColourScreen (); VDP.ScreenChanged=1; memset (VDP.VRAM,0,0x4000); /* Clear RAM and VRAM */ for (i=0;i=0)? "OK":"FAILED"); } else fclose (TapeStream[i]); TapeName[i]=NULL; } static void DiskClose (int i) { int a; if (!DiskStream[i]) return; if (tempfiles[i][0]!='\0') { if (DiskChanged[i]) GZ_Copy (DiskStream[i],DiskName[i]); fclose (DiskStream[i]); if (Verbose) printf ("Removing temporary file %s... ",tempfiles[i]); a=remove (tempfiles[i]); if (Verbose) puts ((a>=0)? "OK":"FAILED"); } else fclose (DiskStream[i]); DiskName[i]=NULL; } static void OpenPrinter (void) { if (PrnName) { if (Verbose) printf ("Opening printer stream %s... ",PrnName); PrnStream=fopen (PrnName,(PrnType==1)? "wt":"wb"); if (Verbose) puts ((PrnStream)? "OK":"FAILED"); if (PrnStream) { ResetPrinter (); if (PrnType==2) { /* Set linefeed distance to 6/72" */ fputc (27,PrnStream); fputc ('A',PrnStream); fputc (6,PrnStream); fputc (27,PrnStream); fputc ('2',PrnStream); } } else PrnName=NULL; } else { if (Verbose) puts ("Logging printer output to stdout"); PrnStream=stdout; PrnName="stdout"; } } static void OpenLPT (void) { if (LPTName) { if (Verbose) printf ("Opening parallel port stream %s... ",LPTName); LPTStream=fopen (LPTName,"wb"); if (Verbose) puts ((LPTStream)? "OK":"FAILED"); if (!LPTStream) LPTName=NULL; } else { if (Verbose) puts ("Logging parallel port output to stdout"); LPTStream=stdout; LPTName="stdout"; } } static void OpenSoundLog (void) { if (SoundName) { if (Verbose) printf ("Opening sound log file %s... ",SoundName); SoundStream=fopen(SoundName,"wb"); if (Verbose) puts ((SoundStream)? "OK":"FAILED"); if (SoundStream) { fwrite ("Coleco Sound File",1,17,SoundStream); fputc (0x1A,SoundStream); /* EOF */ fputc (0x01,SoundStream); /* Version number (0x101) */ fputc (0x01,SoundStream); /* ... */ fputc (IFreq&0xFF,SoundStream); /* Interrupt frequency */ } else { SoundStream=NULL; SoundName=NULL; } } numVDPInts=0; } static void ClosePrinter (void) { if (PrnStream) { PrintChar (0); ResetPrinter (); if (PrnStream!=stdout) fclose (PrnStream); PrnStream=NULL; PrnName=NULL; } } static void CloseLPT (void) { if (LPTStream) { if (LPTStream!=stdout) fclose (LPTStream); LPTStream=NULL; LPTName=NULL; } } static void CloseSoundLog (void) { if (SoundStream) { fclose (SoundStream); SoundStream=NULL; SoundName=NULL; } } #ifdef ZLIB #define fopen gzopen #define fclose gzclose #define fread(B,N,L,F) gzread(F,B,(L)*(N)) #define fwrite(B,N,L,F) gzwrite(F,B,(L)*(N)) #endif /****************************************************************************/ /*** Get parameters from snapshot file ***/ /****************************************************************************/ static int GetSnapshotParams (FILE *f) { static char _TapeName[4][256],_DiskName[4][256],_CartName[256]; byte buf[15]; int i; if (fread(buf,1,16,f)!=16) return -1; /* Read error */ buf[15]='\0'; if (strcmp(buf,"ADAMEm snapshot")) return -2; /* Not a snapshot file */ if (fread(buf,1,2,f)!=2) return -1; i=buf[0]*256+buf[1]; if (i>0x100) return -3; /* Version not supported */ if (fread(buf,1,1,f)!=1) return -1; EmuMode=buf[0]; if (fread(buf,1,1,f)!=1) return -1; RAMPages=buf[0]; if (fread(_CartName,1,256,f)!=256) return -1; if (_CartName[0]) { _CartName[255]='\0'; CartName=_CartName; } else CartName=NULL; for (i=0;i<4;++i) { if (fread(_DiskName[i],1,256,f)!=256) return -1; if (_DiskName[i][0]) { _DiskName[i][255]='\0'; DiskName[i]=_DiskName[i]; } else DiskName[i]=NULL; } for (i=0;i<4;++i) { if (fread(_TapeName[i],1,256,f)!=256) return -1; if (_TapeName[i][0]) { _TapeName[i][255]='\0'; TapeName[i]=_TapeName[i]; } else TapeName[i]=NULL; } return 1; } /****************************************************************************/ /*** Read data from snapshot file ***/ /****************************************************************************/ static int GetSnapshotData (FILE *f) { Z80_Regs R; byte buf[256]; int i; memset (&R,0,sizeof(R)); if (fread(buf,1,31,f)!=31) return -1; R.AF.B.h=buf[0]; R.AF.B.l=buf[1]; R.BC.B.h=buf[2]; R.BC.B.l=buf[3]; R.DE.B.h=buf[4]; R.DE.B.l=buf[5]; R.HL.B.h=buf[6]; R.HL.B.l=buf[7]; R.IX.B.h=buf[8]; R.IX.B.l=buf[9]; R.IY.B.h=buf[10]; R.IY.B.l=buf[11]; R.PC.B.h=buf[12]; R.PC.B.l=buf[13]; R.SP.B.h=buf[14]; R.SP.B.l=buf[15]; R.AF2.B.h=buf[16]; R.AF2.B.l=buf[17]; R.BC2.B.h=buf[18]; R.BC2.B.l=buf[19]; R.DE2.B.h=buf[20]; R.DE2.B.l=buf[21]; R.HL2.B.h=buf[22]; R.HL2.B.l=buf[23]; R.IFF1=buf[24]; R.IFF2=buf[25]; R.HALT=buf[26]; R.IM=buf[27]; R.I=buf[28]; R.R=buf[29]; R.R2=buf[30]; Z80_SetRegs (&R); if (fread(buf,1,4,f)!=4) return -1; if (buf[0]&0x80) Z80_ICount=(-1)&(~0xFFFFFFFF); /* Sign extend */ else Z80_ICount=0; Z80_ICount|=(buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|(buf[3]<<0); if (fread(buf,1,36,f)!=36) return -1; CurrMemSel=buf[0]; ADAMNet=buf[1]; RAMPage=buf[2]; NMI_Line=buf[3]; for (i=0;i<4;++i) { disk_newstatus[i]=buf[4+i]; tape_newstatus[i]=buf[8+i]; disk_lastblock[i]=buf[12+i]; tape_lastblock[i]=buf[16+i]; disk_status[i]=buf[20+i]; tape_status[i]=buf[24+i]; disk_timeout[i]=buf[28+i]; tape_timeout[i]=buf[32+i]; } if (fread(buf,1,14,f)!=14) return -1; for (i=0;i<8;++i) VDP.Reg[i]=buf[i]; VDP.Status=buf[8]; VDP.Mode=buf[9]; VDP.VKey=buf[10]; VDP.VR=buf[11]; VDP.Addr=(buf[12]<<8)+buf[13]; if (fread(buf,1,17,f)!=17) return -1; for (i=0;i<8;++i) { sound_reg[i]=(buf[i*2+0]<<8)+buf[i*2+1]; } sound_data=buf[16]; if (fread(buf,1,1,f)!=1) return -1; JoyMode=buf[0]; if (fread(buf,1,4,f)!=4) return -1; IFreqRecur=(buf[0]<<24)+(buf[1]<<16)+(buf[2]<<8)+(buf[3]<<0); if (fread(buf,1,3,f)!=3) return -1; UsePCB=buf[0]; PCB=(buf[1]<<8)+buf[2]; if (fread(VDP.VRAM,1,16384,f)!=16384) return -1; if (fread(RAM,1,RAMSize,f)!=RAMSize) return -1; return 1; } /****************************************************************************/ /*** Save current emulation status to snapshot file ***/ /****************************************************************************/ static int _SaveSnapshotFile (FILE *f) { Z80_Regs R; byte buf[256]; int i; if (fwrite("ADAMEm snapshot\032",1,16,f)!=16) return -2; /* Write error */ buf[0]=0x01; buf[1]=0x00; if (fwrite(buf,1,2,f)!=2) return -2; buf[0]=EmuMode; if (fwrite(buf,1,1,f)!=1) return -2; buf[0]=RAMPages; if (fwrite(buf,1,1,f)!=1) return -2; memset (buf,0,256); if (CartName) strcpy (buf,CartName); if (fwrite(buf,1,256,f)!=256) return -2; for (i=0;i<4;++i) { memset (buf,0,256); if (DiskName[i]) strcpy (buf,DiskName[i]); if (fwrite(buf,1,256,f)!=256) return -2; } for (i=0;i<4;++i) { memset (buf,0,256); if (TapeName[i]) strcpy (buf,TapeName[i]); if (fwrite(buf,1,256,f)!=256) return -2; } Z80_GetRegs (&R); buf[0]=R.AF.B.h; buf[1]=R.AF.B.l; buf[2]=R.BC.B.h; buf[3]=R.BC.B.l; buf[4]=R.DE.B.h; buf[5]=R.DE.B.l; buf[6]=R.HL.B.h; buf[7]=R.HL.B.l; buf[8]=R.IX.B.h; buf[9]=R.IX.B.l; buf[10]=R.IY.B.h; buf[11]=R.IY.B.l; buf[12]=R.PC.B.h; buf[13]=R.PC.B.l; buf[14]=R.SP.B.h; buf[15]=R.SP.B.l; buf[16]=R.AF2.B.h; buf[17]=R.AF2.B.l; buf[18]=R.BC2.B.h; buf[19]=R.BC2.B.l; buf[20]=R.DE2.B.h; buf[21]=R.DE2.B.l; buf[22]=R.HL2.B.h; buf[23]=R.HL2.B.l; buf[24]=R.IFF1; buf[25]=R.IFF2; buf[26]=R.HALT; buf[27]=R.IM; buf[28]=R.I; buf[29]=R.R; buf[30]=R.R2; if (fwrite(buf,1,31,f)!=31) return -2; buf[0]=Z80_ICount>>24; buf[1]=Z80_ICount>>16; buf[2]=Z80_ICount>>8; buf[3]=Z80_ICount>>0; if (fwrite(buf,1,4,f)!=4) return -2; buf[0]=CurrMemSel; buf[1]=ADAMNet; buf[2]=RAMPage; buf[3]=NMI_Line; for (i=0;i<4;++i) { buf[4+i]=disk_newstatus[i]; buf[8+i]=tape_newstatus[i]; buf[12+i]=disk_lastblock[i]; buf[16+i]=tape_lastblock[i]; buf[20+i]=disk_status[i]; buf[24+i]=tape_status[i]; buf[28+i]=disk_timeout[i]; buf[32+i]=tape_timeout[i]; } if (fwrite(buf,1,36,f)!=36) return -2; for (i=0;i<8;++i) buf[i]=VDP.Reg[i]; buf[8]=VDP.Status; buf[9]=VDP.Mode; buf[10]=VDP.VKey; buf[11]=VDP.VR; buf[12]=VDP.Addr>>8; buf[13]=VDP.Addr>>0; if (fwrite(buf,1,14,f)!=14) return -2; for (i=0;i<8;++i) { buf[i*2+0]=sound_reg[i]>>8; buf[i*2+1]=sound_reg[i]>>0; } buf[16]=sound_data; if (fwrite(buf,1,17,f)!=17) return -2; buf[0]=JoyMode; if (fwrite(buf,1,1,f)!=1) return -2; buf[0]=IFreqRecur>>24; buf[1]=IFreqRecur>>16; buf[2]=IFreqRecur>>8; buf[3]=IFreqRecur>>0; if (fwrite(buf,1,4,f)!=4) return -2; buf[0]=UsePCB; buf[1]=PCB>>8; buf[2]=PCB>>0; if (fwrite(buf,1,3,f)!=3) return -2; if (fwrite(VDP.VRAM,1,16384,f)!=16384) return -2; if (fwrite(RAM,1,RAMSize,f)!=RAMSize) return -2; return 1; } int SaveSnapshotFile (char *filename) { int retval; FILE *f; f=fopen (filename,"wb"); if (!f) return -1; /* File open error */ retval=_SaveSnapshotFile (f); fclose (f); return retval; } /****************************************************************************/ /*** This function allocates all resources necessary for the ***/ /*** hardware-independent part of the code and starts the emulation. In ***/ /*** case of a failure, this function returns 0 ***/ /****************************************************************************/ int StartColeco(void) { FILE *f,*f_snap; int *T,i,j; char *P; T=(int *)"\01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; #ifdef LSB_FIRST if(*T!=1) { printf("********** This machine is high-endian. **********\n" "Take #define LSB_FIRST out and compile ADAMem again\n"); return 0; } #else if(*T==1) { printf("********* This machine is low-endian. **********\n" "Insert #define LSB_FIRST and compile ADAMem again\n"); return 0; } #endif if (DiskSpeed<1) DiskSpeed=1; if (DiskSpeed>10000) DiskSpeed=10000; if (TapeSpeed<1) TapeSpeed=1; if (TapeSpeed>10000) TapeSpeed=10000; f_snap=NULL; if (SnapshotName) { if (Verbose) printf ("Opening snapshot %s... ",SnapshotName); f_snap=fopen (SnapshotName,"rb"); if (!f_snap) { if (Verbose) puts ("FAILED"); } else { if (Verbose) printf ("OK - Reading parameters... "); i=GetSnapshotParams (f_snap); if (i<0) { fclose (f_snap); if (Verbose) { printf ("FAILED:"); switch (i) { case -1: puts ("Read error"); break; case -2: puts ("File type not supported"); break; case -3: puts ("File version not supported"); break; default: printf ("Unknown error code %d\n",i); } } return 0; } if (Verbose) puts ("OK"); } } if (EmuMode) { ROMSize=8+8+32+32; /* OS-7, EOS, WP, CART */ if (RAMPages>255) RAMPages=255; if (RAMPages<0) RAMPages=0; if (RAMPages) /* RAMPages should be a power of 2 */ { for (j=1;j1) printf("%d bytes loaded\n",j); else puts(P); if (j && CheatCount) { if (Verbose) printf (" Patching cheats into cartridge ROM code..."); for (i=0;i>8)&0x7FFF; if (Verbose) printf ("%X...",j); CART[j]=Cheats[i]&255; } if (Verbose) puts(""); } } if (EmuMode) { if(Verbose) printf(" Opening %s... ",EOSFile); j=0; f=fopen(EOSFile,"rb"); if (f) { if(fread(EOS,1,0x2000,f)==0x2000) j=1; fclose (f); } if (Verbose) puts(j? "OK":"FAILED"); if (!j) return 0; if (Verbose) printf(" Opening %s... ",WPFile); j=0; f=fopen(WPFile,"rb"); if (f) { if (fread(WP,1,0x8000,f)==0x8000) j=1; fclose(f); } if (Verbose) puts(j? "OK":"FAILED"); if (!j) return(0); } if (EmuMode) { for (i=0;i<4;++i) { DiskOpen (i); TapeOpen (i); } OpenPrinter (); OpenLPT (); } OpenSoundLog (); if (Verbose) puts ("Initialising memory mapper..."); if (EmuMode) { for (i=0x20;i<0x40;++i) { OutPort[i]=Out20; InPort[i]=Inp20; } for (i=0x60;i<0x80;++i) { OutPort[i]=Out60; InPort[i]=Inp60; } if (RAMPages>1) /* Only map in RAM page selector if */ OutPort[0x42]=Out42; /* expander is >64KB */ OutPort[0x5E]=Out5E; OutPort[0x5F]=Out5F; InPort[0x5E]=Inp5E; InPort[0x5F]=Inp5F; OutPort[0x40]=Out40; InPort[0x40]=Inp40; } for (i=0;i<65536;i+=256) { if (EmuMode) { if (i<0x8000) { AddrTabl[i>>8]=&WP[i]; WriteAddrTabl[i>>8]=DummyWriteTabl; } else { AddrTabl[i>>8]=&RAM[i]; WriteAddrTabl[i>>8]=&RAM[i]; } } else { if (i>=0x6000 && i<0x8000) { AddrTabl[i>>8]=&RAM[i&1023]; WriteAddrTabl[i>>8]=&RAM[i&1023]; } else if (i<0x2000) { AddrTabl[i>>8]=&OS7[i]; WriteAddrTabl[i>>8]=DummyWriteTabl; } else if (i<0x6000) { AddrTabl[i>>8]=DummyReadTabl; WriteAddrTabl[i>>8]=DummyWriteTabl; } else { AddrTabl[i>>8]=&CART[i-0x8000]; WriteAddrTabl[i>>8]=DummyWriteTabl; } } } if (Verbose) printf("Initialising VDP and CPU...\n"); ResetColeco (2); if (f_snap) { if (Verbose) printf ("Reading snapshot data... "); i=GetSnapshotData (f_snap); if (i<0) { if (Verbose) { printf ("FAILED:"); switch (i) { case -1: puts ("Read error"); break; default: printf ("Unknown error code %d\n",i); } } return 0; } if (Verbose) puts ("OK"); } #ifdef MSDOS if (!InitMachine()) return 0; #endif if (f_snap) { VDP.VRAMSize=(VDP.Reg[1]&0x80)? 0x3FFF:0x0FFF; VDP.FGColour=VDP.Reg[7]>>4; VDP.BGColour=VDP.Reg[7]&0x0F; ColourScreen (); VDP.ScreenChanged=1; for (i=0;i<8;++i) DoSound (i,sound_reg[i]); i=ADAMNet; ADAMNet=0; Z80_Out (0x3F,i); i=RAMPage; RAMPage=0; Z80_Out (0x42,i); i=CurrMemSel; CurrMemSel=0; Z80_Out (0x7F,i); if (UsePCB) SetPCBTable(0); } InitDone=1; PC=Z80 (); return 1; } #ifdef ZLIB #undef fopen #undef fclose #undef fread #undef fwrite #endif /****************************************************************************/ /*** Free memory allocated with StartColeco() ***/ /****************************************************************************/ void TrashColeco(void) { int i; #ifdef MSDOS if (InitDone) TrashMachine (); #else if (Verbose) puts ("\n\nShutting down...\n"); #endif if (SaveSnapshot && SnapshotName && InitDone) { if (Verbose) { printf ("Writing snapshot %s...",SnapshotName); fflush (stdout); } i=SaveSnapshotFile(SnapshotName); if (Verbose) { if (i<0) { printf ("FAILED: "); switch (i) { case -1: puts ("Cannot open file"); break; case -2: puts ("Write error (disk full ?)"); break; default: printf ("Unknown error code %d\n",i); } } else puts ("OK"); } } if (RAM) free (RAM); if (ROM) free (ROM); if (VDP.VRAM) free (VDP.VRAM); for (i=0;i<4;++i) { DiskClose (i); TapeClose (i); } ClosePrinter (); CloseLPT (); CloseSoundLog (); if(Verbose && InitDone) printf("EXITED at PC = %Xh.\n",PC); } /****************************************************************************/ /*** Keyboard emulation routines ***/ /****************************************************************************/ static byte keyboard_buffer[16]; static byte keyboard_buffer_int_count[16]; static int keyboard_buffer_count=0; #define MAX_KEYBOARD_INT_COUNT 10 void AddToKeyboardBuffer (byte ch) { if (ch==0) ch=255; /* If NUL is passed, change to 255 */ keyboard_buffer[keyboard_buffer_count]=ch; keyboard_buffer_int_count[keyboard_buffer_count]=0; keyboard_buffer_count=(keyboard_buffer_count+1)&15; keyboard_buffer[keyboard_buffer_count]=0; } static int GetKeyboardChar (void) { int retval; keyboard_buffer_count=(keyboard_buffer_count-1)&15; retval=keyboard_buffer[keyboard_buffer_count]; keyboard_buffer[keyboard_buffer_count]=0; return retval; } static void UpdateKeyboard (int mode,unsigned DCB) { int addr,count,i,a; static byte newkbdstatus=0x80; if (!mode) { RAM[DCB]=newkbdstatus; return; } switch (RAM[DCB]) { case 1: RAM[DCB]=0x80; newkbdstatus=0x80; break; case 2: RAM[DCB]=0x80; newkbdstatus=0x80; break; case 3: RAM[DCB]=0x9B; newkbdstatus=0x80; break; case 4: addr=RAM[(DCB+1)&0xFFFF]+RAM[(DCB+2)&0xFFFF]*256; count=RAM[(DCB+3)&0xFFFF]+RAM[(DCB+4)&0xFFFF]*256; newkbdstatus=0x80; for (i=0;i=32 && (c&128)==0) || c==9) { if (c>32) { flushbuf=0; if (linecount+1>numchars) numchars=linecount+1; } if (linebuffer[linecount]==32) linebuffer[linecount]=c; if (backwards) linecount--; else linecount++; } break; } if (linecount<0) linecount=0; if (linecount>=1024) linecount=1023; if (c==0 || (flushbuf&1)) { for (i=0;i>8; break; case 2: if (Verbose&8) printf ("Disk %d: Soft reset\n",nr+1); RAM[DCB]=disk_status[nr]=disk_newstatus[nr]=0x80; break; case 3: case 4: disk_status[nr]=disk_newstatus[nr]; block=RAM[(DCB+5)&0xFFFF]+RAM[(DCB+6)&0xFFFF]*256+ RAM[(DCB+7)&0xFFFF]*65536+RAM[(DCB+8)&0xFFFF]*256*65536; block&=DiskMask[nr]; if (disk_status[nr]==0x9B) { if (Verbose&8) printf ("Disk %d: %s block %d failed - device busy\n", nr+1,(RAM[DCB]==4)?"Reading":"Writing",block); RAM[DCB]=0x9B; } else if (DiskStream[nr]) { if (block>=DiskSize[nr]) { RAM[(DCB+20)&0xFFFF]|=2; len=0; disk_lastblock[nr]=-1; break; } if (block==disk_lastblock[nr] || RAM[DCB]==3) { addr=RAM[(DCB+1)&0xFFFF]+RAM[(DCB+2)&0xFFFF]*256; len=RAM[(DCB+3)&0xFFFF]+RAM[(DCB+4)&0xFFFF]*256; if (len>blocksize) len=blocksize; if (Verbose&8) printf ("Disk %d: %s %d bytes at %04X starting at block %d\n", nr+1,(RAM[DCB]==4)? "Reading":"Writing",len,addr,block); block*=blocksize/512; for (i=0,j=0;i>8; break; case 2: if (Verbose&8) printf ("Tape %d: Soft reset\n",nr+1); RAM[DCB]=tape_status[nr]=tape_newstatus[nr]=0x80; break; case 3: case 4: tape_status[nr]=tape_newstatus[nr]; block=RAM[(DCB+5)&0xFFFF]+RAM[(DCB+6)&0xFFFF]*256+ RAM[(DCB+7)&0xFFFF]*65536+RAM[(DCB+8)&0xFFFF]*256*65536; block&=TapeMask[nr]; if (tape_status[nr]==0x9B) { if (Verbose&8) printf ("Tape %d: %s block %d failed - device busy\n", nr+1,(RAM[DCB]==4)?"Reading":"Writing",block); RAM[DCB]=0x9B; } else if (TapeStream[nr]) { if (block>=TapeSize[nr]) { RAM[(DCB+20)&0xFFFF]|=2; len=0; tape_lastblock[nr]=-1; break; } if (block==tape_lastblock[nr] || RAM[DCB]==3) { addr=RAM[(DCB+1)&0xFFFF]+RAM[(DCB+2)&0xFFFF]*256; len=RAM[(DCB+3)&0xFFFF]+RAM[(DCB+4)&0xFFFF]*256; if (len>1024) len=1024; if (Verbose&8) printf ("Tape %d: %s %d bytes at %04X starting at block %d\n", nr+1,(RAM[DCB]==4)? "Reading":"Writing",len,addr,block); block*=2; for (i=0,j=0;i=0x80 || RAM[DCB]==0)) return; */ dev_id=(RAM[(DCB+9)&0xFFFF]<<4)+(RAM[(DCB+16)&0xFFFF]&0x0F); switch (dev_id) { case 1: UpdateKeyboard (mode,DCB); break; case 2: UpdatePrinter (mode,DCB); break; case 4: case 5: case 6: case 7: UpdateDisk (mode,dev_id-4,DCB); break; case 8: case 9: case 24: case 25: UpdateTape (mode,dev_id/16+((dev_id&1)*2),DCB); break; default: RAM[DCB]=0x9B; /* Unknown device - return time out */ } } /****************************************************************************/ /*** This function is called when the PCB is read from or written to ***/ /****************************************************************************/ static void UpdatePCB (int mode) { unsigned oldPCB; if (mode) { switch (RAM[PCB]) { case 1: /* Synchronise Z80A */ if (Verbose&16) puts ("Z80A synchronised"); RAM[PCB]=129; break; case 2: /* Synchronise master 6801 */ if (Verbose&16) puts ("Master 6801 synchronised"); RAM[PCB]=130; break; case 3: /* Relocate PCB */ /* Set status byte */ RAM[PCB]=131; /* Get the new PCB address and update the PCB lookup table */ oldPCB=PCB; PCB=RAM[(PCB+1)&0xFFFF]+RAM[(PCB+2)&0xFFFF]*256; if (Verbose&16) printf ("PCB relocated to %04X\n",PCB); SetPCBTable (oldPCB); break; default: if (RAM[PCB]!=0 && RAM[PCB]<0x80) if (Verbose&16) printf ("Unimplemented PCB operation %d\n",RAM[PCB]); } } else { if (Verbose&16) printf ("PCB: Reading status (%02X)\n",RAM[PCB]); } } /****************************************************************************/ /*** Check if a given address is in the PCB/DCB area, given that ***/ /*** PCBTable[address]==1 ***/ /****************************************************************************/ static int IsInPCB (dword a) { /* Check if PCB is mapped in */ if (a<8192 && (CurrMemSel&3)!=1) return 0; if (a<32768 && (CurrMemSel&3)!=1 && (CurrMemSel&3)!=3) return 0; if (a>32767 && (CurrMemSel&12)!=0) return 0; /* Check number of active devices */ if (a>=PCB+4+RAM[(PCB+3)&0xFFFF]*21) return 0; return 1; } /****************************************************************************/ /*** This function is called when the PCB is read from ***/ /****************************************************************************/ void ReadPCB (dword A) { if (!IsInPCB(A)) return; if (A==PCB) UpdatePCB (0); else if (A=234) Y1=Y1-255; else ++Y1; for (N2=0,Y2=(Y1&255);N2=234) Y1=Y1-255; else ++Y1; X1=S1[1]; if (S1[3]&0x80) X1-=32; if (mode && X1>-ScreenSize && X1<256 && Y1>-ScreenSize && Y1<192) { mask1=(SpriteSize==16)? 0xFFFF:0xFF; if (X1<0) mask1>>=((-X1)/((BigSprites)?2:1)); else if (X1+ScreenSize>=256) mask1<<=((257-X1)/((BigSprites)?2:1)); for (N2=N1+1,S2=S1+4;N2<32 && S2[0]!=208 && (VDP.Status&0x20)==0;N2++,S2+=4) { Y2=S2[0]; if (Y2>=234) Y2=Y2-255; else ++Y2; X2=S2[1]; if (S2[3]&0x80) X2-=32; if (X2>-ScreenSize && X2<256 && Y2>-ScreenSize && Y2<192) { mask2=(SpriteSize==16)? 0xFFFF:0xFF; if (X2<0) mask2>>=((-X2)/((BigSprites)?2:1)); else if (X2+ScreenSize>=256) mask2<<=((257-X2)/((BigSprites)?2:1)); V=abs(Y2-Y1); H=abs(X2-X1); if (BigSprites) { V=V/2+(V&1); H=H/2+(H&1); } if (V=0) { P1+=V; } else { P2+=V; Y2=Y1; } if (X2-X1>=0) { T=P1;P1=P2;P2=T; maskT=mask1;mask1=mask2;mask2=maskT; } for (V=8-V;V;--V,P1++,P2++,Y2++) { if (LastSprite[Y2]>=N1 && LastSprite[Y2]>=N2) { X2=P2[0]&mask2; if (X2&((P1[0]&mask1)>>H)) { VDP.Status|=0x20; break; } } } } else { P1=SprGen+(S1[2]&0xFC)*8; P2=SprGen+(S2[2]&0xFC)*8; if (Y2-Y1>=0) { P1+=V; } else { P2+=V; Y2=Y1; } if (X2-X1>=0) { T=P1;P1=P2;P2=T; maskT=mask1;mask1=mask2;mask2=maskT; } for (V=16-V;V;--V,P1++,P2++,Y2++) { if (LastSprite[Y2]>=N1 && LastSprite[Y2]>=N2) { X2=(P2[0]*256+P2[16])&mask2; if (X2&(((P1[0]*256+P1[16])&mask1)>>H)) { VDP.Status|=0x20; break; } } } } } } } } } if (!Support5thSprite) for (Y1=0;Y1<255;++Y1) { SpriteCount[Y1]=0; LastSprite[Y1]=32; } } /****************************************************************************/ /*** Refresh screen, check keyboard and sprites ***/ /****************************************************************************/ static int VDP_Interrupt(void) { static int UCount=1; int dorefresh,i; /* Increase interrupt count */ ++numVDPInts; /* Update sound log file */ if (SoundStream) UpdateSoundStream(); /* Check for keyboard events */ Keyboard (); /* Check keyboard buffer and delete events that are too old */ for (i=0;i<16;++i) if (keyboard_buffer[i]) if (++keyboard_buffer_int_count[i]>MAX_KEYBOARD_INT_COUNT) { keyboard_buffer[i]=0; keyboard_buffer_int_count[i]=0; } /* Check spinner events */ if (SpinnerPosition[0]<-1000) SpinnerPosition[0]=-1000; if (SpinnerPosition[0]>1000) SpinnerPosition[0]=1000; if (SpinnerPosition[1]<-1000) SpinnerPosition[1]=-1000; if (SpinnerPosition[1]>1000) SpinnerPosition[1]=1000; if (SpinnerPosition[0]<0) SpinnerJoyState[0]=0x2000; else if (SpinnerPosition[0]>0) SpinnerJoyState[0]=0x0000; else SpinnerJoyState[0]=0x3000; if (SpinnerPosition[1]>0) SpinnerJoyState[1]=0x2000; else if (SpinnerPosition[1]<0) SpinnerJoyState[1]=0x0000; else SpinnerJoyState[1]=0x3000; SpinnerParam[0]=(0x10000*abs(SpinnerPosition[0]))/1000; SpinnerParam[1]=(0x10000*abs(SpinnerPosition[1]))/1000; if (SpinnerParam[0]) SpinnerRecur[0]=0x10000; if (SpinnerParam[1]) SpinnerRecur[1]=0x10000; /* Check if a screen refresh should be done */ switch (CheckScreenRefresh()) { case 0: dorefresh=0; break; case 1: dorefresh=1; break; default: if (!--UCount) { if (!UPeriod) UPeriod=2; UCount=UPeriod; dorefresh=1; } else dorefresh=0; break; } if (VDP.VKey) { /* Update VDP status reg */ VDP.Status=0x9F; } switch((VDP.Reg[0]&0x02)|(VDP.Reg[1]&0x18)) { case 0x00: case 0x02: case 0x0A: case 0x08: CheckSprites(VDP.VKey); } if (dorefresh) { RefreshScreen(VDP.ScreenChanged); VDP.ScreenChanged=0; } /* Don't process interrupt if VDP is being accessed */ if (!VDP.VKey) return Z80_IGNORE_INT; /* Return interrupt id */ if (VDP.Reg[1]&0x20) { if (NMI_Line) return Z80_IGNORE_INT; else { NMI_Line=1; return Z80_NMI_INT; } } else return Z80_IGNORE_INT; } static int SpinnerInterrupt (void) { int doint=0; SpinnerRecur[0]+=SpinnerParam[0]; SpinnerRecur[1]+=SpinnerParam[1]; JoyState[0]|=0x3000; JoyState[1]|=0x3000; if (SpinnerRecur[0]>0xFFFF) { SpinnerRecur[0]&=0xFFFF; JoyState[0]=(JoyState[0]&0xCFFF)|SpinnerJoyState[0]; doint=1; } if (SpinnerRecur[1]>0xFFFF) { SpinnerRecur[1]&=0xFFFF; JoyState[1]=(JoyState[1]&0xCFFF)|SpinnerJoyState[1]; doint=1; } return (doint)? 0x00FF:Z80_IGNORE_INT; } /****************************************************************************/ /*** This function is called every interrupt. It checks if a VDP or a ***/ /*** spinner interrupt should occur ***/ /****************************************************************************/ int Z80_Interrupt (void) { int i; for (i=0;i<4;++i) { if (disk_timeout[i]) { if (!--disk_timeout[i]) disk_newstatus[i]=0x80; } if (tape_timeout[i]) { if (!--tape_timeout[i]) tape_newstatus[i]=0x80; } } IFreqRecur+=IFreqParam; if (IFreqRecur>0xFFFFF) { IFreqRecur&=0xFFFFF; return VDP_Interrupt (); } return SpinnerInterrupt(); } void Z80_Retn (void) { }; void Z80_Reti (void) { }; /****************************************************************************/ /*** Change disk and tape images, printer log file, etc. ***/ /****************************************************************************/ #ifndef NO_OPTIONS_DIALOGUE void OptionsDialogue (void) { static char _TapeName[4][256],_DiskName[4][256], _PrnName[256],_LPTName[256],_SoundName[256]; char buf[256]; char *p; int tmp; fflush (stdin); do { fprintf (stderr, "Options currently in use are:\n" "Disk A image - %s\n" "Disk B image - %s\n" "Disk C image - %s\n" "Disk D image - %s\n" "Tape A image - %s\n" "Tape B image - %s\n" "Tape C image - %s\n" "Tape D image - %s\n" "Printer log file - %s\n" "Parallel port log file - %s\n" "Sound log file - %s\n" "Interrupt frequency - %dHz\n" "Z80 CPU speed - %u%%\n" "Available commands are:\n" "da,db,dc,dd - Change disk image\n" "ta,tb,tc,td - Change tape image\n" "p - Change printer log file\n" "l - Change parallel port log file\n" "s - Change sound log file\n" "i - Change interrupt frequency\n" "c - Change Z80 CPU speed\n" "b - Back\n" "q - Quit emulator\n> ", (DiskStream[0])? DiskName[0]:"none", (DiskStream[1])? DiskName[1]:"none", (DiskStream[2])? DiskName[2]:"none", (DiskStream[3])? DiskName[3]:"none", (TapeStream[0])? TapeName[0]:"none", (TapeStream[1])? TapeName[1]:"none", (TapeStream[2])? TapeName[2]:"none", (TapeStream[3])? TapeName[3]:"none", (PrnStream)? PrnName:"none", (LPTStream)? LPTName:"none", (SoundStream)? SoundName:"none", IFreq, Z80_IPeriod*100*1000/3579545); buf[2]=buf[3]='\0'; fgets (buf,sizeof(buf),stdin); p=strchr (buf,'\n'); if (p) *p='\0'; fflush (stdin); switch (buf[0]) { case 'd': tmp=buf[1]-'a'; if (tmp>=0 && tmp<4) { DiskClose (tmp); strcpy (_DiskName[tmp],buf+3); DiskName[tmp]=_DiskName[tmp]; DiskOpen (tmp); } break; case 't': tmp=buf[1]-'a'; if (tmp>=0 && tmp<4) { TapeClose (tmp); strcpy (_TapeName[tmp],buf+3); TapeName[tmp]=_TapeName[tmp]; TapeOpen (tmp); } break; case 'p': ClosePrinter (); strcpy (_PrnName,buf+2); PrnName=_PrnName; OpenPrinter (); break; case 'l': CloseLPT (); strcpy (_LPTName,buf+2); LPTName=_LPTName; OpenLPT (); break; case 's': CloseSoundLog (); strcpy (_SoundName,buf+2); SoundName=_SoundName; OpenSoundLog (); break; case 'i': IFreq=atoi(buf+2); if (IFreq<10) IFreq=10; if (IFreq>200) IFreq=200; IFreqParam=(0x100000*IFreq)/1000; if (SoundStream) { fputc (0xFC,SoundStream); fputc (IFreq,SoundStream); } break; case 'c': tmp=atoi(buf+2); if (tmp<10) tmp=10; if (tmp>1000) tmp=1000; Z80_IPeriod=(3579545*tmp)/(100*1000); break; case 'q': Z80_Running=0; break; } } while (buf[0]!='q' && buf[0]!='b'); VDP.ScreenChanged=1; } #endif /* NO_OPTIONS_DIALOGUE */