// Integer typestypedef unsigned char u8;// Bitfield utilitiestemplate<unsigned bitno, unsigned nbits=1, typename T=u8>struct RegBit{ T data; enum { mask = (1u << nbits) - 1u }; template<typename T2> RegBit& operator=(T2 val) { data = (data & ~(mask << bitno)) | ((nbits > 1 ? val & mask : !!val) << bitno); return *this; } operator unsigned() const { return (data >> bitno) & mask; }};// FDS EMULATIONnamespace GamePak{ /**************************************************/ /*** FDS timer IRQ variables: ***/ /**************************************************/ // Are timer IRQs enabled? bool FDS_TimerIRQ_Enable = false; // Which value will be reloded upon write to $4022 unsigned FDS_IRQ_ReloadValue = 0; // Number of CPU cycles remaining until next FDS timer IRQ unsigned FDS_IRQ_Counter = 0; /**************************************************/ /*** FDS disk drive variables ***/ /**************************************************/ // Current side of the disk. Used internally. u8 FDS_CurrentDiskSide = 0; // Current position on the current side of the disk (0 = first byte) unsigned FDS_Position = 0; // Number of CPU cycles remaining until our virtual disk drive // manages to transfer the next byte to/from the disk. unsigned FDS_Delay = 0; // CRC sum. unsigned FDS_CRC = 0; // Last value written to $4021 (byte to be written to disk) u8 FDS_WriteData = 0; // Last value written to $4023 (register enable mask) u8 FDS_RegEnable = 3; // Last value written to $4025 union { u8 raw; RegBit<0,1,u8> MotorOn; // Bit 0 (&0x01) RegBit<1,1,u8> Bit1; // Bit 1 (&0x02) RegBit<2,1,u8> Reading; // Bit 2 (&0x04) RegBit<4,1,u8> CRCmode; // Bit 3 (&0x08) RegBit<6,1,u8> Bit6; // Bit 6 (&0x40) RegBit<7,1,u8> DiskIRQ_Enable; // Bit 7 (&0x80) } FDS_Reg4025; // Last value written to $4026 (only used for bitmasking $4033) u8 FDS_Reg4026 = 0xFF; // Next value to be read from $4031 (byte read from disk) u8 FDS_ReadData = 0; // Whether the FDS has noticed that disk is inserted. bool FDS_DiskReady = true; // bool FDS_TransferComplete = false; bool FDS_GapFound = false; /**************************************************/ /* FDS I/O function. * Called whenever the CPU reads or writes $4020..$403F. * For brevity of this code snipped, assume that no * writes/reads outside that region ever happen. * Sound emulation is omitted for brevity. */ /**************************************************/ u8 FDS_Register_Access(unsigned addr, u8 value, bool write) { switch( (addr & 0xFF) | (write ? 0x100 : 0x000) ) { /*** WRITE PORTS (4020..4026) ***/ case 0x120: // 4020: Write low 8 bits of timer IRQ reload value CPU::intr.FDS_TIMER_IRQ = false; // Untrigger IRQ FDS_IRQ_ReloadValue = (FDS_IRQ_ReloadValue & 0xFF00) | value; break; case 0x121: // 4021: Write high 8 bits of timer IRQ reload value CPU::intr.FDS_TIMER_IRQ = false; // Untrigger IRQ FDS_IRQ_ReloadValue = (FDS_IRQ_ReloadValue & 0x00FF) | (value << 8); break; case 0x122: // 4022: Reload and enable/disable timer IRQ CPU::intr.FDS_TIMER_IRQ = false; // Untrigger IRQ FDS_IRQ_Counter = FDS_IRQ_ReloadValue; FDS_TimerIRQ_Enable = value & 2; break; case 0x123: // 4023: Set register write protection (TODO: handle this) FDS_RegEnable = value; break; case 0x124: // 4024: Set next byte to be written to disk. FDS_WriteData = value; FDS_TransferComplete = false; // Confirm any pending disk IRQ. CPU::intr.FDS_DISK_IRQ = false; break; case 0x125: // 4025: Control the drive. if(!(value & 0x50) && FDS_Reg4025.Bit6) { /* FCEU does this. I have no idea really why. * My intuition says it is WRONG, but removing this * makes disksys.rom unable to load games. */ if(FDS_Position > 0) FDS_Position -= 2; } // Store the new flags. FDS_Reg4025.raw = value; // If Bit 6 was stored as "1", reset the CRC. if(FDS_Reg4025.Bit6) FDS_CRC = 0; // Reload the disk access delay. This is probably WRONG. FDS_Delay = 150; // Set PPU mirroring (for some reason, horiz/vert seems to be reversed) SetMirroring( (value&8) ? 0x1100 : 0x1010 ); break; case 0x126: // 4026: Write the bitmask that only controls what $4033 returns. FDS_Reg4026 = value; break; /** READ PORTS (4030..4033) **/ case 0x030: // 4030: return status and confirm any IRQ. { unsigned v = (CPU::intr.FDS_TIMER_IRQ ? 1 : 0) | (FDS_TransferComplete ? 2 : 0) //| (FDS_DiskReady ? 0x80 : 0x00) | (FDS_DiskReady ? 0x00 : 0x40) ; // Confirm any IRQ (both timer and disk) CPU::intr.FDS_TIMER_IRQ = false; CPU::intr.FDS_DISK_IRQ = false; // Reset $4030 bit 1 (& 0x02). FDS_TransferComplete = false; return v; } case 0x031: // 4031: Return what was read from the disk // Confirm the FDS disk transfer IRQ CPU::intr.FDS_DISK_IRQ = false; // Reset $4030 bit 1 (& 0x02) FDS_TransferComplete = false; // Return the databyte last fetched from disk return FDS_ReadData; case 0x032: // 4032: return more status. // If disk is not inserted, assert bits 0 and 2 // If disk was not yet detected, assert bits 1 and 2 // If motor is not running, assert bit 1 // If $4025 bit 1 is set, assert bit 1 // If disk is write protected, assert bit 2 // Always assert bit 6 return ((FDS_CurrentDiskSide != 0xFF) ? 0 : 5) | (FDS_DiskReady ? 0 : 6) | (FDS_Reg4025.MotorOn ? 0 : 2) | (FDS_Reg4025.Bit1 ? 2 : 0) // | 0x04 // Write protected | 0x40 // constant ; case 0x033: // 4033: Return battery status. return 0x80 & FDS_Reg4026; } return 0; // dummy return } /******************************************************/ /* Tick(): This function is called at each CPU cycle. */ /******************************************************/ void tick() { /* If FDS timer IRQ is enabled and the counter is nonzero, * decrement the counter. If it becomes zero, * assert the IRQ line and disable the counter. */ if(FDS_TimerIRQ_Enable && FDS_IRQ_Counter && !--FDS_IRQ_Counter) { FDS_TimerIRQ_Enable = false; CPU::intr.FDS_TIMER_IRQ = true; } /* If there is no disk inserted, the disk system does no activity. */ if(FDS_CurrentDiskSide == 0xFF) { return; } /* So, disk is in. Have we detected it yet? */ if(!FDS_DiskReady) { // Nope. Can we detect it? Detect it when the delay becomes zero. if(FDS_Delay > 0) --FDS_Delay; if(!FDS_Delay) FDS_DiskReady = true; } /* If we have not detected it yet, the disk system does no activity. */ if(!FDS_DiskReady) return; /* So, disk is inside and detected. */ /* Is position-reset requested? ($4025 bit 1) */ if(FDS_Reg4025.Bit1) { // Reset position and do nothing else. FDS_Position = 0; FDS_GapFound = false; return; } /* If the motor is not running, do nothing else. */ if(!FDS_Reg4025.MotorOn) { return; } /* Is the disk system ready with its next activity? */ if(FDS_Delay > 0) --FDS_Delay; // Elapse the cycle delay counter if(FDS_Delay > 0) return; /* It is ready. */ /* Which byte of the disk-image is the disk head currently over? */ unsigned pos = 65500 * FDS_CurrentDiskSide + FDS_Position; bool got = false; /* If bit $4025 bit 6 is set and we have not yet signalled * the first byte of the disk, do it now. */ if(FDS_Reg4025.Bit6 && !FDS_GapFound) { FDS_GapFound = true; /* Read data from disk. */ FDS_ReadData = DiskImage[pos]; got = true; /* Advance the reading position only if IRQ is enabled. * PROBABLY WRONG, but removing this condition makes * disksys.rom print error codes. */ if(FDS_Reg4025.DiskIRQ_Enable) FDS_Position++; } else { /* So we're not _trying_ to find the first byte of the disk. * If we have already found it, process the next data byte. */ if(FDS_GapFound) { /* Transfer the data from/to disk. */ if(FDS_Reg4025.Reading) FDS_ReadData = DiskImage[pos]; else DiskImage[pos] = (FDS_Reg4025.Bit6 ? FDS_WriteData : 0x00); got = true; /* Advance the reading position only if IRQ is enabled. * PROBABLY WRONG, but removing this condition makes * disksys.rom print error codes. */ if(FDS_Reg4025.DiskIRQ_Enable) FDS_Position++; /* If we reached the end of the disk side, set $4025 bit 1 * so that we return to the beginning of the disk at next cycle. * Probably WRONG. */ if(FDS_Position >= 65500) FDS_Reg4025.Bit1 = 1; } } /* Did we successfully transfer a byte from/to the disk? */ if(got) { /* We did. Set $4030 bit 1. */ FDS_TransferComplete = true; /* Also assert the disk IRQ, if permitted. */ if(FDS_Reg4025.DiskIRQ_Enable) CPU::intr.FDS_DISK_IRQ = true; } /* After 150 CPU cycles, we'll consider the situation again */ FDS_Delay = 150; } /* This function "inserts" a disk. Called from User Interface. */ void FDS_InsertSide(unsigned side) { FDS_CurrentDiskSide = side; FDS_Delay = 1000; // Random constant. About 1000 cycles to detect the disk is in. } /* This function "ejects" the disk. Called from User Interface. */ void FDS_Eject() { FDS_CurrentDiskSide = 0xFF; FDS_DiskReady = false; FDS_Delay = 0; FDS_Reg4025.MotorOn = false; FDS_GapFound = false; }}