2012-10-21 15:58:24 +00:00
using System ;
using System.Linq ;
using System.Text ;
2012-10-27 14:01:55 +00:00
using System.IO ;
2013-11-04 01:39:19 +00:00
2013-11-04 00:36:15 +00:00
using BizHawk.Common ;
2013-11-04 01:39:19 +00:00
using BizHawk.Emulation.Common ;
2012-10-21 15:58:24 +00:00
2013-11-14 13:15:41 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.NES
2012-10-21 15:58:24 +00:00
{
2012-10-22 02:50:43 +00:00
/ *
* http : //sourceforge.net/p/fceultra/code/2696/tree/fceu/src/fds.cpp - only used for timer info
* http : //nesdev.com/FDS%20technical%20reference.txt - implementation is mostly a combination of
* http : //wiki.nesdev.com/w/index.php/Family_Computer_Disk_System - these two documents
* http : //nesdev.com/diskspec.txt - not useless
* /
2012-10-21 15:58:24 +00:00
[NES.INESBoardImplCancel]
public class FDS : NES . NESBoardBase
{
2012-10-28 15:13:56 +00:00
#region configuration
/// <summary>FDS bios image; should be 8192 bytes</summary>
2012-10-21 15:58:24 +00:00
public byte [ ] biosrom ;
2012-10-28 15:13:56 +00:00
/// <summary>.FDS disk image</summary>
2012-10-27 14:01:55 +00:00
byte [ ] diskimage ;
2012-10-28 15:13:56 +00:00
#endregion
#region state
RamAdapter diskdrive ;
FDSAudio audio ;
/// <summary>currently loaded side of the .FDS image, 0 based</summary>
int? currentside = null ;
/// <summary>collection of diffs (as provided by the RamAdapter) for each side in the .FDS image</summary>
byte [ ] [ ] diskdiffs ;
bool _diskirq ;
bool _timerirq ;
/// <summary>disk io ports enabled; see 4023.0</summary>
bool diskenable = false ;
/// <summary>sound io ports enabled; see 4023.1</summary>
bool soundenable = false ;
/// <summary>read on 4033, write on 4026</summary>
byte reg4026 ;
/// <summary>timer reload</summary>
int timerlatch ;
/// <summary>timer current value</summary>
int timervalue ;
/// <summary>4022.0,1</summary>
byte timerreg ;
#endregion
public override void SyncState ( Serializer ser )
{
base . SyncState ( ser ) ;
ser . BeginSection ( "FDS" ) ;
ser . BeginSection ( "RamAdapter" ) ;
diskdrive . SyncState ( ser ) ;
ser . EndSection ( ) ;
ser . BeginSection ( "audio" ) ;
audio . SyncState ( ser ) ;
ser . EndSection ( ) ;
{
// silly little hack
int tmp = currentside ! = null ? ( int ) currentside : 1234567 ;
ser . Sync ( "currentside" , ref tmp ) ;
currentside = tmp = = 1234567 ? null : ( int? ) tmp ;
}
for ( int i = 0 ; i < NumSides ; i + + )
2012-11-26 21:27:54 +00:00
ser . Sync ( "diskdiffs" + i , ref diskdiffs [ i ] , true ) ;
2012-10-28 15:13:56 +00:00
ser . Sync ( "_timerirq" , ref _timerirq ) ;
ser . Sync ( "_diskirq" , ref _diskirq ) ;
ser . Sync ( "diskenable" , ref diskenable ) ;
ser . Sync ( "soundenable" , ref soundenable ) ;
ser . Sync ( "reg4026" , ref reg4026 ) ;
ser . Sync ( "timerlatch" , ref timerlatch ) ;
ser . Sync ( "timervalue" , ref timervalue ) ;
ser . Sync ( "timerreg" , ref timerreg ) ;
ser . EndSection ( ) ;
SetIRQ ( ) ;
}
2012-11-26 21:27:54 +00:00
public void SetDriveLightCallback ( Action < bool > callback )
{
diskdrive . DriveLightCallback = callback ;
}
2012-10-27 14:01:55 +00:00
/// <summary>
/// should only be called once, before emulation begins
/// </summary>
/// <param name="diskimage"></param>
public void SetDiskImage ( byte [ ] diskimage )
{
2013-12-08 21:39:17 +00:00
// each FDS format is worse than the last
if ( diskimage . Take ( 4 ) . SequenceEqual ( System . Text . Encoding . ASCII . GetBytes ( "\x01*NI" ) ) )
{
int nsides = diskimage . Length / 65500 ;
MemoryStream ms = new MemoryStream ( ) ;
ms . Write ( System . Text . Encoding . ASCII . GetBytes ( "FDS\x1A" ) , 0 , 4 ) ;
ms . WriteByte ( ( byte ) nsides ) ;
byte [ ] nulls = new byte [ 11 ] ;
ms . Write ( nulls , 0 , 11 ) ;
ms . Write ( diskimage , 0 , diskimage . Length ) ;
ms . Close ( ) ;
diskimage = ms . ToArray ( ) ;
}
2012-10-27 14:01:55 +00:00
this . diskimage = diskimage ;
diskdiffs = new byte [ NumSides ] [ ] ;
}
2012-11-06 03:05:43 +00:00
/// <summary>
/// returns the currently set disk image. no effect on emulation (provided the image is not modified).
/// </summary>
/// <returns></returns>
public byte [ ] GetDiskImage ( )
{
return diskimage ;
}
2012-10-21 15:58:24 +00:00
// as we have [INESBoardImplCancel], this will only be called with an fds disk image
public override bool Configure ( NES . EDetectionOrigin origin )
{
if ( biosrom = = null | | biosrom . Length ! = 8192 )
2014-07-31 22:06:11 +00:00
throw new MissingFirmwareException ( "FDS bios image needed!" ) ;
2012-10-21 15:58:24 +00:00
Cart . vram_size = 8 ;
Cart . wram_size = 32 ;
Cart . wram_battery = false ;
2012-10-31 18:25:46 +00:00
Cart . system = "Famicom" ;
2012-10-21 15:58:24 +00:00
Cart . board_type = "FAMICOM_DISK_SYSTEM" ;
2012-10-21 19:22:22 +00:00
diskdrive = new RamAdapter ( ) ;
2012-12-15 16:51:04 +00:00
if ( NES . apu ! = null )
{
//audio = new FDSAudio(NES.cpuclockrate);
audio = new FDSAudio ( NES . apu . ExternalQueue ) ;
}
2012-10-21 19:22:22 +00:00
InsertSide ( 0 ) ;
2012-10-28 15:13:56 +00:00
// set mirroring??
2012-10-21 15:58:24 +00:00
return true ;
}
2012-10-27 14:01:55 +00:00
// with a bit of change, these methods could work with a better disk format
2012-10-28 15:13:56 +00:00
public int NumSides { get { return diskimage [ 4 ] ; } }
2012-10-21 19:22:22 +00:00
public void Eject ( )
{
2012-10-27 14:01:55 +00:00
if ( currentside ! = null )
{
diskdiffs [ ( int ) currentside ] = diskdrive . MakeDiff ( ) ;
diskdrive . Eject ( ) ;
currentside = null ;
}
2012-10-21 19:22:22 +00:00
}
public void InsertSide ( int side )
{
2012-10-28 15:13:56 +00:00
if ( side > = NumSides )
throw new ArgumentOutOfRangeException ( ) ;
2012-10-21 19:22:22 +00:00
byte [ ] buf = new byte [ 65500 ] ;
Buffer . BlockCopy ( diskimage , 16 + side * 65500 , buf , 0 , 65500 ) ;
2012-10-26 21:25:20 +00:00
diskdrive . InsertBrokenImage ( buf , false /*true*/ ) ;
2012-11-26 21:27:54 +00:00
if ( diskdiffs [ side ] ! = null & & diskdiffs [ side ] . Length > 0 )
2012-10-27 14:01:55 +00:00
diskdrive . ApplyDiff ( diskdiffs [ side ] ) ;
currentside = side ;
}
public byte [ ] ReadSaveRam ( )
{
// update diff for currently loaded disk first!
if ( currentside ! = null )
diskdiffs [ ( int ) currentside ] = diskdrive . MakeDiff ( ) ;
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
bw . Write ( Encoding . ASCII . GetBytes ( "FDSS" ) ) ;
bw . Write ( NumSides ) ;
for ( int i = 0 ; i < NumSides ; i + + )
{
if ( diskdiffs [ i ] ! = null )
{
bw . Write ( diskdiffs [ i ] . Length ) ;
bw . Write ( diskdiffs [ i ] ) ;
}
else
{
bw . Write ( ( int ) 0 ) ;
}
}
bw . Close ( ) ;
return ms . ToArray ( ) ;
2012-10-26 21:25:20 +00:00
}
2012-10-27 14:01:55 +00:00
public void StoreSaveRam ( byte [ ] data )
{
// it's strange to modify a disk that's in the process of being read.
// but in fact, StoreSaveRam() is only called once right at startup, so this is no big deal
//if (currentside != null)
// throw new Exception("FDS Saveram: Can't load when a disk is active!");
MemoryStream ms = new MemoryStream ( data , false ) ;
BinaryReader br = new BinaryReader ( ms ) ;
byte [ ] cmp = Encoding . ASCII . GetBytes ( "FDSS" ) ;
byte [ ] tmp = br . ReadBytes ( cmp . Length ) ;
if ( ! cmp . SequenceEqual ( tmp ) )
throw new Exception ( "FDS Saveram: bad header" ) ;
int n = br . ReadInt32 ( ) ;
if ( n ! = NumSides )
throw new Exception ( "FDS Saveram: wrong number of sides" ) ;
for ( int i = 0 ; i < NumSides ; i + + )
{
int l = br . ReadInt32 ( ) ;
if ( l > 0 )
diskdiffs [ i ] = br . ReadBytes ( l ) ;
else
diskdiffs [ i ] = null ;
}
if ( currentside ! = null & & diskdiffs [ ( int ) currentside ] ! = null )
diskdrive . ApplyDiff ( diskdiffs [ ( int ) currentside ] ) ;
}
public override byte [ ] SaveRam
{ get { throw new Exception ( "FDS Saveram: Must access with method api!" ) ; } }
2012-10-26 21:25:20 +00:00
public MemoryDomain GetDiskPeeker ( )
{
2015-01-18 15:25:47 +00:00
return new MemoryDomain ( "FDS SIDE" , diskdrive . NumBytes , MemoryDomain . Endian . Little , diskdrive . PeekData , null ) ; // silent poke fail TODO: don't pass null! Throw a not implemented exception, handle this exception in ram tools
2012-10-21 19:22:22 +00:00
}
void SetIRQ ( )
{
IRQSignal = _diskirq | | _timerirq ;
}
bool diskirq { get { return _diskirq ; } set { _diskirq = value ; SetIRQ ( ) ; } }
bool timerirq { get { return _timerirq ; } set { _timerirq = value ; SetIRQ ( ) ; } }
public override void WriteEXP ( int addr , byte value )
{
2012-10-26 21:25:20 +00:00
//if (addr == 0x0025)
// Console.WriteLine("W{0:x4}:{1:x2} {2:x4}", addr + 0x4000, value, NES.cpu.PC);
2012-10-22 00:57:28 +00:00
2012-10-22 16:10:19 +00:00
if ( addr > = 0x0040 )
{
audio . WriteReg ( addr + 0x4000 , value ) ;
return ;
}
2012-10-21 19:22:22 +00:00
switch ( addr )
{
case 0x0020 :
timerlatch & = 0xff00 ;
timerlatch | = value ;
timerirq = false ;
break ;
case 0x0021 :
2012-10-22 02:50:43 +00:00
timerlatch & = 0x00ff ;
2012-10-21 19:22:22 +00:00
timerlatch | = value < < 8 ;
timerirq = false ;
break ;
case 0x0022 :
2012-10-22 02:50:43 +00:00
timerreg = ( byte ) ( value & 3 ) ;
2012-10-31 14:36:43 +00:00
timervalue = timerlatch ;
2012-10-21 19:22:22 +00:00
break ;
case 0x0023 :
diskenable = ( value & 1 ) ! = 0 ;
soundenable = ( value & 2 ) ! = 0 ;
break ;
case 0x0024 :
if ( diskenable )
diskdrive . Write4024 ( value ) ;
break ;
case 0x0025 :
if ( diskenable )
diskdrive . Write4025 ( value ) ;
2012-10-22 00:57:28 +00:00
SetMirrorType ( ( value & 8 ) = = 0 ? EMirrorType . Vertical : EMirrorType . Horizontal ) ;
2012-10-21 19:22:22 +00:00
break ;
case 0x0026 :
if ( diskenable )
reg4026 = value ;
break ;
}
diskirq = diskdrive . irq ;
}
public override byte ReadEXP ( int addr )
{
byte ret = NES . DB ;
2012-10-22 16:10:19 +00:00
if ( addr > = 0x0040 )
return audio . ReadReg ( addr + 0x4000 , ret ) ;
2012-10-21 19:22:22 +00:00
switch ( addr )
{
case 0x0030 :
if ( diskenable )
{
int tmp = diskdrive . Read4030 ( ) & 0xd2 ;
ret & = 0x2c ;
if ( timerirq )
ret | = 1 ;
ret | = ( byte ) tmp ;
timerirq = false ;
}
break ;
case 0x0031 :
if ( diskenable )
ret = diskdrive . Read4031 ( ) ;
break ;
case 0x0032 :
if ( diskenable )
{
int tmp = diskdrive . Read4032 ( ) & 0x47 ;
ret & = 0xb8 ;
ret | = ( byte ) tmp ;
}
break ;
case 0x0033 :
if ( diskenable )
{
ret = reg4026 ;
2012-10-28 15:13:56 +00:00
// uncomment to set low battery flag
// ret &= 0x7f;
2012-10-21 19:22:22 +00:00
}
break ;
}
diskirq = diskdrive . irq ;
2012-10-26 18:51:08 +00:00
//if (addr != 0x0032)
// Console.WriteLine("R{0:x4}:{1:x2} {2:x4}", addr + 0x4000, ret, NES.cpu.PC);
2012-10-21 19:22:22 +00:00
return ret ;
}
2012-11-02 23:32:32 +00:00
public override byte PeekCart ( int addr )
{
if ( addr > = 0x6000 )
return base . PeekCart ( addr ) ;
else
return 0 ; // lazy
}
2012-10-31 14:36:43 +00:00
public override void ClockCPU ( )
2012-10-21 19:22:22 +00:00
{
2012-10-22 02:50:43 +00:00
if ( ( timerreg & 2 ) ! = 0 & & timervalue > 0 )
2012-10-21 19:22:22 +00:00
{
timervalue - - ;
if ( timervalue = = 0 )
{
2012-10-22 02:50:43 +00:00
if ( ( timerreg & 1 ) ! = 0 )
{
2012-10-31 14:36:43 +00:00
timervalue = timerlatch ;
2012-10-22 02:50:43 +00:00
}
else
{
timerreg & = unchecked ( ( byte ) ~ 2 ) ;
timervalue = 0 ;
timerlatch = 0 ;
}
2012-10-21 19:22:22 +00:00
timerirq = true ;
}
}
2012-10-31 14:36:43 +00:00
audio . Clock ( ) ;
}
public override void ClockPPU ( )
{
2012-10-21 19:22:22 +00:00
diskdrive . Clock ( ) ;
diskirq = diskdrive . irq ;
}
2012-10-21 15:58:24 +00:00
public override byte ReadWRAM ( int addr )
{
return WRAM [ addr & 0x1fff ] ;
}
public override void WriteWRAM ( int addr , byte value )
{
WRAM [ addr & 0x1fff ] = value ;
}
public override byte ReadPRG ( int addr )
{
if ( addr > = 0x6000 )
return biosrom [ addr & 0x1fff ] ;
else
return WRAM [ addr + 0x2000 ] ;
}
public override void WritePRG ( int addr , byte value )
{
if ( addr < 0x6000 )
WRAM [ addr + 0x2000 ] = value ;
}
}
}