diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs index cb889a23bb..a37c230a4b 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/FDS.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.IO; namespace BizHawk.Emulation.Consoles.Nintendo { @@ -22,7 +23,18 @@ namespace BizHawk.Emulation.Consoles.Nintendo /// /// .fds disk image /// - public byte[] diskimage; + byte[] diskimage; + + /// + /// should only be called once, before emulation begins + /// + /// + public void SetDiskImage(byte[] diskimage) + { + this.diskimage = diskimage; + diskdiffs = new byte[NumSides][]; + } + RamAdapter diskdrive; FDSAudio audio; @@ -50,6 +62,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo return true; } + // with a bit of change, these methods could work with a better disk format + public int NumSides { get @@ -60,7 +74,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void Eject() { - diskdrive.Eject(); + if (currentside != null) + { + diskdiffs[(int)currentside] = diskdrive.MakeDiff(); + diskdrive.Eject(); + currentside = null; + } } public void InsertSide(int side) @@ -68,8 +87,79 @@ namespace BizHawk.Emulation.Consoles.Nintendo byte[] buf = new byte[65500]; Buffer.BlockCopy(diskimage, 16 + side * 65500, buf, 0, 65500); diskdrive.InsertBrokenImage(buf, false /*true*/); + if (diskdiffs[side] != null) + diskdrive.ApplyDiff(diskdiffs[side]); + currentside = side; } + int? currentside = null; + + byte[][] diskdiffs; + + 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(); + } + + 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 void ClearSaveRam() + { + if (currentside != null) + throw new Exception("FDS Saveram: Can't clear when a disk is active!"); + for (int i = 0; i < diskdiffs.Length; i++) + diskdiffs[i] = null; + } + + public override byte[] SaveRam + { get { throw new Exception("FDS Saveram: Must access with method api!"); } } + + public MemoryDomain GetDiskPeeker() { return new MemoryDomain("FDS SIDE", diskdrive.NumBytes, Endian.Little, diskdrive.PeekData, null); diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/RamAdapter.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/RamAdapter.cs index bdff080ca3..33048e6d15 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/RamAdapter.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/FDS/RamAdapter.cs @@ -87,6 +87,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo return tmp; } + /// + /// advance a 16 bit CRC register with 1 new input bit. x.25 standard + /// + /// + /// + /// static ushort CCITT(ushort crc, int bit) { int bitc = crc & 1; @@ -96,6 +102,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo return crc; } + /// + /// advance a 16 bit CRC register with 8 new input bits. x.25 standard + /// + /// + /// + /// static ushort CCITT_8(ushort crc, byte b) { for (int i = 0; i < 8; i++) @@ -106,7 +118,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo return crc; } - + /// the original contents of this disk when it was loaded. for virtual saveram diff + byte[] originaldisk = null; /// currently loaded disk side (ca 65k bytes) byte[] disk = null; /// current disk location in BITS, not bytes @@ -116,6 +129,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo /// true if current disk is writeprotected bool writeprotect = true; + /// + /// eject the loaded disk + /// public void Eject() { disk = null; @@ -124,8 +140,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo Console.WriteLine("FDS: Disk ejected"); } + /// + /// insert a new disk. might have to eject first??? + /// + /// least significant bits appear first on physical disk + /// length of disk in bits + /// disk is write protected public void Insert(byte[] side, int bitlength, bool writeprotect) { + if (side.Length * 8 < bitlength) + throw new ArgumentException("Disk too small for parameter!"); disk = side; disksize = bitlength; diskpos = 0; @@ -133,13 +157,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo state = RamAdapterState.INSERTING; SetCycles(); Console.WriteLine("FDS: Disk Inserted"); + originaldisk = (byte[])disk.Clone(); } /// /// insert a side image from an fds disk /// - /// - /// + /// 65500 bytes from a broken-ass .fds file to be corrected + /// disk is write protected public void InsertBrokenImage(byte[] side, bool writeprotect) { byte[] realside = FixFDSSide(side); @@ -147,6 +172,37 @@ namespace BizHawk.Emulation.Consoles.Nintendo //File.WriteAllBytes("fdsdebug.bin", realside); } + public void ApplyDiff(byte[] data) + { + int bitsize = data[0] * 0x10000 + data[1] * 0x100 + data[2]; + if (bitsize != disksize) + throw new ArgumentException("Disk size mismatch!"); + int pos = 0; + while (bitsize > 0) + { + disk[pos] ^= data[pos + 3]; + pos++; + bitsize -= 8; + } + } + + public byte[] MakeDiff() + { + byte[] ret = new byte[(disksize + 7) / 8 + 3]; + int bitsize = disksize; + ret[0] = (byte)(bitsize / 0x10000); + ret[1] = (byte)(bitsize / 0x100); + ret[2] = (byte)(bitsize); + int pos = 0; + while (bitsize > 0) + { + ret[pos + 3] = (byte)(disk[pos] ^ originaldisk[pos]); + pos++; + bitsize -= 8; + } + return ret; + } + /// /// memorydomain debugging /// @@ -165,6 +221,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo } // all timings are in terms of PPU cycles (@5.37mhz) + /// + /// ppu cycles until next action + /// int cycleswaiting = 0; enum RamAdapterState @@ -177,11 +236,17 @@ namespace BizHawk.Emulation.Consoles.Nintendo SPINUP, /// head moving back to beginning RESET, - /// nothing + /// nothing happening IDLE, }; + /// + /// physical state of the drive + /// RamAdapterState state = RamAdapterState.IDLE; + /// + /// set cycleswaiting param after a state change + /// void SetCycles() { // these are mostly guesses @@ -208,8 +273,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo break; } } - - // data write reg + + /// + /// data write reg + /// + /// public void Write4024(byte value) { bytetransferflag = false; @@ -217,16 +285,28 @@ namespace BizHawk.Emulation.Consoles.Nintendo //Console.WriteLine("!!4024:{0:x2}", value); } + /// + /// cached 4025 write; can be modified internally by some things + /// byte cached4025; + /// + /// can be raised on byte transfer complete + /// public bool irq; /// true if 4025.1 is set to true bool transferreset = false; + /// + /// 16 bit CRC register. in normal operation, will become all 0 on finishing a read (see x.25 spec for more details) + /// ushort crc = 0; - // control reg + /// + /// control reg + /// + /// public void Write4025(byte value) { if ((value & 1) != 0) // start motor @@ -260,12 +340,15 @@ namespace BizHawk.Emulation.Consoles.Nintendo irq = false; // ?? cached4025 = value; - if ((cached4025 & 4) == 0) - if ((cached4025 & 0x10) != 0) - Console.WriteLine("FDS: Starting CRC"); + //if ((cached4025 & 4) == 0) + // if ((cached4025 & 0x10) != 0) + // Console.WriteLine("FDS: Starting CRC"); } - // some bits come from outside RamAdapter + /// + /// general status reg, some bits are from outside the RamAdapter class + /// + /// public byte Read4030() { byte ret = 0; @@ -290,12 +373,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo /// int lastreaddiskpos; + /// + /// more status stuff + /// + /// public byte Read4031() { bytetransferflag = false; irq = false; //?? //Console.WriteLine("{0:x2} @{1}", readreglatch, lastreaddiskpos); - // it seems very hard to avoid this situation, hence the switch to latched shift regs + // note that the shift regs are latched, hence this doesn't happen //if (readregpos != 0) //{ // Console.WriteLine("FDS == BIT MISSED =="); @@ -303,6 +390,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo return readreglatch; } + /// + /// more status stuff + /// + /// public byte Read4032() { byte ret = 0xff; @@ -317,7 +408,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo } /// - /// 5.37mhz + /// clock at ~5.37mhz /// public void Clock() { @@ -356,27 +447,25 @@ namespace BizHawk.Emulation.Consoles.Nintendo state = RamAdapterState.RUNNING; SetCycles(); //transferreset = false; // this definitely does not happen. - //numcrc = 0; Console.WriteLine("FDS: Spin up complete! Disk is running"); break; case RamAdapterState.IDLE: SetCycles(); break; - } } } + // read and write shift regs, with bit positions and latched values for reload byte readreg; byte writereg; int readregpos; int writeregpos; byte readreglatch; byte writereglatch; - - bool _bytetransferflag; - bool bytetransferflag { get { return _bytetransferflag; } set { _bytetransferflag = value; } } + + bool bytetransferflag; bool lookingforendofgap = false; @@ -395,7 +484,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo { if (bit == 1) // found! { - Console.WriteLine("FDS: End of Gap @{0}", diskpos); + //Console.WriteLine("FDS: End of Gap @{0}", diskpos); lookingforendofgap = false;//cached4025 &= unchecked((byte)~0x40); // stop looking for end of gap readregpos = 0; @@ -427,9 +516,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo if ((cached4025 & 0x10) != 0) { - Console.WriteLine("FDS: crc byte {0:x2} @{1}", readreg, diskpos); + //Console.WriteLine("FDS: crc byte {0:x2} @{1}", readreg, diskpos); cached4025 &= unchecked((byte)~0x10); // clear CRC reading. no real effect other than to silence debug?? - Console.WriteLine("FDS: Final CRC {0:x4}", crc); + //Console.WriteLine("FDS: Final CRC {0:x4}", crc); } } } @@ -465,25 +554,25 @@ namespace BizHawk.Emulation.Consoles.Nintendo bytetransferflag = true; if ((cached4025 & 0x80) != 0) irq = true; - Console.WriteLine("FDS: Write @{0} Reload {1:x2}", diskpos + 1, writereglatch); + //Console.WriteLine("FDS: Write @{0} Reload {1:x2}", diskpos + 1, writereglatch); if ((cached4025 & 0x10) != 0) { - Console.WriteLine("FDS: write clear CRC", readreg, diskpos); + //Console.WriteLine("FDS: write clear CRC", readreg, diskpos); if (crc == 0) { cached4025 &= unchecked((byte)~0x10); // clear CRC reading - Console.WriteLine("FDS: write CRC commit finished"); + //Console.WriteLine("FDS: write CRC commit finished"); // it seems that after a successful CRC, the writereglatch is reset to 0 value. this is needed? writereglatch = 0; } - Console.WriteLine("{0:x4}", crc); + //Console.WriteLine("{0:x4}", crc); writereg = (byte)crc; - Console.WriteLine("{0:x2}", writereg); + //Console.WriteLine("{0:x2}", writereg); crc >>= 8; - Console.WriteLine("{0:x4}", crc); + //Console.WriteLine("{0:x4}", crc); // loaded the first CRC byte to write, so stop computing CRC on data writecomputecrc = false; } diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs index f73c696832..465acdbc29 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs @@ -327,12 +327,21 @@ namespace BizHawk.Emulation.Consoles.Nintendo public byte[] ReadSaveRam() { + if (board is FDS) + return (board as FDS).ReadSaveRam(); + if (board == null || board.SaveRam == null) return null; return (byte[])board.SaveRam.Clone(); } public void StoreSaveRam(byte[] data) { + if (board is FDS) + { + (board as FDS).StoreSaveRam(data); + return; + } + if (board == null || board.SaveRam == null) return; Array.Copy(data, board.SaveRam, data.Length); @@ -340,6 +349,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo public void ClearSaveRam() { + if (board is FDS) + { + (board as FDS).ClearSaveRam(); + return; + } + if (board == null || board.SaveRam == null) return; for (int i = 0; i < board.SaveRam.Length; i++) @@ -348,7 +363,13 @@ namespace BizHawk.Emulation.Consoles.Nintendo public bool SaveRamModified { - get { if (board == null) return false; if (board.SaveRam == null) return false; return true; } + get + { + if (board == null) return false; + if (board is FDS) return true; + if (board.SaveRam == null) return false; + return true; + } set { } } @@ -374,7 +395,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo domains.Add(CIRAMdomain); domains.Add(OAMdoman); - if (board.SaveRam != null) + if (!(board is FDS) && board.SaveRam != null) { var BatteryRam = new MemoryDomain("Battery RAM", board.SaveRam.Length, Endian.Little, addr => board.SaveRam[addr], (addr, value) => board.SaveRam[addr] = value); @@ -483,7 +504,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo cart = new CartInfo(); var fdsboard = new FDS(); fdsboard.biosrom = fdsbios; - fdsboard.diskimage = rom; + fdsboard.SetDiskImage(rom); fdsboard.Create(this); fdsboard.Configure(origin);