/** 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-2008 **/ /** You are not allowed to distribute this software **/ /** commercially. Please, notify me, if you make any **/ /** changes to this file. **/ /*************************************************************/ #include "MSX.h" #include "Sound.h" #include "Floppy.h" #include #include #include #include #include #include #ifdef __BORLANDC__ #include #endif #ifdef __WATCOMC__ #include #endif #if defined(UNIX) || defined(S60) || defined(PSP) #include #endif #ifdef ZLIB #include #endif #ifdef MINIZIP #include "unzip.h" #endif #ifdef ALTSOUND extern int Use2413; /* MSX-MUSIC emulation (1-yes) */ extern int Use8950; /* MSX-AUDIO emulation (1-yes) */ #endif #define PRINTOK if(Verbose) puts("OK") #define PRINTFAILED if(Verbose) puts("FAILED") #define PRINTRESULT(R) if(Verbose) puts((R)? "OK":"FAILED") #define RGB2INT(R,G,B) ((B)|((int)(G)<<8)|((int)(R)<<16)) /* MSDOS chdir() is broken and has to be replaced :( */ #ifdef MSDOS #include "LibMSDOS.h" #define chdir(path) ChangeDir(path) #endif /** User-defined parameters for fMSX *************************/ int Mode = MSX_MSX2|MSX_NTSC|MSX_GUESSA|MSX_GUESSB; byte Verbose = 1; /* Debug msgs ON/OFF */ byte UPeriod = 75; /* % of frames to draw */ int VPeriod = CPU_VPERIOD; /* CPU cycles per VBlank */ int HPeriod = CPU_HPERIOD; /* CPU cycles per HBlank */ int RAMPages = 4; /* Number of RAM pages */ int VRAMPages = 2; /* Number of VRAM pages */ byte ExitNow = 0; /* 1 = Exit the emulator */ /** Main hardware: CPU, RAM, VRAM, mappers *******************/ Z80 CPU; /* Z80 CPU state and regs */ byte *VRAM,*VPAGE; /* Video RAM */ byte *RAM[8]; /* Main RAM (8x8kB pages) */ byte *EmptyRAM; /* Empty RAM page (8kB) */ byte SaveCMOS; /* Save CMOS.ROM on exit */ byte *MemMap[4][4][8]; /* Memory maps [PPage][SPage][Addr] */ byte *RAMData; /* RAM Mapper contents */ byte RAMMapper[4]; /* RAM Mapper state */ byte RAMMask; /* RAM Mapper mask */ byte *ROMData[MAXSLOTS]; /* ROM Mapper contents */ byte ROMMapper[MAXSLOTS][4]; /* ROM Mappers state */ byte ROMMask[MAXSLOTS]; /* ROM Mapper masks */ byte ROMType[MAXSLOTS]; /* ROM Mapper types */ byte EnWrite[4]; /* 1 if write enabled */ byte PSL[4],SSL[4]; /* Lists of current slots */ byte PSLReg,SSLReg[4]; /* Storage for A8h port and (FFFFh) */ /** Memory blocks to free in TrashMSX() **********************/ const byte *Chunks[MAXCHUNKS]; /* Memory blocks to free */ int NChunks; /* Number of memory blcks */ /** Working directory names **********************************/ const char *ProgDir = 0; /* Program directory */ const char *WorkDir; /* Working directory */ /** Cartridge files used by fMSX *****************************/ const char *ROMName[MAXCARTS] = { "CARTA.ROM","CARTB.ROM" }; /** On-cartridge SRAM data ***********************************/ char *SRAMName[MAXSLOTS] = {0,0,0,0,0,0};/* Filenames (gen-d)*/ byte SaveSRAM[MAXSLOTS] = {0,0,0,0,0,0}; /* Save SRAM on exit*/ byte *SRAMData[MAXSLOTS]; /* SRAM (battery backed) */ /** Disk images used by fMSX *********************************/ const char *DSKName[MAXDRIVES] = { "DRIVEA.DSK","DRIVEB.DSK" }; /** Soundtrack logging ***************************************/ const char *SndName = "LOG.MID"; /* Sound log file */ /** Emulation state saving ***********************************/ const char *STAName = "DEFAULT.STA";/* State file (autogen-d)*/ /** Fixed font used by fMSX **********************************/ const char *FNTName = "DEFAULT.FNT"; /* Font file for text */ byte *FontBuf; /* Font for text modes */ /** Printer **************************************************/ const char *PrnName = 0; /* Printer redirect. file */ FILE *PrnStream; /** Cassette tape ********************************************/ const char *CasName = "DEFAULT.CAS"; /* Tape image file */ FILE *CasStream; /** Serial port **********************************************/ const char *ComName = 0; /* Serial redirect. file */ FILE *ComIStream; FILE *ComOStream; /** Kanji font ROM *******************************************/ byte *Kanji; /* Kanji ROM 4096x32 */ int KanLetter; /* Current letter index */ byte KanCount; /* Byte count 0..31 */ /** Keyboard, joystick, and mouse ****************************/ volatile byte KeyState[16]; /* Keyboard map state */ word JoyState; /* Joystick states */ int MouState[2]; /* Mouse states */ byte MouseDX[2],MouseDY[2]; /* Mouse offsets */ byte OldMouseX[2],OldMouseY[2]; /* Old mouse coordinates */ byte MCount[2]; /* Mouse nibble counter */ /** General I/O registers: i8255 *****************************/ I8255 PPI; /* i8255 PPI at A8h-ABh */ byte IOReg; /* Storage for AAh port */ /** Disk controller: WD1793 **********************************/ WD1793 FDC; /* WD1793 at 7FF8h-7FFFh */ FDIDisk FDD[4]; /* Floppy disk images */ /** Sound hardware: PSG, SCC, OPLL ***************************/ AY8910 PSG; /* PSG registers & state */ YM2413 OPLL; /* OPLL registers & state */ SCC SCChip; /* SCC registers & state */ byte SCCOn[2]; /* 1 = SCC page active */ word FMPACKey; /* MAGIC = SRAM active */ /** Serial I/O hardware: i8251+i8253 *************************/ I8251 SIO; /* SIO registers & state */ /** Real-time clock ******************************************/ byte RTCReg,RTCMode; /* RTC register numbers */ byte RTC[4][13]; /* RTC registers */ /** Video processor ******************************************/ byte *ChrGen,*ChrTab,*ColTab; /* VDP tables (screen) */ byte *SprGen,*SprTab; /* VDP tables (sprites) */ int ChrGenM,ChrTabM,ColTabM; /* VDP masks (screen) */ int SprTabM; /* VDP masks (sprites) */ word VAddr; /* VRAM address in VDP */ byte VKey,PKey,WKey; /* Status keys for VDP */ byte FGColor,BGColor; /* Colors */ byte XFGColor,XBGColor; /* Second set of colors */ byte ScrMode; /* Current screen mode */ byte VDP[64],VDPStatus[16]; /* VDP registers */ byte IRQPending; /* Pending interrupts */ int ScanLine; /* Current scanline */ byte VDPData; /* VDP data buffer */ byte PLatch; /* Palette buffer */ byte ALatch; /* Address buffer */ int Palette[16]; /* Current palette */ /** Places in DiskROM to be patched with ED FE C9 ************/ static const word DiskPatches[] = { 0x4010,0x4013,0x4016,0x401C,0x401F,0 }; /** Places in BIOS to be patched with ED FE C9 ***************/ static const word BIOSPatches[] = { 0x00E1,0x00E4,0x00E7,0x00EA,0x00ED,0x00F0,0x00F3,0 }; /** Cartridge map, by primary and secondary slots ************/ static const byte CartMap[4][4] = { { 255,3,4,5 },{ 0,0,0,0 },{ 1,1,1,1 },{ 2,255,255,255 } }; /** Screen Mode Handlers [number of screens + 1] *************/ void (*RefreshLine[MAXSCREEN+2])(byte Y) = { RefreshLine0, /* SCR 0: TEXT 40x24 */ RefreshLine1, /* SCR 1: TEXT 32x24 */ RefreshLine2, /* SCR 2: BLK 256x192 */ RefreshLine3, /* SCR 3: 64x48x16 */ RefreshLine4, /* SCR 4: BLK 256x192 */ RefreshLine5, /* SCR 5: 256x192x16 */ RefreshLine6, /* SCR 6: 512x192x4 */ RefreshLine7, /* SCR 7: 512x192x16 */ RefreshLine8, /* SCR 8: 256x192x256 */ 0, /* SCR 9: NONE */ RefreshLine10, /* SCR 10: YAE 256x192 */ RefreshLine10, /* SCR 11: YAE 256x192 */ RefreshLine12, /* SCR 12: YJK 256x192 */ RefreshLineTx80 /* SCR 0: TEXT 80x24 */ }; /** VDP Address Register Masks *******************************/ static const struct { byte R2,R3,R4,R5,M2,M3,M4,M5; } MSK[MAXSCREEN+2] = { { 0x7F,0x00,0x3F,0x00,0x00,0x00,0x00,0x00 }, /* SCR 0: TEXT 40x24 */ { 0x7F,0xFF,0x3F,0xFF,0x00,0x00,0x00,0x00 }, /* SCR 1: TEXT 32x24 */ { 0x7F,0x80,0x3C,0xFF,0x00,0x7F,0x03,0x00 }, /* SCR 2: BLK 256x192 */ { 0x7F,0x00,0x3F,0xFF,0x00,0x00,0x00,0x00 }, /* SCR 3: 64x48x16 */ { 0x7F,0x80,0x3C,0xFC,0x00,0x7F,0x03,0x03 }, /* SCR 4: BLK 256x192 */ { 0x60,0x00,0x00,0xFC,0x1F,0x00,0x00,0x03 }, /* SCR 5: 256x192x16 */ { 0x60,0x00,0x00,0xFC,0x1F,0x00,0x00,0x03 }, /* SCR 6: 512x192x4 */ { 0x20,0x00,0x00,0xFC,0x1F,0x00,0x00,0x03 }, /* SCR 7: 512x192x16 */ { 0x20,0x00,0x00,0xFC,0x1F,0x00,0x00,0x03 }, /* SCR 8: 256x192x256 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, /* SCR 9: NONE */ { 0x20,0x00,0x00,0xFC,0x1F,0x00,0x00,0x03 }, /* SCR 10: YAE 256x192 */ { 0x20,0x00,0x00,0xFC,0x1F,0x00,0x00,0x03 }, /* SCR 11: YAE 256x192 */ { 0x20,0x00,0x00,0xFC,0x1F,0x00,0x00,0x03 }, /* SCR 12: YJK 256x192 */ { 0x7C,0xF8,0x3F,0x00,0x03,0x07,0x00,0x00 } /* SCR 0: TEXT 80x24 */ }; /** MegaROM Mapper Names *************************************/ static const char *ROMNames[MAXMAPPERS+1] = { "GENERIC/8kB","GENERIC/16kB","KONAMI5/8kB", "KONAMI4/8kB","ASCII/8kB","ASCII/16kB", "GMASTER2/SRAM","FMPAC/SRAM","UNKNOWN" }; /** Keyboard Mapping *****************************************/ /** This keyboard mapping is used by KBD_SET()/KBD_RES() **/ /** macros to modify KeyState[] bits. **/ /*************************************************************/ const byte Keys[][2] = { { 0,0x00 },{ 8,0x10 },{ 8,0x20 },{ 8,0x80 }, /* None,LEFT,UP,RIGHT */ { 8,0x40 },{ 6,0x01 },{ 6,0x02 },{ 6,0x04 }, /* DOWN,SHIFT,CONTROL,GRAPH */ { 7,0x20 },{ 7,0x08 },{ 6,0x08 },{ 7,0x40 }, /* BS,TAB,CAPSLOCK,SELECT */ { 8,0x02 },{ 7,0x80 },{ 8,0x08 },{ 8,0x04 }, /* HOME,ENTER,DELETE,INSERT */ { 6,0x10 },{ 7,0x10 },{ 6,0x20 },{ 6,0x40 }, /* COUNTRY,STOP,F1,F2 */ { 6,0x80 },{ 7,0x01 },{ 7,0x02 },{ 9,0x08 }, /* F3,F4,F5,PAD0 */ { 9,0x10 },{ 9,0x20 },{ 9,0x40 },{ 7,0x04 }, /* PAD1,PAD2,PAD3,ESCAPE */ { 9,0x80 },{ 10,0x01 },{ 10,0x02 },{ 10,0x04 }, /* PAD4,PAD5,PAD6,PAD7 */ { 8,0x01 },{ 0,0x02 },{ 2,0x01 },{ 0,0x08 }, /* SPACE,[!],["],[#] */ { 0,0x10 },{ 0,0x20 },{ 0,0x80 },{ 2,0x01 }, /* [$],[%],[&],['] */ { 1,0x02 },{ 0,0x01 },{ 1,0x01 },{ 1,0x08 }, /* [(],[)],[*],[=] */ { 2,0x04 },{ 1,0x04 },{ 2,0x08 },{ 2,0x10 }, /* [,],[-],[.],[/] */ { 0,0x01 },{ 0,0x02 },{ 0,0x04 },{ 0,0x08 }, /* 0,1,2,3 */ { 0,0x10 },{ 0,0x20 },{ 0,0x40 },{ 0,0x80 }, /* 4,5,6,7 */ { 1,0x01 },{ 1,0x02 },{ 1,0x80 },{ 1,0x80 }, /* 8,9,[:],[;] */ { 2,0x04 },{ 1,0x08 },{ 2,0x08 },{ 2,0x10 }, /* [<],[=],[>],[?] */ { 0,0x04 },{ 2,0x40 },{ 2,0x80 },{ 3,0x01 }, /* [@],A,B,C */ { 3,0x02 },{ 3,0x04 },{ 3,0x08 },{ 3,0x10 }, /* D,E,F,G */ { 3,0x20 },{ 3,0x40 },{ 3,0x80 },{ 4,0x01 }, /* H,I,J,K */ { 4,0x02 },{ 4,0x04 },{ 4,0x08 },{ 4,0x10 }, /* L,M,N,O */ { 4,0x20 },{ 4,0x40 },{ 4,0x80 },{ 5,0x01 }, /* P,Q,R,S */ { 5,0x02 },{ 5,0x04 },{ 5,0x08 },{ 5,0x10 }, /* T,U,V,W */ { 5,0x20 },{ 5,0x40 },{ 5,0x80 },{ 1,0x20 }, /* X,Y,Z,[[] */ { 1,0x10 },{ 1,0x40 },{ 0,0x40 },{ 1,0x04 }, /* [\],[]],[^],[_] */ { 2,0x02 },{ 2,0x40 },{ 2,0x80 },{ 3,0x01 }, /* [`],a,b,c */ { 3,0x02 },{ 3,0x04 },{ 3,0x08 },{ 3,0x10 }, /* d,e,f,g */ { 3,0x20 },{ 3,0x40 },{ 3,0x80 },{ 4,0x01 }, /* h,i,j,k */ { 4,0x02 },{ 4,0x04 },{ 4,0x08 },{ 4,0x10 }, /* l,m,n,o */ { 4,0x20 },{ 4,0x40 },{ 4,0x80 },{ 5,0x01 }, /* p,q,r,s */ { 5,0x02 },{ 5,0x04 },{ 5,0x08 },{ 5,0x10 }, /* t,u,v,w */ { 5,0x20 },{ 5,0x40 },{ 5,0x80 },{ 1,0x20 }, /* x,y,z,[{] */ { 1,0x10 },{ 1,0x40 },{ 2,0x02 },{ 8,0x08 }, /* [|],[}],[~],DEL */ { 10,0x08 },{ 10,0x10 } /* PAD8,PAD9 */ }; /** Internal Functions ***************************************/ /** These functions are defined and internally used by the **/ /** code in MSX.c. **/ /*************************************************************/ byte *LoadROM(const char *Name,int Size,byte *Buf); int GuessROM(const byte *Buf,int Size); void SetMegaROM(int Slot,byte P0,byte P1,byte P2,byte P3); void MapROM(word A,byte V); /* Switch MegaROM banks */ void PSlot(byte V); /* Switch primary slots */ void SSlot(byte V); /* Switch secondary slots */ void VDPOut(byte R,byte V); /* Write value into a VDP register */ void Printer(byte V); /* Send a character to a printer */ void PPIOut(byte New,byte Old); /* Set PPI bits (key click, etc.) */ void CheckSprites(void); /* Check collisions and 5th sprite */ byte RTCIn(byte R); /* Read RTC registers */ byte SetScreen(void); /* Change screen mode */ word SetIRQ(byte IRQ); /* Set/Reset IRQ */ word StateID(void); /* Compute emulation state ID */ static int stricmpn(const char *S1,const char *S2,int Limit); static byte *GetMemory(int Size); /* Get memory chunk */ static void FreeMemory(byte *Ptr);/* Free memory chunk */ static void FreeAllMemory(void); /* Free all memory chunks */ /** stricmpn() ***********************************************/ /** Case-indifferent comparison of up to Length characters. **/ /*************************************************************/ static int stricmpn(const char *S1,const char *S2,int Limit) { for(;*S1&&*S2&&Limit&&(toupper(*S1)==toupper(*S2));++S1,++S2,--Limit); return(Limit? toupper(*S1)-toupper(*S2):0); } /** GetMemory() **********************************************/ /** Allocate a memory chunk of given size using malloc(). **/ /** Store allocated address in Chunks[] for later disposal. **/ /*************************************************************/ static byte *GetMemory(int Size) { byte *P; if((Size<=0)||(NChunks>=MAXCHUNKS)) return(0); P=(byte *)malloc(Size); if(P) Chunks[NChunks++]=P; return(P); } /** FreeMemory() *********************************************/ /** Free memory allocated by a previous GetMemory() call. **/ /*************************************************************/ static void FreeMemory(byte *Ptr) { int J; /* Special case: we do not free EmptyRAM! */ if(!Ptr||(Ptr==EmptyRAM)) return; for(J=0;(J100? 100:UPeriod; /* Allocate 16kB for the empty space (scratch RAM) */ if(Verbose) printf("Allocating 16kB for empty space...\n"); if(!(EmptyRAM=GetMemory(0x4000))) { PRINTFAILED;return(0); } memset(EmptyRAM,NORAM,0x4000); /* Reset memory map to the empty space */ for(I=0;I<4;++I) for(J=0;J<4;++J) for(K=0;K<8;++K) MemMap[I][J][K]=EmptyRAM; /* Save current directory */ if(ProgDir) if(WorkDir=getcwd(0,1024)) Chunks[NChunks++]=WorkDir; /* Set invalid modes and RAM/VRAM sizes before calling ResetMSX() */ Mode = ~NewMode; RAMPages = 0; VRAMPages = 0; /* Try resetting MSX, allocating memory, loading ROMs */ if((ResetMSX(NewMode,NewRAMPages,NewVRAMPages)^NewMode)&MSX_MODEL) return(0); if(!RAMPages||!VRAMPages) return(0); /* Change to the program directory */ if(ProgDir) chdir(ProgDir); /* Try loading font */ if(FNTName) { if(Verbose) printf("Loading %s font...",FNTName); J=LoadFNT(FNTName); PRINTRESULT(J); } if(Verbose) printf("Loading optional ROMs: "); /* Try loading CMOS memory contents */ if(LoadROM("CMOS.ROM",sizeof(RTC),(byte *)RTC)) { if(Verbose) printf("CMOS.ROM.."); } else memcpy(RTC,RTCInit,sizeof(RTC)); /* Try loading Kanji alphabet ROM */ #ifdef MINIZIP if(Kanji=LoadROM("SYSTEM.ZIP/KANJI.ROM",0x20000,0)) { if(Verbose) printf("KANJI.ROM.."); } else #endif if(Kanji=LoadROM("KANJI.ROM",0x20000,0)) { if(Verbose) printf("KANJI.ROM.."); } /* Try loading RS232 support ROM to slot */ #ifdef MINIZIP if(P=LoadROM("SYSTEM.ZIP/RS232.ROM",0x4000,0)) { if(Verbose) printf("RS232.ROM.."); MemMap[3][3][2]=P; MemMap[3][3][3]=P+0x2000; } else #endif if(P=LoadROM("RS232.ROM",0x4000,0)) { if(Verbose) printf("RS232.ROM.."); MemMap[3][3][2]=P; MemMap[3][3][3]=P+0x2000; } PRINTOK; /* Start loading system cartridges */ J=MAXCARTS; /* If MSX2 or better and DiskROM present... */ /* ...try loading MSXDOS2 cartridge into 3:0 */ if(!MODEL(MSX_MSX1)&&(MemMap[3][1][2]!=EmptyRAM)&&!ROMData[2]) { #ifdef MINIZIP if(LoadCart("SYSTEM.ZIP/MSXDOS2.ROM",2,MAP_GEN16)) SetMegaROM(2,0,1,ROMMask[J]-1,ROMMask[J]); else #endif if(LoadCart("MSXDOS2.ROM",2,MAP_GEN16)) SetMegaROM(2,0,1,ROMMask[J]-1,ROMMask[J]); } /* If MSX2 or better, load PAINTER cartridge */ if(!MODEL(MSX_MSX1)) { for(;(J256)) NewRAMPages=MODEL(MSX_MSX1)? 4:8; if((NewVRAMPages<(MODEL(MSX_MSX1)? 2:8))||(NewVRAMPages>8)) NewVRAMPages=MODEL(MSX_MSX1)? 2:8; /* If changing amount of RAM... */ if(NewRAMPages!=RAMPages) { if(Verbose) printf("Allocating %dkB for RAM...",NewRAMPages*16); if(P1=GetMemory(NewRAMPages*0x4000)) { memset(P1,NORAM,NewRAMPages*0x4000); FreeMemory(RAMData); RAMPages = NewRAMPages; RAMMask = NewRAMPages-1; RAMData = P1; } PRINTRESULT(P1); } /* If changing amount of VRAM... */ if(NewVRAMPages!=VRAMPages) { if(Verbose) printf("Allocating %dkB for VRAM...",NewVRAMPages*16); if(P1=GetMemory(NewVRAMPages*0x4000)) { memset(P1,0x00,NewVRAMPages*0x4000); FreeMemory(VRAM); VRAMPages = NewVRAMPages; VRAM = P1; } PRINTRESULT(P1); } /* For all slots... */ for(J=0;J<4;++J) { /* Slot is currently read-only */ EnWrite[J] = 0; /* PSL=0:0:0:0, SSL=0:0:0:0 */ PSL[J] = 0; SSL[J] = 0; /* RAMMap=3:2:1:0 */ MemMap[3][2][J*2] = RAMData+(3-J)*0x4000; MemMap[3][2][J*2+1] = MemMap[3][2][J*2]+0x2000; RAMMapper[J] = 3-J; /* Setting address space */ RAM[J*2] = MemMap[0][0][J*2]; RAM[J*2+1] = MemMap[0][0][J*2+1]; } /* For all MegaROMs... */ for(J=0;J4) { /* For normal MegaROMs, set first four pages */ if((ROMData[J][0]=='A')&&(ROMData[J][1]=='B')) SetMegaROM(J,0,1,2,3); /* Some MegaROMs default to last pages on reset */ else if((ROMData[J][(I-2)<<13]=='A')&&(ROMData[J][((I-2)<<13)+1]=='B')) SetMegaROM(J,I-2,I-1,I-2,I-1); /* If 'AB' signature is not found at the beginning or the end */ /* then it is not a MegaROM but rather a plain 64kB ROM */ } /* Reset sound chips */ Reset8910(&PSG,(1000*CPU_CLOCK)>>1,0); ResetSCC(&SCChip,AY8910_CHANNELS); Reset2413(&OPLL,AY8910_CHANNELS); Sync8910(&PSG,AY8910_SYNC); SyncSCC(&SCChip,SCC_SYNC); Sync2413(&OPLL,YM2413_SYNC); /* Reset serial I/O */ Reset8251(&SIO,ComIStream,ComOStream); /* Reset PPI chips and slot selectors */ Reset8255(&PPI); PPI.Rout[0]=PSLReg=0x00; PPI.Rout[2]=IOReg=0x00; SSLReg[0]=0x00; SSLReg[1]=0x00; SSLReg[2]=0x00; SSLReg[3]=0x00; /* Reset floppy disk controller */ Reset1793(&FDC,FDD,WD1793_KEEP); /* Reset VDP */ memcpy(VDP,VDPInit,sizeof(VDP)); memcpy(VDPStatus,VDPSInit,sizeof(VDPStatus)); /* Reset keyboard */ memset((void *)KeyState,0xFF,16); /* Set initial palette */ for(J=0;J<16;++J) { Palette[J]=PalInit[J]; SetColor(J,(Palette[J]>>16)&0xFF,(Palette[J]>>8)&0xFF,Palette[J]&0xFF); } /* Reset mouse coordinates/counters */ for(J=0;J<2;++J) MouState[J]=MouseDX[J]=MouseDY[J]=OldMouseX[J]=OldMouseY[J]=MCount[J]=0; IRQPending=0x00; /* No IRQs pending */ SCCOn[0]=SCCOn[1]=0; /* SCCs off for now */ RTCReg=RTCMode=0; /* Clock registers */ KanCount=0;KanLetter=0; /* Kanji extension */ ChrTab=ColTab=ChrGen=VRAM; /* VDP tables */ SprTab=SprGen=VRAM; ChrTabM=ColTabM=ChrGenM=SprTabM=~0; /* VDP addr. masks */ VPAGE=VRAM; /* VRAM page */ FGColor=BGColor=XFGColor=XBGColor=0; /* VDP colors */ ScrMode=0; /* Screen mode */ VKey=PKey=1;WKey=0; /* VDP keys */ VAddr=0x0000; /* VRAM access addr */ ScanLine=0; /* Current scanline */ VDPData=NORAM; /* VDP data buffer */ JoyState=0; /* Joystick state */ /* Set "V9958" VDP version for MSX2+ */ if(MODEL(MSX_MSX2P)) VDPStatus[1]|=0x04; #ifdef ALTSOUND /* Reset soundchips */ ResetSound(); #endif /* Reset CPU */ ResetZ80(&CPU); /* Done */ return(Mode); } /** RdZ80() **************************************************/ /** Z80 emulation calls this function to read a byte from **/ /** address A in the Z80 address space. Also see OpZ80() in **/ /** Z80.c which is a simplified code-only RdZ80() version. **/ /*************************************************************/ byte RdZ80(word A) { /* Filter out everything but [xx11 1111 1xxx 1xxx] */ if((A&0x3F88)!=0x3F88) return(RAM[A>>13][A&0x1FFF]); /* Secondary slot selector */ if(A==0xFFFF) return(~SSLReg[PSL[3]]); /* Floppy disk controller */ /* 7FF8h..7FFFh Standard DiskROM */ /* BFF8h..BFFFh MSX-DOS BDOS */ /* 7F80h..7F87h Arabic DiskROM */ /* 7FB8h..7FBFh SV738/TechnoAhead */ if((PSL[A>>14]==3)&&(SSL[A>>14]==1)) switch(A) { /* Standard MSX-DOS Arabic SV738 */ case 0x7FF8: case 0xBFF8: case 0x7F80: case 0x7FB8: /* STATUS */ case 0x7FF9: case 0xBFF9: case 0x7F81: case 0x7FB9: /* TRACK */ case 0x7FFA: case 0xBFFA: case 0x7F82: case 0x7FBA: /* SECTOR */ case 0x7FFB: case 0xBFFB: case 0x7F83: case 0x7FBB: /* DATA */ return(Read1793(&FDC,A&0x0003)); case 0x7FFF: case 0xBFFF: case 0x7F84: case 0x7FBC: /* SYSTEM */ return(Read1793(&FDC,WD1793_READY)); } /* Default to reading memory */ return(RAM[A>>13][A&0x1FFF]); } /** WrZ80() **************************************************/ /** Z80 emulation calls this function to write byte V to **/ /** address A of Z80 address space. **/ /*************************************************************/ void WrZ80(word A,byte V) { /* Secondary slot selector */ if(A==0xFFFF) { SSlot(V);return; } /* Floppy disk controller */ /* 7FF8h..7FFFh Standard DiskROM */ /* BFF8h..BFFFh MSX-DOS BDOS */ /* 7F80h..7F87h Arabic DiskROM */ /* 7FB8h..7FBFh SV738/TechnoAhead */ if(((A&0x3F88)==0x3F88)&&(PSL[A>>14]==3)&&(SSL[A>>14]==1)) switch(A) { /* Standard MSX-DOS Arabic SV738 */ case 0x7FF8: case 0xBFF8: case 0x7F80: case 0x7FB8: /* COMMAND */ case 0x7FF9: case 0xBFF9: case 0x7F81: case 0x7FB9: /* TRACK */ case 0x7FFA: case 0xBFFA: case 0x7F82: case 0x7FBA: /* SECTOR */ case 0x7FFB: case 0xBFFB: case 0x7F83: case 0x7FBB: /* DATA */ Write1793(&FDC,A&0x0003,V); return; case 0xBFFC: /* Standard/MSX-DOS */ case 0x7FFC: /* Side: [xxxxxxxS] */ Write1793(&FDC,WD1793_SYSTEM,FDC.Drive|S_DENSITY|(V&0x01? 0:S_SIDE)); return; case 0xBFFD: /* Standard/MSX-DOS */ case 0x7FFD: /* Drive: [xxxxxxxD] */ Write1793(&FDC,WD1793_SYSTEM,(V&0x01)|S_DENSITY|(FDC.Side? 0:S_SIDE)); return; case 0x7FBC: /* Arabic/SV738 */ case 0x7F84: /* Side/Drive/Motor: [xxxxMSDD] */ Write1793(&FDC,WD1793_SYSTEM,(V&0x03)|S_DENSITY|(V&0x04? 0:S_SIDE)); return; } /* Write to RAM, if enabled */ if(EnWrite[A>>14]) { RAM[A>>13][A&0x1FFF]=V;return; } /* Switch MegaROM pages */ if((A>0x3FFF)&&(A<0xC000)) MapROM(A,V); } /** InZ80() **************************************************/ /** Z80 emulation calls this function to read a byte from **/ /** a given I/O port. **/ /*************************************************************/ byte InZ80(word Port) { /* MSX only uses 256 IO ports */ Port&=0xFF; /* Return an appropriate port value */ switch(Port) { #ifdef ALTSOUND case 0x04: if(Use8950) return 2; else return(NORAM); case 0x05: if(Use8950) return 0; else return(NORAM); case 0xC0: if(Use8950) return ReadAUDIO(0); else return(NORAM); case 0xC1: if(Use8950) return ReadAUDIO(1); else return(NORAM); #endif case 0x90: return(0xFD); /* Printer READY signal */ case 0xB5: return(RTCIn(RTCReg)); /* RTC registers */ case 0xA8: /* Primary slot state */ case 0xA9: /* Keyboard port */ case 0xAA: /* General IO register */ case 0xAB: /* PPI control register */ PPI.Rin[1]=KeyState[PPI.Rout[2]&0x0F]; return(Read8255(&PPI,Port-0xA8)); case 0xFC: /* Mapper page at 0000h */ case 0xFD: /* Mapper page at 4000h */ case 0xFE: /* Mapper page at 8000h */ case 0xFF: /* Mapper page at C000h */ return(RAMMapper[Port-0xFC]|~RAMMask); case 0xD9: /* Kanji support */ Port=Kanji? Kanji[KanLetter+KanCount]:NORAM; KanCount=(KanCount+1)&0x1F; return(Port); case 0x80: /* SIO data */ case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: return(NORAM); /*return(Rd8251(&SIO,Port&0x07));*/ case 0x98: /* VRAM read port */ /* Read from VRAM data buffer */ Port=VDPData; /* Reset VAddr latch sequencer */ VKey=1; /* Fill data buffer with a new value */ VDPData=VPAGE[VAddr]; /* Increment VRAM address */ VAddr=(VAddr+1)&0x3FFF; /* If rolled over, modify VRAM page# */ if(!VAddr&&(ScrMode>3)) { VDP[14]=(VDP[14]+1)&(VRAMPages-1); VPAGE=VRAM+((int)VDP[14]<<14); } return(Port); case 0x99: /* VDP status registers */ /* Read an appropriate status register */ Port=VDPStatus[VDP[15]]; /* Reset VAddr latch sequencer */ VKey=1; /* Update status register's contents */ switch(VDP[15]) { case 0: VDPStatus[0]&=0x5F;SetIRQ(~INT_IE0);break; case 1: VDPStatus[1]&=0xFE;SetIRQ(~INT_IE1);break; case 7: VDPStatus[7]=VDP[44]=VDPRead();break; } /* Return the status register value */ return(Port); case 0xA2: /* PSG input port */ /* PSG[14] returns joystick/mouse data */ if(PSG.Latch==14) { int DX,DY,L,J; /* Number of a joystick port */ Port = (PSG.R[15]&0x40)>>6; L = JOYTYPE(Port); /* If no joystick, return dummy value */ if(L==JOY_NONE) return(0x7F); /* Compute mouse offsets, if needed */ if(MCount[Port]==1) { /* Get new mouse coordinates */ DX=MouState[Port]&0xFF; DY=(MouState[Port]>>8)&0xFF; /* Compute offsets and store coordinates */ J=OldMouseX[Port]-DX;OldMouseX[Port]=DX;DX=J; J=OldMouseY[Port]-DY;OldMouseY[Port]=DY;DY=J; /* For 512-wide mode, double horizontal offset */ if((ScrMode==6)||((ScrMode==7)&&!ModeYJK)||(ScrMode==MAXSCREEN+1)) DX<<=1; /* Adjust offsets */ MouseDX[Port]=(DX>127? 127:(DX<-127? -127:DX))&0xFF; MouseDY[Port]=(DY>127? 127:(DY<-127? -127:DY))&0xFF; } /* Get joystick state */ J=~(Port? (JoyState>>8):JoyState)&0x3F; /* Determine return value */ switch(MCount[Port]) { case 0: Port=PSG.R[15]&(0x10<>4)|(J&0x30);break; case 2: Port=(MouseDX[Port]&0x0F)|(J&0x30);break; case 3: Port=(MouseDY[Port]>>4)|(J&0x30);break; case 4: Port=(MouseDY[Port]&0x0F)|(J&0x30);break; } /* 6th bit is always 1 */ return(Port|0x40); } /* PSG[15] resets mouse counters (???) */ if(PSG.Latch==15) { /* @@@ For debugging purposes */ /*printf("Reading from PSG[15]\n");*/ /*MCount[0]=MCount[1]=0;*/ return(PSG.R[15]&0xF0); } /* Return PSG[0-13] as they are */ #ifdef ALTSOUND return ReadPSG(PSG.Latch); #else return(RdData8910(&PSG)); #endif case 0xD0: /* FDC status */ case 0xD1: /* FDC track */ case 0xD2: /* FDC sector */ case 0xD3: /* FDC data */ case 0xD4: /* FDC IRQ/DRQ */ /* Brazilian DiskROM I/O ports */ return(Read1793(&FDC,Port-0xD0)); } /* Return NORAM for non-existing ports */ if(Verbose&0x20) printf("I/O: Read from unknown PORT[%02Xh]\n",Port); return(NORAM); } /** OutZ80() *************************************************/ /** Z80 emulation calls this function to write byte V to a **/ /** given I/O port. **/ /*************************************************************/ void OutZ80(word Port,byte Value) { register byte I,J; Port&=0xFF; switch(Port) { #ifdef ALTSOUND case 0x7C: if (Use2413) OPLL.Latch=Value; return; /* OPLL Register# */ case 0x7D: if (Use2413) WriteOPLL(OPLL.Latch,Value);return; /* OPLL Data */ case 0xA0: PSG.Latch=Value;return; /* PSG Register# */ case 0xC0: if (Use8950) WriteAUDIO(0,Value);return; /* AUDIO Register#*/ case 0xC1: if (Use8950) WriteAUDIO(1,Value);return; /* AUDIO Data */ #else case 0x7C: WrCtrl2413(&OPLL,Value);return; /* OPLL Register# */ case 0x7D: WrData2413(&OPLL,Value);return; /* OPLL Data */ case 0xA0: WrCtrl8910(&PSG,Value);return; /* PSG Register# */ #endif case 0x91: Printer(Value);return; /* Printer Data */ case 0xB4: RTCReg=Value&0x0F;return; /* RTC Register# */ case 0xD8: /* Upper bits of Kanji ROM address */ KanLetter=(KanLetter&0x1F800)|((int)(Value&0x3F)<<5); KanCount=0; return; case 0xD9: /* Lower bits of Kanji ROM address */ KanLetter=(KanLetter&0x007E0)|((int)(Value&0x3F)<<11); KanCount=0; return; case 0x80: /* SIO data */ case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: return; /*Wr8251(&SIO,Port&0x07,Value); return;*/ case 0x98: /* VDP Data */ VKey=1; if(WKey) { /* VDP set for writing */ VDPData=VPAGE[VAddr]=Value; VAddr=(VAddr+1)&0x3FFF; } else { /* VDP set for reading */ VDPData=VPAGE[VAddr]; VAddr=(VAddr+1)&0x3FFF; VPAGE[VAddr]=Value; } /* If VAddr rolled over, modify VRAM page# */ if(!VAddr&&(ScrMode>3)) { VDP[14]=(VDP[14]+1)&(VRAMPages-1); VPAGE=VRAM+((int)VDP[14]<<14); } return; case 0x99: /* VDP Address Latch */ if(VKey) { ALatch=Value;VKey=0; } else { VKey=1; switch(Value&0xC0) { case 0x80: /* Writing into VDP registers */ VDPOut(Value&0x3F,ALatch); break; case 0x00: case 0x40: /* Set the VRAM access address */ VAddr=(((word)Value<<8)+ALatch)&0x3FFF; /* WKey=1 when VDP set for writing into VRAM */ WKey=Value&0x40; /* When set for reading, perform first read */ if(!WKey) { VDPData=VPAGE[VAddr]; VAddr=(VAddr+1)&0x3FFF; if(!VAddr&&(ScrMode>3)) { VDP[14]=(VDP[14]+1)&(VRAMPages-1); VPAGE=VRAM+((int)VDP[14]<<14); } } break; } } return; case 0x9A: /* VDP Palette Latch */ if(PKey) { PLatch=Value;PKey=0; } else { byte R,G,B; /* New palette entry written */ PKey=1; J=VDP[16]; /* Compute new color components */ R=(PLatch&0x70)*255/112; G=(Value&0x07)*255/7; B=(PLatch&0x07)*255/7; /* Set new color for palette entry J */ Palette[J]=RGB2INT(R,G,B); SetColor(J,R,G,B); /* Next palette entry */ VDP[16]=(J+1)&0x0F; } return; case 0x9B: /* VDP Register Access */ J=VDP[17]&0x3F; if(J!=17) VDPOut(J,Value); if(!(VDP[17]&0x80)) VDP[17]=(J+1)&0x3F; return; case 0xA1: /* PSG Data */ /* PSG[15] is responsible for joystick/mouse */ if(PSG.Latch==15) { /* @@@ For debugging purposes */ /*printf("Writing PSG[15] <- %02Xh\n",Value);*/ /* For mouse, update nibble counter */ /* For joystick, set nibble counter to 0 */ if((Value&0x0C)==0x0C) MCount[1]=0; else if((JOYTYPE(1)==JOY_MOUSE)&&((Value^PSG.R[15])&0x20)) MCount[1]+=MCount[1]==4? -3:1; /* For mouse, update nibble counter */ /* For joystick, set nibble counter to 0 */ if((Value&0x03)==0x03) MCount[0]=0; else if((JOYTYPE(0)==JOY_MOUSE)&&((Value^PSG.R[15])&0x10)) MCount[0]+=MCount[0]==4? -3:1; } /* Put value into a register */ #ifdef ALTSOUND WritePSG(PSG.Latch,Value); #else WrData8910(&PSG,Value); #endif return; case 0xA8: /* Primary slot state */ case 0xA9: /* Keyboard port */ case 0xAA: /* General IO register */ case 0xAB: /* PPI control register */ /* Write to PPI */ Write8255(&PPI,Port-0xA8,Value); /* If general I/O register has changed... */ if(PPI.Rout[2]!=IOReg) { PPIOut(PPI.Rout[2],IOReg);IOReg=PPI.Rout[2]; } /* If primary slot state has changed... */ if(PPI.Rout[0]!=PSLReg) PSlot(PPI.Rout[0]); /* Done */ return; case 0xB5: /* RTC Data */ if(RTCReg<13) { /* J = register bank# now */ J=RTCMode&0x03; /* Store the value */ RTC[J][RTCReg]=Value; /* If CMOS modified, we need to save it */ if(J>1) SaveCMOS=1; return; } /* RTC[13] is a register bank# */ if(RTCReg==13) RTCMode=Value; return; case 0xD0: /* FDC command */ case 0xD1: /* FDC track */ case 0xD2: /* FDC sector */ case 0xD3: /* FDC data */ /* Brazilian DiskROM I/O ports */ Write1793(&FDC,Port-0xD0,Value); return; case 0xD4: /* FDC system */ /* Brazilian DiskROM drive/side: [xxxSxxDx] */ Value=((Value&0x02)>>1)|S_DENSITY|(Value&0x10? 0:S_SIDE); Write1793(&FDC,WD1793_SYSTEM,Value); return; case 0xFC: /* Mapper page at 0000h */ case 0xFD: /* Mapper page at 4000h */ case 0xFE: /* Mapper page at 8000h */ case 0xFF: /* Mapper page at C000h */ 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] = RAMData+((int)Value<<14); MemMap[3][2][I+1] = MemMap[3][2][I]+0x2000; if((PSL[J]==3)&&(SSL[J]==2)) { EnWrite[J] = 1; RAM[I] = MemMap[3][2][I]; RAM[I+1] = MemMap[3][2][I+1]; } } return; } /* Unknown port */ if(Verbose&0x20) printf("I/O: Write to unknown PORT[%02Xh]=%02Xh\n",Port,Value); } /** MapROM() *************************************************/ /** Switch ROM Mapper pages. This function is supposed to **/ /** be called when ROM page registers are written to. **/ /*************************************************************/ void MapROM(register word A,register byte V) { byte I,J,PS,SS,*P; /* @@@ For debugging purposes printf("(%04Xh) = %02Xh at PC=%04Xh\n",A,V,CPU.PC.W); */ J = A>>14; /* 16kB page number 0-3 */ PS = PSL[J]; /* Primary slot number */ SS = SSL[J]; /* Secondary slot number */ I = CartMap[PS][SS]; /* Cartridge number */ /* Drop out if no cartridge in that slot */ if(I>=MAXSLOTS) return; #ifdef ALTSOUND if((A&0xFF00)==0x9800 || (A&0xFF00)==0xB800) { Write2212(A,V); return; } #endif /* SCC: enable/disable for no cart */ if(!ROMData[I]&&(A==0x9000)) SCCOn[I]=(V==0x3F)? 1:0; /* SCC: types 0, 2, or no cart */ if(((A&0xFF00)==0x9800)&&SCCOn[I]) { /* Compute SCC register number */ J=A&0x00FF; /* When no MegaROM present, we allow the program */ /* to write into SCC wave buffer using EmptyRAM */ /* as a scratch pad. */ if(!ROMData[I]&&(J<0x80)) EmptyRAM[0x1800+J]=V; /* Output data to SCC chip */ WriteSCC(&SCChip,J,V); return; } /* SCC+: types 0, 2, or no cart */ if(((A&0xFF00)==0xB800)&&SCCOn[I]) { /* Compute SCC register number */ J=A&0x00FF; /* When no MegaROM present, we allow the program */ /* to write into SCC wave buffer using EmptyRAM */ /* as a scratch pad. */ if(!ROMData[I]&&(J<0xA0)) EmptyRAM[0x1800+J]=V; /* Output data to SCC chip */ WriteSCCP(&SCChip,J,V); return; } /* If no cartridge or no mapper, exit */ if(!ROMData[I]||!ROMMask[I]) return; switch(ROMType[I]) { case MAP_GEN8: /* Generic 8kB cartridges (Konami, etc.) */ /* Only interested in writes to 4000h-BFFFh */ if((A<0x4000)||(A>0xBFFF)) break; J=(A-0x4000)>>13; /* Turn SCC on/off on writes to 8000h-9FFFh */ if(J==2) SCCOn[I]=(V==0x3F)? 1:0; /* Switch ROM pages */ V&=ROMMask[I]; if(V!=ROMMapper[I][J]) { RAM[J+2]=MemMap[PS][SS][J+2]=ROMData[I]+((int)V<<13); ROMMapper[I][J]=V; } if(Verbose&0x08) printf("ROM-MAPPER %c: 8kB ROM page #%d at %d:%d:%04Xh\n",I+'A',V,PS,SS,J*0x2000+0x4000); return; case MAP_GEN16: /* Generic 16kB cartridges (MSXDOS2, HoleInOneSpecial) */ /* Only interested in writes to 4000h-BFFFh */ if((A<0x4000)||(A>0xBFFF)) break; J=(A&0x8000)>>14; /* Switch ROM pages */ V=(V<<1)&ROMMask[I]; if(V!=ROMMapper[I][J]) { RAM[J+2]=MemMap[PS][SS][J+2]=ROMData[I]+((int)V<<13); RAM[J+3]=MemMap[PS][SS][J+3]=RAM[J+2]+0x2000; ROMMapper[I][J]=V; } if(Verbose&0x08) printf("ROM-MAPPER %c: 16kB ROM page #%d at %d:%d:%04Xh\n",I+'A',V>>1,PS,SS,J*0x2000+0x4000); return; case MAP_KONAMI5: /* KONAMI5 8kB cartridges */ /* Only interested in writes to 5000h/7000h/9000h/B000h */ if((A<0x5000)||(A>0xB000)||((A&0x1FFF)!=0x1000)) break; J=(A-0x5000)>>13; /* Turn SCC on/off on writes to 9000h */ if(J==2) SCCOn[I]=(V==0x3F)? 1:0; /* Switch ROM pages */ V&=ROMMask[I]; if(V!=ROMMapper[I][J]) { RAM[J+2]=MemMap[PS][SS][J+2]=ROMData[I]+((int)V<<13); ROMMapper[I][J]=V; } if(Verbose&0x08) printf("ROM-MAPPER %c: 8kB ROM page #%d at %d:%d:%04Xh\n",I+'A',V,PS,SS,J*0x2000+0x4000); return; case MAP_KONAMI4: /* KONAMI4 8kB cartridges */ /* Only interested in writes to 6000h/8000h/A000h */ /* (page at 4000h is fixed) */ if((A<0x6000)||(A>0xA000)||(A&0x1FFF)) break; J=(A-0x4000)>>13; /* Switch ROM pages */ V&=ROMMask[I]; if(V!=ROMMapper[I][J]) { RAM[J+2]=MemMap[PS][SS][J+2]=ROMData[I]+((int)V<<13); ROMMapper[I][J]=V; } if(Verbose&0x08) printf("ROM-MAPPER %c: 8kB ROM page #%d at %d:%d:%04Xh\n",I+'A',V,PS,SS,J*0x2000+0x4000); return; case MAP_ASCII8: /* ASCII 8kB cartridges */ /* If switching pages... */ if((A>=0x6000)&&(A<0x8000)) { J=(A&0x1800)>>11; /* If selecting SRAM... */ if(V&(ROMMask[I]+1)) { /* Select SRAM page */ V=0xFF; P=SRAMData[I]; if(Verbose&0x08) printf("ROM-MAPPER %c: 8kB SRAM at %d:%d:%04Xh\n",I+'A',PS,SS,J*0x2000+0x4000); } else { /* Select ROM page */ V&=ROMMask[I]; P=ROMData[I]+((int)V<<13); if(Verbose&0x08) printf("ROM-MAPPER %c: 8kB ROM page #%d at %d:%d:%04Xh\n",I+'A',V,PS,SS,J*0x2000+0x4000); } /* If page was actually changed... */ if(V!=ROMMapper[I][J]) { MemMap[PS][SS][J+2]=P; ROMMapper[I][J]=V; /* Only update memory when cartridge's slot selected */ if((PSL[(J>>1)+1]==PS)&&(SSL[(J>>1)+1]==SS)) RAM[J+2]=P; } /* Done with page switch */ return; } /* Write to SRAM */ if((A>=0x8000)&&(A<0xC000)&&(ROMMapper[I][((A>>13)&1)+2]==0xFF)) { RAM[A>>13][A&0x1FFF]=V; SaveSRAM[I]=1; /* Done with SRAM write */ return; } break; case MAP_ASCII16: /*** ASCII 16kB cartridges ***/ /* If switching pages... */ if((A>=0x6000)&&(A<0x8000)) { J=(A&0x1000)>>11; /* If selecting SRAM... */ if(V&(ROMMask[I]+1)) { /* Select SRAM page */ V=0xFF; P=SRAMData[I]; if(Verbose&0x08) printf("ROM-MAPPER %c: 2kB SRAM at %d:%d:%04Xh\n",I+'A',PS,SS,J*0x2000+0x4000); } else { /* Select ROM page */ V=(V<<1)&ROMMask[I]; P=ROMData[I]+((int)V<<13); if(Verbose&0x08) printf("ROM-MAPPER %c: 16kB ROM page #%d at %d:%d:%04Xh\n",I+'A',V>>1,PS,SS,J*0x2000+0x4000); } /* If page was actually changed... */ if(V!=ROMMapper[I][J]) { MemMap[PS][SS][J+2]=P; MemMap[PS][SS][J+3]=P+0x2000; ROMMapper[I][J]=V; /* Only update memory when cartridge's slot selected */ if((PSL[(J>>1)+1]==PS)&&(SSL[(J>>1)+1]==SS)) { RAM[J+2]=P; RAM[J+3]=P+0x2000; } } /* Done with page switch */ return; } /* Write to SRAM */ if((A>=0x8000)&&(A<0xC000)&&(ROMMapper[I][2]==0xFF)) { P=RAM[A>>13]; A&=0x07FF; P[A+0x0800]=P[A+0x1000]=P[A+0x1800]= P[A+0x2000]=P[A+0x2800]=P[A+0x3000]= P[A+0x3800]=P[A]=V; SaveSRAM[I]=1; /* Done with SRAM write */ return; } break; case MAP_GMASTER2: /* Konami GameMaster2+SRAM cartridge */ /* Switch ROM and SRAM pages, page at 4000h is fixed */ if((A>=0x6000)&&(A<=0xA000)&&!(A&0x1FFF)) { /* Figure out which ROM page gets switched */ J=(A-0x4000)>>13; /* If changing SRAM page... */ if(V&0x10) { /* Select SRAM page */ RAM[J+2]=MemMap[PS][SS][J+2]=SRAMData[I]+(V&0x20? 0x2000:0); /* SRAM is now on */ ROMMapper[I][J]=0xFF; if(Verbose&0x08) printf("GMASTER2 %c: 4kB SRAM page #%d at %d:%d:%04Xh\n",I+'A',(V&0x20)>>5,PS,SS,J*0x2000+0x4000); } else { /* Compute new ROM page number */ V&=ROMMask[I]; /* If ROM page number has changed... */ if(V!=ROMMapper[I][J]) { RAM[J+2]=MemMap[PS][SS][J+2]=ROMData[I]+((int)V<<13); ROMMapper[I][J]=V; } if(Verbose&0x08) printf("GMASTER2 %c: 8kB ROM page #%d at %d:%d:%04Xh\n",I+'A',V,PS,SS,J*0x2000+0x4000); } /* Done with page switch */ return; } /* Write to SRAM */ if((A>=0xB000)&&(A<0xC000)&&(ROMMapper[I][3]==0xFF)) { RAM[5][(A&0x0FFF)|0x1000]=RAM[5][A&0x0FFF]=V; SaveSRAM[I]=1; /* Done with SRAM write */ return; } break; case MAP_FMPAC: /* Panasonic FMPAC+SRAM cartridge */ /* See if any switching occurs */ switch(A) { case 0x7FF7: /* ROM page select */ V=(V<<1)&ROMMask[I]; ROMMapper[I][0]=V; /* 4000h-5FFFh contains SRAM when correct FMPACKey supplied */ if(FMPACKey!=FMPAC_MAGIC) { P=ROMData[I]+((int)V<<13); RAM[2]=MemMap[PS][SS][2]=P; RAM[3]=MemMap[PS][SS][3]=P+0x2000; } if(Verbose&0x08) printf("FMPAC %c: 16kB ROM page #%d at %d:%d:4000h\n",I+'A',V>>1,PS,SS); return; case 0x7FF6: /* OPL1 enable/disable? */ if(Verbose&0x08) printf("FMPAC %c: (7FF6h) = %02Xh\n",I+'A',V); V&=0x11; return; case 0x5FFE: /* Write 4Dh, then (5FFFh)=69h to enable SRAM */ case 0x5FFF: /* (5FFEh)=4Dh, then write 69h to enable SRAM */ FMPACKey=A&1? ((FMPACKey&0x00FF)|((int)V<<8)) : ((FMPACKey&0xFF00)|V); P=FMPACKey==FMPAC_MAGIC? SRAMData[I]:(ROMData[I]+((int)ROMMapper[I][0]<<13)); RAM[2]=MemMap[PS][SS][2]=P; RAM[3]=MemMap[PS][SS][3]=P+0x2000; if(Verbose&0x08) printf("FMPAC %c: 8kB SRAM %sabled at %d:%d:4000h\n",I+'A',FMPACKey==FMPAC_MAGIC? "en":"dis",PS,SS); return; } /* Write to SRAM */ if((A>=0x4000)&&(A<0x5FFE)&&(FMPACKey==FMPAC_MAGIC)) { RAM[A>>13][A&0x1FFF]=V; SaveSRAM[I]=1; return; } break; } /* No MegaROM mapper or there is an incorrect write */ if(Verbose&0x08) printf("MEMORY: Bad write (%d:%d:%04Xh) = %02Xh\n",PS,SS,A,V); } /** PSlot() **************************************************/ /** Switch primary memory slots. This function is called **/ /** when value in port A8h changes. **/ /*************************************************************/ void PSlot(register byte V) { register byte J,I; if(PSLReg!=V) for(PSLReg=V,J=0;J<4;++J,V>>=2) { I = J<<1; PSL[J] = V&3; SSL[J] = (SSLReg[PSL[J]]>>I)&3; RAM[I] = MemMap[PSL[J]][SSL[J]][I]; RAM[I+1] = MemMap[PSL[J]][SSL[J]][I+1]; EnWrite[J] = (PSL[J]==3)&&(SSL[J]==2)&&(MemMap[3][2][I]!=EmptyRAM); } } /** SSlot() **************************************************/ /** Switch secondary memory slots. This function is called **/ /** when value in (FFFFh) changes. **/ /*************************************************************/ void SSlot(register byte V) { register byte J,I; /* Cartridge slots do not have subslots, fix them at 0:0:0:0 */ if((PSL[3]==1)||(PSL[3]==2)) V=0x00; /* In MSX1, slot 0 does not have subslots either */ if(!PSL[3]&&((Mode&MSX_MODEL)==MSX_MSX1)) V=0x00; if(SSLReg[PSL[3]]!=V) for(SSLReg[PSL[3]]=V,J=0;J<4;++J,V>>=2) { if(PSL[J]==PSL[3]) { I = J<<1; SSL[J] = V&3; RAM[I] = MemMap[PSL[J]][SSL[J]][I]; RAM[I+1] = MemMap[PSL[J]][SSL[J]][I+1]; EnWrite[J] = (PSL[J]==3)&&(SSL[J]==2)&&(MemMap[3][2][I]!=EmptyRAM); } } } /** SetIRQ() *************************************************/ /** Set or reset IRQ. Returns IRQ vector assigned to **/ /** CPU.IRequest. When upper bit of IRQ is 1, IRQ is reset. **/ /*************************************************************/ word SetIRQ(register byte IRQ) { if(IRQ&0x80) IRQPending&=IRQ; else IRQPending|=IRQ; CPU.IRequest=IRQPending? INT_IRQ:INT_NONE; return(CPU.IRequest); } /** SetScreen() **********************************************/ /** Change screen mode. Returns new screen mode. **/ /*************************************************************/ byte SetScreen(void) { register byte I,J; switch(((VDP[0]&0x0E)>>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;break; } /* Recompute table addresses */ I=(J>6)&&(J!=MAXSCREEN+1)? 11:10; ChrTab = VRAM+((int)(VDP[2]&MSK[J].R2)<=MAXSLOTS)) return; /* Find primary/secondary slots */ for(PS=0;PS<4;++PS) { for(SS=0;(SS<4)&&(CartMap[PS][SS]!=Slot);++SS); if(SS<4) break; } /* Drop out if slots not found */ if(PS>=4) return; /* Apply masks to ROM pages */ P0&=ROMMask[Slot]; P1&=ROMMask[Slot]; P2&=ROMMask[Slot]; P3&=ROMMask[Slot]; /* Set memory map */ MemMap[PS][SS][2]=ROMData[Slot]+P0*0x2000; MemMap[PS][SS][3]=ROMData[Slot]+P1*0x2000; MemMap[PS][SS][4]=ROMData[Slot]+P2*0x2000; MemMap[PS][SS][5]=ROMData[Slot]+P3*0x2000; /* Set ROM mappers */ ROMMapper[Slot][0]=P0; ROMMapper[Slot][1]=P1; ROMMapper[Slot][2]=P2; ROMMapper[Slot][3]=P3; } /** VDPOut() *************************************************/ /** Write value into a given VDP register. **/ /*************************************************************/ void VDPOut(register byte R,register byte V) { register byte J; switch(R) { case 0: /* Reset HBlank interrupt if disabled */ if((VDPStatus[1]&0x01)&&!(V&0x10)) { VDPStatus[1]&=0xFE; SetIRQ(~INT_IE1); } /* Set screen mode */ if(VDP[0]!=V) { VDP[0]=V;SetScreen(); } break; case 1: /* Set/Reset VBlank interrupt if enabled or disabled */ if(VDPStatus[0]&0x80) SetIRQ(V&0x20? INT_IE0:~INT_IE0); /* Set screen mode */ if(VDP[1]!=V) { VDP[1]=V;SetScreen(); } break; case 2: J=(ScrMode>6)&&(ScrMode!=MAXSCREEN+1)? 11:10; ChrTab = VRAM+((int)(V&MSK[ScrMode].R2)<>4;BGColor=V&0x0F;break; case 10: V&=0x07; ColTab=VRAM+((int)(VDP[3]&MSK[ScrMode].R3)<<6)+((int)V<<14); break; case 11: V&=0x03; SprTab=VRAM+((int)(VDP[5]&MSK[ScrMode].R5)<<7)+((int)V<<15); break; case 14: V&=VRAMPages-1;VPAGE=VRAM+((int)V<<14); break; case 15: V&=0x0F;break; case 16: V&=0x0F;PKey=1;break; case 17: V&=0xBF;break; case 25: VDP[25]=V; SetScreen(); break; case 44: VDPWrite(V);break; case 46: VDPDraw(V);break; } /* Write value into a register */ VDP[R]=V; } /** Printer() ************************************************/ /** Send a character to the printer. **/ /*************************************************************/ void Printer(byte V) { if(!PrnStream) { PrnStream = PrnName? fopen(PrnName,"ab"):0; PrnStream = PrnStream? PrnStream:stdout; } fputc(V,PrnStream); } /** PPIOut() *************************************************/ /** This function is called on each write to PPI to make **/ /** key click sound, motor relay clicks, and so on. **/ /*************************************************************/ void PPIOut(register byte New,register byte Old) { /* Keyboard click bit */ if((New^Old)&0x80) Drum(DRM_CLICK,64); /* Motor relay bit */ if((New^Old)&0x10) Drum(DRM_CLICK,255); } /** RTCIn() **************************************************/ /** Read value from a given RTC register. **/ /*************************************************************/ byte RTCIn(register byte R) { static time_t PrevTime; static struct tm TM; register byte J; time_t CurTime; /* Only 16 registers/mode */ R&=0x0F; /* Bank mode 0..3 */ J=RTCMode&0x03; if(R>12) J=R==13? RTCMode:NORAM; else if(J) J=RTC[J][R]; else { /* Retrieve system time if any time passed */ CurTime=time(NULL); if(CurTime!=PrevTime) { TM=*localtime(&CurTime); PrevTime=CurTime; } /* Parse contents of last retrieved TM */ switch(R) { case 0: J=TM.tm_sec%10;break; case 1: J=TM.tm_sec/10;break; case 2: J=TM.tm_min%10;break; case 3: J=TM.tm_min/10;break; case 4: J=TM.tm_hour%10;break; case 5: J=TM.tm_hour/10;break; case 6: J=TM.tm_wday;break; case 7: J=TM.tm_mday%10;break; case 8: J=TM.tm_mday/10;break; case 9: J=(TM.tm_mon+1)%10;break; case 10: J=(TM.tm_mon+1)/10;break; case 11: J=(TM.tm_year-80)%10;break; case 12: J=((TM.tm_year-80)/10)%10;break; default: J=0x0F;break; } } /* Four upper bits are always high */ return(J|0xF0); } /** LoopZ80() ************************************************/ /** Refresh screen, check keyboard and sprites. Call this **/ /** function on each interrupt. **/ /*************************************************************/ word LoopZ80(Z80 *R) { static byte BFlag=0; static byte BCount=0; static int UCount=0; static byte ACount=0; static byte Drawing=0; register int J; /* Flip HRefresh bit */ VDPStatus[2]^=0x20; /* If HRefresh is now in progress... */ if(!(VDPStatus[2]&0x20)) { /* HRefresh takes most of the scanline */ R->IPeriod=!ScrMode||(ScrMode==MAXSCREEN+1)? CPU_H240:CPU_H256; /* New scanline */ ScanLine=ScanLine<(PALVideo? 312:261)? ScanLine+1:0; /* If first scanline of the screen... */ if(!ScanLine) { /* Drawing now... */ Drawing=1; /* Reset VRefresh bit */ VDPStatus[2]&=0xBF; /* Refresh display */ if(UCount>=100) { UCount-=100;RefreshScreen(); } UCount+=UPeriod; /* Blinking for TEXT80 */ if(BCount) BCount--; else { BFlag=!BFlag; if(!VDP[13]) { XFGColor=FGColor;XBGColor=BGColor; } else { BCount=(BFlag? VDP[13]&0x0F:VDP[13]>>4)*10; if(BCount) { if(BFlag) { XFGColor=FGColor;XBGColor=BGColor; } else { XFGColor=VDP[12]>>4;XBGColor=VDP[12]&0x0F; } } } } } /* Line coincidence is active at 0..255 */ /* in PAL and 0..234/244 in NTSC */ J=PALVideo? 256:ScanLines212? 245:235; /* When reaching end of screen, reset line coincidence */ if(ScanLine==J) { VDPStatus[1]&=0xFE; SetIRQ(~INT_IE1); } /* When line coincidence is active... */ if(ScanLineIRequest=IRQPending? INT_IRQ:INT_NONE; return(R->IRequest); } /*********************************/ /* We come here for HBlanks only */ /*********************************/ /* HBlank takes HPeriod-HRefresh */ R->IPeriod=!ScrMode||(ScrMode==MAXSCREEN+1)? CPU_H240:CPU_H256; R->IPeriod=HPeriod-R->IPeriod; /* If last scanline of VBlank, see if we need to wait more */ J=PALVideo? 313:262; if(ScanLine>=J-1) { J*=CPU_HPERIOD; if(VPeriod>J) R->IPeriod+=VPeriod-J; } /* If first scanline of the bottom border... */ if(ScanLine==(ScanLines212? 212:192)) Drawing=0; /* If first scanline of VBlank... */ J=PALVideo? (ScanLines212? 212+42:192+52):(ScanLines212? 212+18:192+28); if(!Drawing&&(ScanLine==J)) { /* Set VBlank bit, set VRefresh bit */ VDPStatus[0]|=0x80; VDPStatus[2]|=0x40; /* Generate VBlank interrupt */ if(VDP[1]&0x20) SetIRQ(INT_IE0); } /* Run V9938 engine */ LoopVDP(); /* Refresh scanline, possibly with the overscan */ if((UCount>=100)&&Drawing&&(ScanLine<256)) { if(!ModeYJK||(ScrMode<7)||(ScrMode>8)) (RefreshLine[ScrMode])(ScanLine); else if(ModeYAE) RefreshLine10(ScanLine); else RefreshLine12(ScanLine); } /* Keyboard, sound, and other stuff always runs at line 192 */ /* This way, it can't be shut off by overscan tricks (Maarten) */ if(ScanLine==192) { /* Check sprites and set Collision, 5Sprites, 5thSprite bits */ if(!SpritesOFF&&ScrMode&&(ScrMode=JOY_MOUSTICK) { /* Get new mouse state */ MouState[0]=Mouse(0); /* Merge mouse buttons into joystick buttons */ JoyState|=(MouState[0]>>12)&0x0030; /* If mouse-as-joystick... */ if(JOYTYPE(0)==JOY_MOUSTICK) { J=MouState[0]&0xFF; JoyState|=J>OldMouseX[0]? 0x0008:J>8)&0xFF; JoyState|=J>OldMouseY[0]? 0x0002:J=JOY_MOUSTICK) { /* Get new mouse state */ MouState[1]=Mouse(1); /* Merge mouse buttons into joystick buttons */ JoyState|=(MouState[1]>>4)&0x3000; /* If mouse-as-joystick... */ if(JOYTYPE(1)==JOY_MOUSTICK) { J=MouState[1]&0xFF; JoyState|=J>OldMouseX[1]? 0x0800:J>8)&0xFF; JoyState|=J>OldMouseY[1]? 0x0200:J3) { /* Autofire spacebar if needed */ if(OPTION(MSX_AUTOSPACE)) KBD_RES(' '); /* Autofire FIRE-A if needed */ if(OPTION(MSX_AUTOFIREA)) JoyState&=~(JST_FIREA|(JST_FIREA<<8)); /* Autofire FIRE-B if needed */ if(OPTION(MSX_AUTOFIREB)) JoyState&=~(JST_FIREB|(JST_FIREB<<8)); } } /* Return whatever interrupt is pending */ R->IRequest=IRQPending? INT_IRQ:INT_NONE; return(R->IRequest); } /** CheckSprites() *******************************************/ /** Check for sprite collisions and 5th/9th sprite in a **/ /** row. **/ /*************************************************************/ void CheckSprites(void) { register word LS,LD; register byte DH,DV,*PS,*PD,*T; byte I,J,N,M,*S,*D; /* Clear 5Sprites, Collision, and 5thSprite bits */ VDPStatus[0]=(VDPStatus[0]&0x9F)|0x1F; for(N=0,S=SprTab;(N<32)&&(S[0]!=208);N++,S+=4); M=SolidColor0; if(Sprites16x16) { for(J=0,S=SprTab;J240)) { DH=S[1]-D[1]; if((DH<16)||(DH>240)) { PS=SprGen+((int)(S[2]&0xFC)<<3); PD=SprGen+((int)(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+((int)S[2]<<3); PD=SprGen+((int)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; } } } } } } /** GuessROM() ***********************************************/ /** Guess MegaROM mapper of a ROM. **/ /*************************************************************/ int GuessROM(const byte *Buf,int Size) { int J,I,K,ROMCount[MAXMAPPERS]; char S[256]; FILE *F; /* Compute ROM's CRC */ for(J=K=0;JROMCount[I]) I=J; /* Return the most likely mapper type */ return(I); } /** StateID() ************************************************/ /** Compute 16bit emulation state ID used to identify .STA **/ /** files. **/ /*************************************************************/ word StateID(void) { word ID; int J,I; ID=0x0000; /* Add up cartridge ROMs, BIOS, BASIC, ExtBIOS, and DiskBIOS bytes */ for(I=0;I=MAXDRIVES) return(0); /* Reset FDC, in case it was running a command */ Reset1793(&FDC,FDD,WD1793_KEEP); /* Eject disk if requested */ if(!FileName) { EjectFDI(&FDD[N]);return(1); } /* If FileName not empty, try loading disk image */ if(*FileName&&LoadFDI(&FDD[N],FileName,FMT_AUTO)) return(1); /* * Failed to open as a plain file */ /* Create a new 720kB disk image */ P = NewFDI(&FDD[N], DSK_SIDS_PER_DISK, DSK_TRKS_PER_SIDE, DSK_SECS_PER_TRCK, DSK_SECTOR_SIZE ); /* If FileName not empty, treat it as directory, otherwise new disk */ if(P&&!(*FileName? DSKLoad(FileName,P):DSKCreate(P))) { EjectFDI(&FDD[N]);return(0); } /* Done */ return(!!P); } /** LoadFile() ***********************************************/ /** Simple utility function to load cartridge, state, font **/ /** or a disk image, based on the file extension, etc. **/ /*************************************************************/ int LoadFile(const char *FileName) { const char *T; int J; /* Find file extension */ T = strrchr(FileName,'\\'); T = T? T:strrchr(FileName,'/'); T = T? T+1:FileName; if(!(T=strchr(T,'.'))) return(0); /* Try loading as a disk */ if(!stricmpn(T,".DSK",4)||!stricmpn(T,".FDI",4)) { /* Change disk image in drive A: */ if(!ChangeDisk(0,FileName)) return(0); /* Eject all user cartridges if successful */ for(J=0;J/ notation */ if(!(ArchivedName=strrchr(ZipName,'/'))) { free(ZipName); return(0); } *ArchivedName='\0'; ArchivedName++; /* Assume it's a ZIP file and try opening it */ if(!(ZF=unzOpen(ZipName))) { free(ZipName); return(0); } /* Locate the requested file in the ZIP archive */ if (unzLocateFile(ZF,ArchivedName,0)!=UNZ_OK) { free(ZipName); unzClose(ZF); return(0); } free(ZipName); } #else if(!F) return(0); #endif /* Determine data size, if wasn't given */ if(!Size) { #ifdef MINIZIP if(ZF) { unz_file_info FI; /* Get decompressed file size */ if(unzGetCurrentFileInfo(ZF,&FI,NULL,0,NULL,0,NULL,0)!=UNZ_OK) { unzClose(ZF); return(0); } Size=FI.uncompressed_size; } else #endif /* Determine size via ftell() or by reading entire [GZIPped] stream */ if(!fseek(F,0,SEEK_END)) Size=ftell(F); else { /* Read file in 16kB increments */ while((J=fread(EmptyRAM,1,0x4000,F))==0x4000) Size+=J; if(J>0) Size+=J; /* Clean up the EmptyRAM! */ memset(EmptyRAM,NORAM,0x4000); } #ifndef MINIZIP if(!ZF) #endif /* Rewind file to the beginning */ rewind(F); } /* Allocate memory */ P=Buf? Buf:GetMemory(Size); if(!P) { #ifdef MINIZIP if(ZF) unzClose(ZF); else #endif fclose(F); return(0); } /* Read data */ #ifdef MINIZIP if(ZF) { /* Open archived file for reading */ if(unzOpenCurrentFile(ZF)!=UNZ_OK) { if(!Buf) FreeMemory(P); unzClose(ZF); return(0); } /* Read */ J=unzReadCurrentFile(ZF,P,Size); /* Close archived file */ unzCloseCurrentFile(ZF); if(J!=Size) { if(!Buf) FreeMemory(P); unzClose(ZF); return(0); } } else #endif if((J=fread(P,1,Size,F))!=Size) { if(!Buf) FreeMemory(P); fclose(F); return(0); } /* Done */ #ifdef MINIZIP if(ZF) unzClose(ZF); else #endif fclose(F); return(P); } /** LoadCart() ***********************************************/ /** Load cartridge into given slot. Returns cartridge size **/ /** in 16kB pages on success, 0 on failure. **/ /*************************************************************/ int LoadCart(const char *FileName,int Slot,int Type) { int C1,C2,Len,Pages,ROM64; byte *P,PS,SS; FILE *F; /* Slot number must be valid */ if((Slot<0)||(Slot>=MAXSLOTS)) return(0); /* Find primary/secondary slots */ for(PS=0;PS<4;++PS) { for(SS=0;(SS<4)&&(CartMap[PS][SS]!=Slot);++SS); if(SS<4) break; } /* Drop out if slots not found */ if(PS>=4) return(0); /* If there is a SRAM in this cartridge slot... */ if(SRAMData[Slot]&&SaveSRAM[Slot]&&SRAMName[Slot]) { /* Open .SAV file */ if(Verbose) printf("Writing %s...",SRAMName[Slot]); if(!(F=fopen(SRAMName[Slot],"wb"))) SaveSRAM[Slot]=0; else { /* Write .SAV file */ switch(ROMType[Slot]) { case MAP_ASCII8: case MAP_FMPAC: if(fwrite(SRAMData[Slot],1,0x2000,F)!=0x2000) SaveSRAM[Slot]=0; break; case MAP_ASCII16: if(fwrite(SRAMData[Slot],1,0x0800,F)!=0x0800) SaveSRAM[Slot]=0; break; case MAP_GMASTER2: if(fwrite(SRAMData[Slot],1,0x1000,F)!=0x1000) SaveSRAM[Slot]=0; if(fwrite(SRAMData[Slot]+0x2000,1,0x1000,F)!=0x1000) SaveSRAM[Slot]=0; break; } /* Done with .SAV file */ fclose(F); } /* Done saving SRAM */ PRINTRESULT(SaveSRAM[Slot]); } /* If ejecting cartridge... */ if(!FileName) { /* Free memory if present */ FreeMemory(ROMData[Slot]); ROMData[Slot] = 0; ROMMask[Slot] = 0; /* Set memory map to dummy RAM */ for(C1=0;C1<8;++C1) MemMap[PS][SS][C1]=EmptyRAM; /* Restart MSX */ ResetMSX(Mode,RAMPages,VRAMPages); /* Cartridge ejected */ if(Verbose) printf("Ejected cartridge from slot %c\n",Slot+'A'); return(0); } /* Try opening file */ F=fopen(FileName,"rb"); #ifdef MINIZIP unzFile ZF=NULL; if(!F) { /* Name is not an actual file: maybe it's a file inside a ZIP file? */ char *ZipName=strdup(FileName); char *ArchivedName; /* Check if Name is in / notation */ if(!(ArchivedName=strrchr(ZipName,'/'))) { free(ZipName); return(0); } *ArchivedName='\0'; ArchivedName++; /* Assume it's a ZIP file and try opening it */ if(!(ZF=unzOpen(ZipName))) { free(ZipName); return(0); } /* Locate the requested file in the ZIP archive */ if (unzLocateFile(ZF,ArchivedName,0)!=UNZ_OK) { free(ZipName); unzClose(ZF); return(0); } free(ZipName); } #else if(!F) return(0); #endif if(Verbose) printf("Found %s:\n",FileName); #ifdef MINIZIP if(ZF) { unz_file_info FI; /* Get decompressed file size */ if(unzGetCurrentFileInfo(ZF,&FI,NULL,0,NULL,0,NULL,0)!=UNZ_OK) { unzClose(ZF); return(0); } Len=FI.uncompressed_size; } else #endif /* Determine size via ftell() or by reading entire [GZIPped] stream */ if(!fseek(F,0,SEEK_END)) Len=ftell(F); else { /* Read file in 16kB increments */ for(Len=0;(C2=fread(EmptyRAM,1,0x4000,F))==0x4000;Len+=C2); if(C2>0) Len+=C2; /* Clean up the EmptyRAM! */ memset(EmptyRAM,NORAM,0x4000); } #ifndef MINIZIP if(!ZF) #endif /* Rewind file */ rewind(F); /* Compute size in 8kB pages */ Len>>=13; /* Calculate 2^n closest to number of pages */ for(Pages=1;Pages=0) { C1=fgetc(F); C2=fgetc(F); ROM64=(C1=='A')&&(C2=='B'); } } /* Maybe it is the last page that contains "AB" signature? */ if((Len>=2)&&((C1!='A')||(C2!='B'))) { #ifdef MINIZIP if(ZF) { int I; C1=C2=0; /* There's currently no fseek-type function for libunzip */ /* Read the unneeded portion into a temp. area */ unzOpenCurrentFile(ZF); for(I=0;I=0) { C1=fgetc(F); C2=fgetc(F); } } /* Done with the file */ #ifdef MINIZIP if(ZF) unzClose(ZF); else #endif fclose(F); /* If we can't find "AB" signature, drop out */ if((C1!='A')||(C2!='B')) { if(Verbose) puts(" Not a valid cartridge ROM"); return(0); } if(Verbose) printf(" Cartridge %c: ",'A'+Slot); /* Show ROM type and size */ if(Verbose) printf ( "%dkB %s ROM..",Len*8, ROM64||(Len<=4)? "NORMAL":Type>=MAP_GUESS? "UNKNOWN":ROMNames[Type] ); /* Assign ROMMask for MegaROMs */ ROMMask[Slot]=!ROM64&&(Len>4)? (Pages-1):0x00; /* Allocate space for the ROM */ ROMData[Slot]=GetMemory(Pages<<13); if(!ROMData[Slot]) { PRINTFAILED;return(0); } /* Try loading ROM */ if(!LoadROM(FileName,Len<<13,ROMData[Slot])) { PRINTFAILED;return(0); } /* Mirror ROM if it is smaller than 2^n pages */ if(Len=MAP_GUESS)&&(ROMMask[Slot]+1>4)) { Type=GuessROM(ROMData[Slot],0x2000*(ROMMask[Slot]+1)); if(Verbose) printf("guessed %s..",ROMNames[Type]); if(Slot4)) SetMegaROM(Slot,0,1,ROMMask[Slot]-1,ROMMask[Slot]); /* If cartridge may need a SRAM... */ if(MAP_SRAM(Type)) { /* Free previous SRAM resources */ FreeMemory(SRAMData[Slot]); FreeMemory(SRAMName[Slot]); /* Get SRAM memory */ SRAMData[Slot]=GetMemory(0x4000); if(!SRAMData[Slot]) { if(Verbose) printf("scratch SRAM.."); SRAMData[Slot]=EmptyRAM; } else { if(Verbose) printf("got 16kB SRAM.."); memset(SRAMData[Slot],NORAM,0x4000); } /* Generate SRAM file name and load SRAM contents */ if(SRAMName[Slot]=GetMemory(strlen(FileName)+5)) { /* Compose SRAM file name */ strcpy(SRAMName[Slot],FileName); P=strrchr(SRAMName[Slot],'.'); if(P) strcpy(P,".sav"); else strcat(SRAMName[Slot],".sav"); /* Try opening file... */ if(F=fopen(SRAMName[Slot],"rb")) { /* Read SRAM file */ Len=fread(SRAMData[Slot],1,0x4000,F); fclose(F); /* Print information if needed */ if(Verbose) printf("loaded %d bytes from %s..",Len,SRAMName[Slot]); /* Mirror data according to the mapper type */ P=SRAMData[Slot]; switch(Type) { case MAP_FMPAC: memset(P+0x2000,NORAM,0x2000); P[0x1FFE]=FMPAC_MAGIC&0xFF; P[0x1FFF]=FMPAC_MAGIC>>8; break; case MAP_GMASTER2: memcpy(P+0x2000,P+0x1000,0x1000); memcpy(P+0x3000,P+0x1000,0x1000); memcpy(P+0x1000,P,0x1000); break; case MAP_ASCII16: memcpy(P+0x0800,P,0x0800); memcpy(P+0x1000,P,0x0800); memcpy(P+0x1800,P,0x0800); memcpy(P+0x2000,P,0x0800); memcpy(P+0x2800,P,0x0800); memcpy(P+0x3000,P,0x0800); memcpy(P+0x3800,P,0x0800); break; } } } } /* Done setting up cartridge */ ResetMSX(Mode,RAMPages,VRAMPages); PRINTOK; /* If first used user slot... */ if(!Slot||((Slot==1)&&!ROMData[0])) { /* Remove old state name */ FreeMemory((char *)STAName); /* If memory for STAName gets allocated... */ if(STAName=GetMemory(strlen(FileName)+5)) { /* Copy name */ strcpy((char *)STAName,FileName); /* Find extension */ P=strrchr(STAName,'.'); /* Either replace extension with ".sta" or add ".sta" */ if(P) strcpy(P,".sta"); else strcat((char *)STAName,".sta"); /* Try loading state */ if(Verbose) printf("Loading state from %s...",STAName); C1=LoadSTA(STAName); PRINTRESULT(C1); } } /* Done loading cartridge */ return(Pages); } /** SaveSTA() ************************************************/ /** Save emulation state to a .STA file. **/ /*************************************************************/ int SaveSTA(const char *FileName) { static byte Header[16] = "STE\032\003\0\0\0\0\0\0\0\0\0\0\0"; unsigned int State[256],J,I,K; FILE *F; /* Open state file */ if(!(F=fopen(FileName,"wb"))) return(0); /* Prepare the header */ J=StateID(); Header[5] = RAMPages; Header[6] = VRAMPages; Header[7] = J&0x00FF; Header[8] = J>>8; /* Write out the header */ if(fwrite(Header,1,sizeof(Header),F)!=sizeof(Header)) { fclose(F);return(0); } /* Fill out hardware state */ J=0; State[J++] = VDPData; State[J++] = PLatch; State[J++] = ALatch; State[J++] = VAddr; State[J++] = VKey; State[J++] = PKey; State[J++] = WKey; State[J++] = IRQPending; State[J++] = ScanLine; State[J++] = RTCReg; State[J++] = RTCMode; State[J++] = KanLetter; State[J++] = KanCount; State[J++] = IOReg; State[J++] = PSLReg; State[J++] = FMPACKey; /* Memory setup */ for(I=0;I<4;++I) { State[J++] = SSLReg[I]; State[J++] = PSL[I]; State[J++] = SSL[I]; State[J++] = EnWrite[I]; State[J++] = RAMMapper[I]; } /* Cartridge setup */ for(I=0;I>16)&0xFF,(Palette[I]>>8)&0xFF,Palette[I]&0xFF); /* Set screen mode and VRAM table addresses */ SetScreen(); /* Set some other variables */ VPAGE = VRAM+((int)VDP[14]<<14); FGColor = VDP[7]>>4; BGColor = VDP[7]&0x0F; XFGColor = FGColor; XBGColor = BGColor; /* All sound channels could have been changed */ PSG.Changed = (1<