/** fMSX: portable MSX emulator ******************************/ /** **/ /** MSX.c **/ /** **/ /** This file contains implementation for the MSX-specific **/ /** hardware: slots, memory mapper, PPIs, VDP, PSG, clock, **/ /** etc. Initialization code and definitions needed for the **/ /** machine-dependent drivers are also here. **/ /** **/ /** Copyright (C) Marat Fayzullin 1994,1995,1996 **/ /** You are not allowed to distribute this software **/ /** commercially. Please, notify me, if you make any **/ /** changes to this file. **/ /*************************************************************/ #include #include #include #include #include "MSX.h" #include "Boot.h" // dirty way to get ROM files in #include "msxrom.h" #include "gamerom.h" #define PRINTOK if(Verbose) printf(MsgOK) #define PRINTFAILED if(Verbose) printf(MsgFAILED) char *MsgOK = "OK\n"; char *MsgFAILED = "FAILED\n"; byte Verbose = 1; /* Debug msgs ON/OFF */ byte SaveCMOS = 0; /* Save CMOS.ROM on exit */ byte MSXVersion = 0; /* 0=MSX1,1=MSX2,2=MSX2+ */ byte ROMTypeA = 0; /* MegaROM types */ byte ROMTypeB = 0; byte UPeriod = 1; /* Interrupts/scr. update */ int RAMPages = 4; /* Number of RAM pages */ int VRAMPages = 4; /* Number of VRAM pages */ char *FontName = "DEFAULT.FNT"; /* Font file for text */ byte *FontBuf = NULL; /* Font for text modes */ byte UseFont = 0; /* Use ext. font when 1 */ byte *VRAM=NULL,*VPAGE; /* Video RAM */ byte *RAM[8]; /* Main RAM [8x8kB pages] */ byte *EmptyRAM; /* Empty RAM page [8kB] */ byte *MemMap[4][4][8]; /* Memory maps [PPage][SPage][Addr] */ byte *RAMMap[256]; /* Blocks for RAM Mapper */ byte RAMMapper[4]; /* RAM Mapper state */ byte RAMMask; /* RAM Mapper mask */ byte *ROMMap[2][256]; /* Blocks for ROM Mappers */ byte ROMMapper[2][4]; /* ROM Mappers state */ byte ROMMask[2]; /* ROM Mapper masks */ byte EnWrite[4]; /* 1 if write enabled */ byte PSL[4],SSL[4]; /* Lists of current slots */ byte PSLReg,SSLReg; /* Storage for A8h port and (FFFFh) */ byte JoyState[2]; /* Joystick states */ byte KeyMap[16]; /* Keyboard map */ byte IOReg; /* Storage for AAh port */ byte PPIReg; /* PPI control register */ byte COMReg; /* 8251 ACIA control reg. */ byte OPLLReg=0; /* OPLL register number */ byte PSGReg=0; /* PSG register number */ byte OPLL[256]; /* OPLL registers */ byte PSG[16]; /* PSG registers */ byte SCC[256]; /* SCC registers */ byte RTCReg=0,RTCMode=0; /* RTC register numbers */ byte RTC[4][13]; /* RTC registers */ byte *ChrGen,*ChrTab,*ColTab; /* VDP tables [screens] */ byte *SprGen,*SprTab,*SprCol; /* VDP tables [sprites] */ byte Palette[32][3]; /* Palette registers */ pair WVAddr,RVAddr; /* Storage for VRAM addresses in VDP */ byte VKey=1,PKey=1; /* Status keys for VDP */ byte FGColor=0,BGColor=0; /* Colors */ byte XFGColor,XBGColor; /* Second set of colors */ byte ScrMode=0; /* Current screen mode */ byte VDP[64],VDPStatus[16]; /* VDP registers */ byte ScanLine=0; /* Current scanline */ byte EndOfFrame=1; /* 1 when end of frame */ /******** Places in BIOS to be patched with ED FE C9: ******/ word BIOSPatches[] = { 0x00E1,0x00E4,0x00E7,0x00EA,0x00ED,0x00F0,0x00F3,0 }; /******** Masks for VDP table address registers: **************/ struct { byte R2,R3,R4,R5; } MSK[MAXSCREEN+2] = { { 0x7F,0x00,0x3F,0x00 }, /* SCR 0:TEXT 40x24 */ { 0x7F,0xFF,0x3F,0xFF }, /* SCR 1:TEXT 32x24 */ { 0x7F,0x80,0x3C,0xFF }, /* SCR 2:BLK 256x192 */ { 0x7F,0x00,0x3F,0xFF }, /* SCR 3:64x48x16 */ { 0x7F,0x80,0x3C,0xFC }, /* SCR 4:BLK 256x192 */ { 0x60,0x00,0x00,0xFC }, /* SCR 5:256x192x16 */ { 0x60,0x00,0x00,0xFC }, /* SCR 6:512x192x4 */ { 0x20,0x00,0x00,0xFC }, /* SCR 7:512x192x16 */ { 0x20,0x00,0x00,0xFC }, /* SCR 8:256x192x256 */ { 0x7C,0xF8,0x3F,0x00 } /* SCR 0:TEXT 80x24 */ }; /******** Screen Mode Handlers [number of screens + 1]: *******/ extern struct { /* Screen mode handlers: */ int (*Init)(); /* Initialize screen */ void (*Colors)(); /* Update colors/palette */ void (*Refresh)(byte Y1,byte Y2); /* Refresh screen */ } SCR[MAXSCREEN+2]; /* Number of screens + 1 */ /********** Data for the CPU->VRAM function of V9938: *********/ struct { byte *P,Mode,TR,Mask,Op,Diff,Shift[4]; int X,NX,NY,XC,NXC,XStep,YStep; } MMC; /* Load a .ROM file into given slot */ byte *LoadROM(char *STR,char *F,int P,int S,int A); int LoadCart(char *Name,int Slot);/* Load cartridge from a .ROM file */ void MapROM(word Addr,byte Value);/* Switch MegaROM banks */ void SSlot(byte Value); /* Switch secondary slots */ void VDPOut(byte Reg,byte Value); /* Write value into a VDP register */ void CheckSprites(void); /* Check collisions and 5th sprite */ byte RTCIn(byte R); /* Read RTC registers */ void DoVDP(byte V); /* Perform V9938 operation */ /* Function used by DoVDP() for operations on pixels */ byte Oper(byte A,byte B,byte N); /* Functions used by DoVDP() to copy byte arrays */ void MCopyF(byte *D,byte *S,int N); void MCopyB(byte *D,byte *S,int N); /****************************************************************/ /*** This function is called when a read from RAM occurs. ***/ /****************************************************************/ byte M_RDMEM(register word A) { if(A!=0xFFFF) return(RAM[A>>13][A&0x1FFF]); else return((PSL[3]==3)? ~SSLReg:RAM[7][0x1FFF]); } /****************************************************************/ /*** This function is called when a write to RAM occurs. It ***/ /*** checks for write protection and slot selectors. ***/ /****************************************************************/ void M_WRMEM(register word A,register byte V) { if(A!=0xFFFF) { if(EnWrite[A>>14]) RAM[A>>13][A&0x1FFF]=V; else if((A>0x3FFF)&&(A<0xC000)) MapROM(A,V); } else { if(PSL[3]==3) SSlot(V); else if(EnWrite[3]) RAM[7][A&0x1FFF]=V; } } /****************************************************************/ /*** Write number into given IO port. ***/ /****************************************************************/ void DoOut(register byte Port,register byte Value) { static byte Pal,Addr; /* Palette and address buffers */ register byte I,J,K,*P; switch(Port) { case 0x7C: OPLLReg=Value;return; case 0x7D: OPLL[OPLLReg]=Value; OPLLOut(OPLLReg,Value); return; case 0x98: if(VKey) { if(VPAGE) VPAGE[WVAddr.W]=Value; WVAddr.W=(WVAddr.W+1)&0x3FFF; if(!WVAddr.W&&(ScrMode>3)) { VDP[14]=(VDP[14]+1)&0x07; VPAGE=(VDP[14]>1)); else IOReg&=~(1<<(7&(Value>>1))); PPIOut(IOReg&0x80); return; case 0xB4: RTCReg=Value&0x0F;return; case 0xB5: if(RTCReg<13) { J=RTCMode&0x03;RTC[J][RTCReg]=Value; } else if(RTCReg==13) RTCMode=Value; return; case 0xA8: if(Value!=PSLReg) for(J=0,PSLReg=Value;J<4;J++,Value>>=2) { PSL[J]=Value&3;I=J<<1; K=(PSL[J]==3)? SSL[J]:0; EnWrite[J]=(K==2)&&(MemMap[3][2][I]!=EmptyRAM); RAM[I]=MemMap[PSL[J]][K][I]; RAM[I+1]=MemMap[PSL[J]][K][I+1]; } return; case 0xFC: case 0xFD: case 0xFE: case 0xFF: J=Port-0xFC;Value&=RAMMask; if(RAMMapper[J]!=Value) { if(Verbose&0x08){ printf("RAM-MAPPER: block %d at %Xh\n",Value,J*0x4000); } I=J<<1; RAMMapper[J]=Value; MemMap[3][2][I]=RAMMap[Value]; MemMap[3][2][I+1]=RAMMap[Value]+0x2000; if((PSL[J]==3)&&(SSL[J]==2)) { EnWrite[J]=(RAMMap[Value]!=EmptyRAM); RAM[I]=MemMap[3][2][I]; RAM[I+1]=MemMap[3][2][I+1]; } } return; } } /****************************************************************/ /*** Read number from given IO port. ***/ /****************************************************************/ byte DoIn(register byte Port) { register byte *P; switch(Port) { case 0x80: Port=(0x20<<((COMReg&0x0C)>>2))-1; /* 8251 ACIA data */ return(NORAM&Port); case 0x81: return(0x85); /* 8251 ACIA status */ case 0x90: return(0xFD); /* Printer READY signal */ case 0x98: Port=VPAGE? VPAGE[RVAddr.W]:NORAM; /* VRAM read port */ RVAddr.W=(RVAddr.W+1)&0x3FFF; if(!RVAddr.W&&(ScrMode>3)) { VDP[14]=(VDP[14]+1)&0x07; VPAGE=(VDP[14]>MMC.Shift[MMC.XC%MMC.Diff])&MMC.Mask; VDPStatus[7]=VDP[44]=Port; MMC.NXC--; if(MMC.NXC>0) MMC.XC+=MMC.XStep; else if(--MMC.NY) { MMC.XC=MMC.X;MMC.NXC=MMC.NX;MMC.P+=MMC.YStep; } else VDPStatus[2]&=0x7E; } break; } VKey=1;return(Port); case 0xA2: if(PSGReg!=14) return(PSGReg>13? NORAM:PSG[PSGReg]); else { if(PSG[15]&0x40) if(PSG[15]&0x20) return(0x7F); else return(JoyState[1]&(((PSG[15]&0x0C)<<2)|0x4F)); else if(PSG[15]&0x10) return(0x7F); else return(JoyState[0]&(((PSG[15]&0x03)<<4)|0x4F)); } case 0xA8: return(PSLReg); /* Primary slot state */ case 0xA9: return(KeyMap[IOReg&0x0F]); /* Keyboard port */ case 0xAA: return(IOReg); /* General IO register */ case 0xAB: return(PPIReg); /* PPI control register */ case 0xB5: return(RTCIn(RTCReg)); /* RTC registers */ case 0xFC: /* Mapper page at 0000h */ case 0xFD: /* Mapper page at 4000h */ case 0xFE: /* Mapper page at 8000h */ case 0xFF: return(RAMMapper[Port-0xFC]|~RAMMask);/* Mapper page at C000h */ default: return(NORAM); /* If no port, ret FFh */ } } /****************************************************************/ /*** Switch ROM Mapper pages. This function is supposed to be ***/ /*** to be called when ROM page registers are written to. ***/ /****************************************************************/ void MapROM(register word Addr,register byte Value) { byte I,J,K; /* J contains page number 0-3 */ J=Addr>>14; /* I contains slot number 0/1 */ if(PSL[J]==1) I=0; else if(PSL[J]==2) I=1; else return; /* K contains MegaROM type 0-5 */ K=I? ROMTypeB:ROMTypeA; /* SCC: types 0, 2, or no cart */ if((Addr&0xFF00)==0x9800) if(!ROMMap[I][0]||(K==0)||(K==2)) { J=Addr&0x00FF; SCC[J]=Value;SCCOut(J,Value); return; } /* If no cartridge, exit */ if(!ROMMap[I][0]) return; switch(K) { case 0: /*** KONAMI 8kB cartridges [general] ***/ if((Addr&0xFF00)==0x9800) { J=Addr&0x00FF; SCC[J]=Value;SCCOut(J,Value); return; } J=(Addr-0x4000)>>13;K=0; break; case 1: /*** MSXDOS2 16kB cartridge ***/ if((Addr<0x6000)||(Addr>0x7FFF)) return; J=0;K=1; break; case 2: /*** KONAMI5 8kB cartridges ***/ if((Addr&0xFF00)==0x9800) { J=Addr&0x00FF; SCC[J]=Value;SCCOut(J,Value); return; } Addr-=0x5000;if(Addr&0x9FFF) return; J=Addr>>13;K=0; break; case 3: /*** KONAMI4 8kB cartridges ***/ Addr-=0x4000;if(Addr&0x9FFF) return; J=Addr>>13;K=0; break; case 4: /*** ASCII 8kB cartridges ***/ if((Addr>=0x8000)||(Addr<0x6000)) return; J=(Addr&0x1800)>>11;K=0; break; case 5: /*** ASCII 16kB cartridges ***/ if((Addr>=0x8000)||(Addr<0x6000)) return; J=(Addr&0x1000)>>11;K=1; break; default: printf("Unknown MegaROM type %d\n",I? ROMTypeB:ROMTypeA); return; } Value&=ROMMask[I]; if(K) Value<<=1; if(Value!=ROMMapper[I][J]) { ROMMapper[I][J]=Value; RAM[J+2]=MemMap[I+1][0][J+2]=ROMMap[I][Value]; if(K) { if(Verbose&0x08) printf("ROM-MAPPER: 16kB block %d at %04Xh\n",Value,J*0x2000+0x4000); ROMMapper[I][J+1]=Value+1; RAM[J+3]=MemMap[I+1][0][J+3]=ROMMap[I][Value+1]; } else if(Verbose&0x08) printf("ROM-MAPPER: 8kB block %d at %04Xh\n",Value,J*0x2000+0x4000); } } /****************************************************************/ /*** Switch secondary memory slots. This function is supposed ***/ /*** to be called when value in (FFFFh) changes. ***/ /****************************************************************/ void SSlot(register byte Value) { register byte I,J,K,*P; if(SSLReg!=Value) { SSLReg=Value; for(J=0;J<4;J++,Value>>=2) { SSL[J]=Value&3; if(PSL[J]==3) { I=J<<1; EnWrite[J]=(SSL[J]==2)&&(MemMap[3][2][I]!=EmptyRAM); RAM[I]=MemMap[3][SSL[J]][I]; RAM[I+1]=MemMap[3][SSL[J]][I+1]; } } } } /****************************************************************/ /*** Allocate memory, load ROM images, initialize mapper, VDP ***/ /*** CPU and start the emulation. This function returns 0 in ***/ /*** the case of failure. ***/ /****************************************************************/ int StartMSX(void) { word A; reg R; int *T,I,J,K; byte *P; /*** MSX versions: ***/ static char *Versions[] = { "MSX","MSX2","MSX2+" }; /*** CMOS ROM default values: ***/ static byte RTCInit[4][13] = { { 0,0,0,0,0,0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0,0,0,0,0,0 }, { 0,0,0,0,40,80,15,4,4,0,0,0,0 }, { 0,0,0,0,0,0,0,0,0,0,0,0,0 } }; /*** PSG initial register states: ***/ static byte PSGInit[16] = { 0,0,0,0,0,0,0,0xFD,0,0,0,0,0,0,0xFF,0 }; /*** VDP status register states: ***/ static byte VDPSInit[16] = { 0x9F,0,0x4C,0,0,0,0,0,0,0,0,0,0,0,0,0 }; /*** VDP control register states: ***/ static byte VDPInit[64] = { 0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; /*** Initial palette: ***/ static byte PalInit[16][3] = { {0x00,0x00,0x00},{0x00,0x00,0x00},{0x20,0xC0,0x20},{0x60,0xE0,0x60}, {0x20,0x20,0xE0},{0x40,0x60,0xE0},{0xA0,0x20,0x20},{0x40,0xC0,0xE0}, {0xE0,0x20,0x20},{0xE0,0x60,0x60},{0xC0,0xC0,0x20},{0xC0,0xC0,0x80}, {0x20,0x80,0x20},{0xC0,0x40,0xA0},{0xA0,0xA0,0xA0},{0xE0,0xE0,0xE0}, }; /*** STARTUP CODE starts here: ***/ if((RAMPages<(MSXVersion? 8:4))||(RAMPages>256)) RAMPages=MSXVersion? 8:4; if((VRAMPages<(MSXVersion? 4:2))||(VRAMPages>8)) VRAMPages=MSXVersion? 8:2; if(Verbose) printf("Allocating 8kB for empty space..."); EmptyRAM=malloc(0x2000); ROMMask[0]=ROMMask[1]=0; for(I=0;I<256;I++) RAMMap[I]=ROMMap[0][I]=ROMMap[1][I]=EmptyRAM; for(I=0;I<4;I++) for(J=0;J<4;J++) for(K=0;K<8;K++) MemMap[I][J][K]=EmptyRAM; if(EmptyRAM) memset(EmptyRAM,NORAM,0x2000); else { PRINTFAILED;return(0); } if(Verbose) printf("OK\nAllocating %dkB for VRAM...",VRAMPages*16); if(!(VRAM=malloc(VRAMPages*0x4000))) return(0); if(Verbose) printf("OK\nAllocating %dx16kB RAM pages...",RAMPages); for(J=0;J4) { C3=Slot? ROMTypeB:ROMTypeA; if(Verbose) printf("%s MegaROM\n",ROMNames[C3]); for(C3=0;C3<4;C3++) { MemMap[Slot+1][0][C3+2]=ROMMap[Slot][C3];ROMMapper[Slot][C3]=C3; } for(C3=1;C3>5; if((C2+C1>8)||((C1!=2)&&(C1!=4))) { PRINTFAILED;return(0); } if((C2>2)&&(C1==4)) C2=2; if(Verbose) printf("at %04Xh\n",C2*0x2000); for(C3=0;C3>1)|(VDP[1]&0x18)) { case 0x10: J=0;break; case 0x00: J=1;break; case 0x01: J=2;break; case 0x08: J=3;break; case 0x02: J=4;break; case 0x03: J=5;break; case 0x04: J=6;break; case 0x05: J=7;break; case 0x07: J=8;break; case 0x12: J=MAXSCREEN+1;break; default: J=ScrMode; } if(J!=ScrMode) { I=((J>6)&&(J!=MAXSCREEN+1))? 11:10; ChrTab=VRAM+((long)(VDP[2]&MSK[J].R2)<>1)|(V&0x18)) { case 0x10: J=0;break; case 0x00: J=1;break; case 0x01: J=2;break; case 0x08: J=3;break; case 0x02: J=4;break; case 0x03: J=5;break; case 0x04: J=6;break; case 0x05: J=7;break; case 0x07: J=8;break; case 0x12: J=MAXSCREEN+1;break; default: J=ScrMode; } if(J!=ScrMode) { I=((J>6)&&(J!=MAXSCREEN+1))? 11:10; ChrTab=VRAM+((long)(VDP[2]&MSK[J].R2)<6)&&(ScrMode!=MAXSCREEN+1))? 11:10; ChrTab=VRAM+((long)(V&MSK[ScrMode].R2)<>4;BGColor=V&0x0F; SCR[ScrMode].Colors(); break; case 8: case 9: VDP[R]=V;SCR[ScrMode].Colors();break; case 10: V&=0x07; ColTab= VRAM+((long)(VDP[3]&MSK[ScrMode].R3)<<6)+((long)V<<14); break; case 11: V&=0x03; SprTab= VRAM+((long)(VDP[5]&MSK[ScrMode].R5)<<7)+((long)V<<15); break; case 14: V&=0x07;VPAGE=(V0) MMC.XC+=MMC.XStep; else if(--MMC.NY) { MMC.XC=MMC.X;MMC.NXC=MMC.NX;MMC.P+=MMC.YStep; } else VDPStatus[2]&=0x7E; } break; case 46: if((ScrMode>4)&&(ScrMode<9)&&!(VDPStatus[2]&0x01)) DoVDP(V); V=0x00;return; } VDP[R]=V;return; } /****************************************************************/ /*** This function is used by DoVDP() for operations on ***/ /*** pixels. ***/ /****************************************************************/ byte Oper(register byte A,register byte B,register byte N) { switch(N) { case 0: return(A); case 1: return(A&B); case 2: return(A|B); case 3: return(A^B); case 4: return(~A); } return(B); } /****************************************************************/ /*** These two functions called by DoVDP() copy an array of ***/ /*** bytes in different directions. ***/ /****************************************************************/ void MCopyF(register byte *D,register byte *S,register int N) { while(N--) *D++=*S++; } void MCopyB(register byte *D,register byte *S,register int N) { while(N--) *D--=*S--; } /****************************************************************/ /*** Perform given V9938 operation. ***/ /****************************************************************/ void DoVDP(register byte V) { int SX,SY,DX,DY,NX,NY; register int TX,TY,L; register byte *P,SM,I,J,TR; static byte Shift[4][4] = { {4,0,0,0},{6,4,2,0},{4,0,0,0},{0,0,0,0} }; static byte Diff[4] = { 2,4,2,1 }; static byte Mask[4] = { 0x0F,0x03,0x0F,0xFF }; static int Width[4] = { 256,512,512,256 }; static int Height[4] = { 1024,1024,512,512 }; static byte Line[4] = { 7,7,8,8 }; static int Bytes[4] = { 128,128,256,256 }; SX=VDP[32]+((word)VDP[33]<<8);SY=VDP[34]+((word)VDP[35]<<8); DX=VDP[36]+((word)VDP[37]<<8);DY=VDP[38]+((word)VDP[39]<<8); NX=VDP[40]+((word)VDP[41]<<8);NY=VDP[42]+((word)VDP[43]<<8); SM=ScrMode-5;TR=!(V&0x08); if(Verbose&0x02) printf ( "VDP COMMAND: %X (%d,%d)->(%d,%d) [%d,%d] Color %d\n", V,SX,SY,DX,DY,VDP[45]&0x04? -NX:NX,VDP[45]&0x08? -NY:NY,VDP[44] ); switch(V&0xF0) { /*** Abort current operation: ***/ case 0x00: VDPStatus[2]&=0x7E; break; /*** Get color of a point at SX,SY: ***/ case 0x40: P=VRAM+((long)SY<>Shift[SM][SX%Diff[SM]])&Mask[SM]; break; /*** Set color of a point at DX,DY: ***/ case 0x50: J=VDP[44]&Mask[SM]; if(J||TR) { P=VRAM+((long)DY<=0;SX--) { T=P+SX/Diff[SM]; J=(*T>>Shift[SM][SX%Diff[SM]])&Mask[SM]; if((J==K)^I) break; } else for(;SX>Shift[SM][SX%Diff[SM]])&Mask[SM]; if((J==K)^I) break; } SX=SX>=Width[SM]? Width[SM]-1:SX<0? 0:SX; VDPStatus[8]=SX&0xFF; VDPStatus[9]=(SX>>8)|0xFE; if((J==K)^I) VDPStatus[2]|=0x10; else VDPStatus[2]&=0xEF; } break; /*** Draw a line of length NX,NY from DX,DY: ***/ case 0x70: J=VDP[44]&Mask[SM]; if(J||TR) { V&=0x07; if(!NX&&!NY) { P=VRAM+((long)DY<=0) { L-=AY;DX+=TX; } } else for(L=AY-NX;DX!=SX;DX+=TX,L+=AY) { P=VRAM+((long)DY<=0) { L-=AX;DY+=TY; } } } } break; /*** Fill a box with a given color: ***/ case 0xC0: { register int X,M; register byte *DP; J=VDP[44]&Mask[SM]; J|=(J<Height[SM]) NY=Height[SM]-DY; if(SY+NY>Height[SM]) NY=Height[SM]-SY; } for(;NY;NY--,SP+=TY,DP+=TY) for(L=NX,X=DX;L;) { J=X%Diff[SM];M=X/Diff[SM];P=SP+M; if(J||(L>I)&Mask[SM]; P=DP+M; *P=(*P&~(Mask[SM]<0) MCopyF(DP+M,P,J); else MCopyB(DP+M,P,J); J=L%Diff[SM];X+=TX*(L-J);L=J; } } } break; case 0xD0: { register int XS,XD,M; register byte *SP,*DP; TX=VDP[45]&0x04? -1:1; SP=VRAM+((long)SY<0)? 0:NX-1; } if(VDP[45]&0x08) { TY=-Bytes[SM]; if(DY-NY<-1) NY=DY+1; if(SY-NY<-1) NY=SY+1; } else { TY=Bytes[SM]; if(DY+NY>Height[SM]) NY=Height[SM]-DY; if(SY+NY>Height[SM]) NY=Height[SM]-SY; } for(;NY;NY--,SP+=TY,DP+=TY) for(L=NX,XS=SX,XD=DX;L;) { J=XS%Diff[SM];I=XD%Diff[SM];P=SP+XS/Diff[SM]; if(J||I||(L>Shift[SM][J])&Mask[SM]; P=DP+XD/Diff[SM];I=Shift[SM][I]; *P=(*P&~(Mask[SM]<0) MCopyF(DP+XD/Diff[SM],P,M); else MCopyB(DP+XD/Diff[SM],P,M); M=L%Diff[SM];L=TX*(L-M);XS+=L;XD+=L;L=M; } } } break; case 0x90: { register int XS,XD; register byte *SP,*DP; V&=0x07; TX=VDP[45]&0x04? -1:1; SP=VRAM+((long)SY<0)? 0:NX-1; } if(VDP[45]&0x08) { TY=-Bytes[SM]; if(DY-NY<-1) NY=DY+1; if(SY-NY<-1) NY=SY+1; } else { TY=Bytes[SM]; if(DY+NY>Height[SM]) NY=Height[SM]-DY; if(SY+NY>Height[SM]) NY=Height[SM]-SY; } for(;NY;NY--,SP+=TY,DP+=TY) for(L=NX,XS=SX,XD=DX;L;L--,XS+=TX,XD+=TX) { P=SP+XS/Diff[SM]; J=(*P>>Shift[SM][XS%Diff[SM]])&Mask[SM]; if(J||TR) { P=DP+XD/Diff[SM];I=Shift[SM][XD%Diff[SM]]; *P=(*P&~(Mask[SM]<Height[SM]) NY=Height[SM]-DY; } MMC.XStep=(MMC.Mode!=0xF0? 1:Diff[SM])*(VDP[45]&0x04? -1:1); if(!NX) { NX=Width[SM];DX=(MMC.XStep>0)? 0:NX-1; } MMC.X=MMC.XC=DX;MMC.NX=MMC.NXC=NX;MMC.NY=NY; MMC.Diff=Diff[SM];MMC.Mask=Mask[SM]; memcpy(MMC.Shift,Shift[SM],4); if(MMC.Mode!=0xA0) VDPOut(44,VDP[44]); break; default: printf("Unimplemented V9938 operation %X\n",V); } } /****************************************************************/ /*** Read number from given RTC register. ***/ /****************************************************************/ byte RTCIn(register byte R) { register byte J; char S="Fri Mar 28 22:00:00 1997n0"; /*** ctime() returns "DDD MMM dd hh:mm:ss YYYYn0" static byte RTable[13] = { 18,17,15,14,12,11,25,9,8,25,25,23,22 }; static char *Days[7] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; static char *Months[12] = { "Jan","Feb","Mar","Apr","May","Jun", "Jul","Aug","Sep","Oct","Nov","Dec" }; J=RTCMode&0x03; if(R>12) J=(R==13)? RTCMode:NORAM; else if(J) J=RTC[J][R]; else { switch(R) { case 6: for(J=0;(J<7)&&strncmp(S,Days[J],3);J++); J=(J==7)? 0:J+1;break; case 9: for(J=0;(J<12)&&strncmp(S+4,Months[J],3);J++); J=(J==12)? 0:(J+1)%10;break; case 10: for(J=0;(J<12)&&strncmp(S+4,Months[J],3);J++); J=(J==12)? 0:(J+1)/10;break; case 11: S[24]='\0';J=(atoi(S+20)-1980)%10;break; case 12: S[24]='\0';J=((atoi(S+20)-1980)/10)%10;break; default: J=S[RTable[R]]-'0'; } } */ return(J|0xF0); } /****************************************************************/ /*** Refresh screen, check keyboard and sprites. Call this ***/ /*** function on each interrupt. ***/ /****************************************************************/ word Interrupt(void) { static byte BFlag=0; static byte BCount=0; static int UCount=1; register byte I,J,K; // speed check .. // printf("cpulines: %d\n",VSync(1)); // VSync(0); // Checking keyboard: Keyboard(); // If VDP is being accessed, don't process the interrupt if(!VKey) return(0xFFFF); if((!--UCount)||!EndOfFrame) { UCount=UPeriod; // Refreshing screen: J=VDP[19]-VDP[23]+3; I=ScanLines212? 211:191; if((J>I)||!(VDP[0]&0x10)) J=I; else VDPStatus[1]|=0x01; if(ScanLine>J) { K=ScanLine;ScanLine=J;J=K; } EndOfFrame=(J==I); (SCR[ScrMode].Refresh)(ScanLine,J); ScanLine=(J240)) { DH=S[1]-D[1]; if((DH<16)||(DH>240)) { PS=SprGen+((long)(S[2]&0xFC)<<3); PD=SprGen+((long)(D[2]&0xFC)<<3); if(DV<16) PD+=DV; else { DV=256-DV;PS+=DV; } if(DH>240) { DH=256-DH;T=PS;PS=PD;PD=T; } while(DV<16) { LS=((word)*PS<<8)+*(PS+16); LD=((word)*PD<<8)+*(PD+16); if(LD&(LS>>DH)) break; else { DV++;PS++;PD++; } } if(DV<16) { VDPStatus[0]|=0x20;return; } } } } } else { for(J=0,S=SprTab;J248)) { DH=S[1]-D[1]; if((DH<8)||(DH>248)) { PS=SprGen+((long)S[2]<<3); PD=SprGen+((long)D[2]<<3); if(DV<8) PD+=DV; else { DV=256-DV;PS+=DV; } if(DH>248) { DH=256-DH;T=PS;PS=PD;PD=T; } while((DV<8)&&!(*PD&(*PS>>DH))) { DV++;PS++;PD++; } if(DV<8) { VDPStatus[0]|=0x20;return; } } } } } } /****************************************************************/ /*** Emulate BIOS calls. This function is called on the ED FE ***/ /*** instruction to emulate disk/tape access, etc. ***/ /****************************************************************/ void Patch(reg *R) { static byte TapeHeader[8] = { 0x1F,0xA6,0xDE,0xBA,0xCC,0x13,0x7D,0x74 }; static struct { int Sectors;byte Heads,Names,PerTrack,PerFAT,PerCluster; } Info[8] = { { 720,1,112,9,2,2 }, { 1440,2,112,9,3,2 }, { 640,1,112,8,1,2 }, { 1280,2,112,8,2,2 }, { 360,1, 64,9,2,1 }, { 720,2,112,9,2,2 }, { 320,1, 64,8,1,1 }, { 640,2,112,8,1,2 } }; byte Buf[512],Count,PS,SS,N,*P; int J,I,Sector; word Addr; switch(R->PC.W-2) { default: printf("Unknown BIOS trap called at PC=%04Xh\n",R->PC.W-2); } }