FDS: SaveRam support. The "saveram" actually stored is a simple collection of (originalside ^ side), more or less. Like any wip beta, don't get too attached to your saves yet. Also misc cleanup.

This commit is contained in:
goyuken 2012-10-27 14:01:55 +00:00
parent 84a86a72a5
commit afaa256e37
3 changed files with 232 additions and 32 deletions

View File

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.IO;
namespace BizHawk.Emulation.Consoles.Nintendo namespace BizHawk.Emulation.Consoles.Nintendo
{ {
@ -22,7 +23,18 @@ namespace BizHawk.Emulation.Consoles.Nintendo
/// <summary> /// <summary>
/// .fds disk image /// .fds disk image
/// </summary> /// </summary>
public byte[] diskimage; byte[] diskimage;
/// <summary>
/// should only be called once, before emulation begins
/// </summary>
/// <param name="diskimage"></param>
public void SetDiskImage(byte[] diskimage)
{
this.diskimage = diskimage;
diskdiffs = new byte[NumSides][];
}
RamAdapter diskdrive; RamAdapter diskdrive;
FDSAudio audio; FDSAudio audio;
@ -50,6 +62,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
return true; return true;
} }
// with a bit of change, these methods could work with a better disk format
public int NumSides public int NumSides
{ {
get get
@ -60,7 +74,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public void Eject() public void Eject()
{ {
diskdrive.Eject(); if (currentside != null)
{
diskdiffs[(int)currentside] = diskdrive.MakeDiff();
diskdrive.Eject();
currentside = null;
}
} }
public void InsertSide(int side) public void InsertSide(int side)
@ -68,8 +87,79 @@ namespace BizHawk.Emulation.Consoles.Nintendo
byte[] buf = new byte[65500]; byte[] buf = new byte[65500];
Buffer.BlockCopy(diskimage, 16 + side * 65500, buf, 0, 65500); Buffer.BlockCopy(diskimage, 16 + side * 65500, buf, 0, 65500);
diskdrive.InsertBrokenImage(buf, false /*true*/); 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() public MemoryDomain GetDiskPeeker()
{ {
return new MemoryDomain("FDS SIDE", diskdrive.NumBytes, Endian.Little, diskdrive.PeekData, null); return new MemoryDomain("FDS SIDE", diskdrive.NumBytes, Endian.Little, diskdrive.PeekData, null);

View File

@ -87,6 +87,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo
return tmp; return tmp;
} }
/// <summary>
/// advance a 16 bit CRC register with 1 new input bit. x.25 standard
/// </summary>
/// <param name="crc"></param>
/// <param name="bit"></param>
/// <returns></returns>
static ushort CCITT(ushort crc, int bit) static ushort CCITT(ushort crc, int bit)
{ {
int bitc = crc & 1; int bitc = crc & 1;
@ -96,6 +102,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo
return crc; return crc;
} }
/// <summary>
/// advance a 16 bit CRC register with 8 new input bits. x.25 standard
/// </summary>
/// <param name="crc"></param>
/// <param name="b"></param>
/// <returns></returns>
static ushort CCITT_8(ushort crc, byte b) static ushort CCITT_8(ushort crc, byte b)
{ {
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
@ -106,7 +118,8 @@ namespace BizHawk.Emulation.Consoles.Nintendo
return crc; return crc;
} }
/// <summary>the original contents of this disk when it was loaded. for virtual saveram diff</summary>
byte[] originaldisk = null;
/// <summary>currently loaded disk side (ca 65k bytes)</summary> /// <summary>currently loaded disk side (ca 65k bytes)</summary>
byte[] disk = null; byte[] disk = null;
/// <summary>current disk location in BITS, not bytes</summary> /// <summary>current disk location in BITS, not bytes</summary>
@ -116,6 +129,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
/// <summary>true if current disk is writeprotected</summary> /// <summary>true if current disk is writeprotected</summary>
bool writeprotect = true; bool writeprotect = true;
/// <summary>
/// eject the loaded disk
/// </summary>
public void Eject() public void Eject()
{ {
disk = null; disk = null;
@ -124,8 +140,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo
Console.WriteLine("FDS: Disk ejected"); Console.WriteLine("FDS: Disk ejected");
} }
/// <summary>
/// insert a new disk. might have to eject first???
/// </summary>
/// <param name="side">least significant bits appear first on physical disk</param>
/// <param name="bitlength">length of disk in bits</param>
/// <param name="writeprotect">disk is write protected</param>
public void Insert(byte[] side, int bitlength, bool writeprotect) public void Insert(byte[] side, int bitlength, bool writeprotect)
{ {
if (side.Length * 8 < bitlength)
throw new ArgumentException("Disk too small for parameter!");
disk = side; disk = side;
disksize = bitlength; disksize = bitlength;
diskpos = 0; diskpos = 0;
@ -133,13 +157,14 @@ namespace BizHawk.Emulation.Consoles.Nintendo
state = RamAdapterState.INSERTING; state = RamAdapterState.INSERTING;
SetCycles(); SetCycles();
Console.WriteLine("FDS: Disk Inserted"); Console.WriteLine("FDS: Disk Inserted");
originaldisk = (byte[])disk.Clone();
} }
/// <summary> /// <summary>
/// insert a side image from an fds disk /// insert a side image from an fds disk
/// </summary> /// </summary>
/// <param name="side"></param> /// <param name="side">65500 bytes from a broken-ass .fds file to be corrected</param>
/// <param name="writeprotect"></param> /// <param name="writeprotect">disk is write protected</param>
public void InsertBrokenImage(byte[] side, bool writeprotect) public void InsertBrokenImage(byte[] side, bool writeprotect)
{ {
byte[] realside = FixFDSSide(side); byte[] realside = FixFDSSide(side);
@ -147,6 +172,37 @@ namespace BizHawk.Emulation.Consoles.Nintendo
//File.WriteAllBytes("fdsdebug.bin", realside); //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;
}
/// <summary> /// <summary>
/// memorydomain debugging /// memorydomain debugging
/// </summary> /// </summary>
@ -165,6 +221,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
} }
// all timings are in terms of PPU cycles (@5.37mhz) // all timings are in terms of PPU cycles (@5.37mhz)
/// <summary>
/// ppu cycles until next action
/// </summary>
int cycleswaiting = 0; int cycleswaiting = 0;
enum RamAdapterState enum RamAdapterState
@ -177,11 +236,17 @@ namespace BizHawk.Emulation.Consoles.Nintendo
SPINUP, SPINUP,
/// <summary>head moving back to beginning</summary> /// <summary>head moving back to beginning</summary>
RESET, RESET,
/// <summary>nothing</summary> /// <summary>nothing happening</summary>
IDLE, IDLE,
}; };
/// <summary>
/// physical state of the drive
/// </summary>
RamAdapterState state = RamAdapterState.IDLE; RamAdapterState state = RamAdapterState.IDLE;
/// <summary>
/// set cycleswaiting param after a state change
/// </summary>
void SetCycles() void SetCycles()
{ {
// these are mostly guesses // these are mostly guesses
@ -208,8 +273,11 @@ namespace BizHawk.Emulation.Consoles.Nintendo
break; break;
} }
} }
// data write reg /// <summary>
/// data write reg
/// </summary>
/// <param name="value"></param>
public void Write4024(byte value) public void Write4024(byte value)
{ {
bytetransferflag = false; bytetransferflag = false;
@ -217,16 +285,28 @@ namespace BizHawk.Emulation.Consoles.Nintendo
//Console.WriteLine("!!4024:{0:x2}", value); //Console.WriteLine("!!4024:{0:x2}", value);
} }
/// <summary>
/// cached 4025 write; can be modified internally by some things
/// </summary>
byte cached4025; byte cached4025;
/// <summary>
/// can be raised on byte transfer complete
/// </summary>
public bool irq; public bool irq;
/// <summary>true if 4025.1 is set to true</summary> /// <summary>true if 4025.1 is set to true</summary>
bool transferreset = false; bool transferreset = false;
/// <summary>
/// 16 bit CRC register. in normal operation, will become all 0 on finishing a read (see x.25 spec for more details)
/// </summary>
ushort crc = 0; ushort crc = 0;
// control reg /// <summary>
/// control reg
/// </summary>
/// <param name="value"></param>
public void Write4025(byte value) public void Write4025(byte value)
{ {
if ((value & 1) != 0) // start motor if ((value & 1) != 0) // start motor
@ -260,12 +340,15 @@ namespace BizHawk.Emulation.Consoles.Nintendo
irq = false; // ?? irq = false; // ??
cached4025 = value; cached4025 = value;
if ((cached4025 & 4) == 0) //if ((cached4025 & 4) == 0)
if ((cached4025 & 0x10) != 0) // if ((cached4025 & 0x10) != 0)
Console.WriteLine("FDS: Starting CRC"); // Console.WriteLine("FDS: Starting CRC");
} }
// some bits come from outside RamAdapter /// <summary>
/// general status reg, some bits are from outside the RamAdapter class
/// </summary>
/// <returns></returns>
public byte Read4030() public byte Read4030()
{ {
byte ret = 0; byte ret = 0;
@ -290,12 +373,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo
/// </summary> /// </summary>
int lastreaddiskpos; int lastreaddiskpos;
/// <summary>
/// more status stuff
/// </summary>
/// <returns></returns>
public byte Read4031() public byte Read4031()
{ {
bytetransferflag = false; bytetransferflag = false;
irq = false; //?? irq = false; //??
//Console.WriteLine("{0:x2} @{1}", readreglatch, lastreaddiskpos); //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) //if (readregpos != 0)
//{ //{
// Console.WriteLine("FDS == BIT MISSED =="); // Console.WriteLine("FDS == BIT MISSED ==");
@ -303,6 +390,10 @@ namespace BizHawk.Emulation.Consoles.Nintendo
return readreglatch; return readreglatch;
} }
/// <summary>
/// more status stuff
/// </summary>
/// <returns></returns>
public byte Read4032() public byte Read4032()
{ {
byte ret = 0xff; byte ret = 0xff;
@ -317,7 +408,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
} }
/// <summary> /// <summary>
/// 5.37mhz /// clock at ~5.37mhz
/// </summary> /// </summary>
public void Clock() public void Clock()
{ {
@ -356,27 +447,25 @@ namespace BizHawk.Emulation.Consoles.Nintendo
state = RamAdapterState.RUNNING; state = RamAdapterState.RUNNING;
SetCycles(); SetCycles();
//transferreset = false; // this definitely does not happen. //transferreset = false; // this definitely does not happen.
//numcrc = 0;
Console.WriteLine("FDS: Spin up complete! Disk is running"); Console.WriteLine("FDS: Spin up complete! Disk is running");
break; break;
case RamAdapterState.IDLE: case RamAdapterState.IDLE:
SetCycles(); SetCycles();
break; break;
} }
} }
} }
// read and write shift regs, with bit positions and latched values for reload
byte readreg; byte readreg;
byte writereg; byte writereg;
int readregpos; int readregpos;
int writeregpos; int writeregpos;
byte readreglatch; byte readreglatch;
byte writereglatch; byte writereglatch;
bool _bytetransferflag; bool bytetransferflag;
bool bytetransferflag { get { return _bytetransferflag; } set { _bytetransferflag = value; } }
bool lookingforendofgap = false; bool lookingforendofgap = false;
@ -395,7 +484,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
{ {
if (bit == 1) // found! 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 lookingforendofgap = false;//cached4025 &= unchecked((byte)~0x40); // stop looking for end of gap
readregpos = 0; readregpos = 0;
@ -427,9 +516,9 @@ namespace BizHawk.Emulation.Consoles.Nintendo
if ((cached4025 & 0x10) != 0) 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?? 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; bytetransferflag = true;
if ((cached4025 & 0x80) != 0) if ((cached4025 & 0x80) != 0)
irq = true; 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) if ((cached4025 & 0x10) != 0)
{ {
Console.WriteLine("FDS: write clear CRC", readreg, diskpos); //Console.WriteLine("FDS: write clear CRC", readreg, diskpos);
if (crc == 0) if (crc == 0)
{ {
cached4025 &= unchecked((byte)~0x10); // clear CRC reading 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? // it seems that after a successful CRC, the writereglatch is reset to 0 value. this is needed?
writereglatch = 0; writereglatch = 0;
} }
Console.WriteLine("{0:x4}", crc); //Console.WriteLine("{0:x4}", crc);
writereg = (byte)crc; writereg = (byte)crc;
Console.WriteLine("{0:x2}", writereg); //Console.WriteLine("{0:x2}", writereg);
crc >>= 8; 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 // loaded the first CRC byte to write, so stop computing CRC on data
writecomputecrc = false; writecomputecrc = false;
} }

View File

@ -327,12 +327,21 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public byte[] ReadSaveRam() public byte[] ReadSaveRam()
{ {
if (board is FDS)
return (board as FDS).ReadSaveRam();
if (board == null || board.SaveRam == null) if (board == null || board.SaveRam == null)
return null; return null;
return (byte[])board.SaveRam.Clone(); return (byte[])board.SaveRam.Clone();
} }
public void StoreSaveRam(byte[] data) public void StoreSaveRam(byte[] data)
{ {
if (board is FDS)
{
(board as FDS).StoreSaveRam(data);
return;
}
if (board == null || board.SaveRam == null) if (board == null || board.SaveRam == null)
return; return;
Array.Copy(data, board.SaveRam, data.Length); Array.Copy(data, board.SaveRam, data.Length);
@ -340,6 +349,12 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public void ClearSaveRam() public void ClearSaveRam()
{ {
if (board is FDS)
{
(board as FDS).ClearSaveRam();
return;
}
if (board == null || board.SaveRam == null) if (board == null || board.SaveRam == null)
return; return;
for (int i = 0; i < board.SaveRam.Length; i++) for (int i = 0; i < board.SaveRam.Length; i++)
@ -348,7 +363,13 @@ namespace BizHawk.Emulation.Consoles.Nintendo
public bool SaveRamModified 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 { } set { }
} }
@ -374,7 +395,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
domains.Add(CIRAMdomain); domains.Add(CIRAMdomain);
domains.Add(OAMdoman); 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, var BatteryRam = new MemoryDomain("Battery RAM", board.SaveRam.Length, Endian.Little,
addr => board.SaveRam[addr], (addr, value) => board.SaveRam[addr] = value); addr => board.SaveRam[addr], (addr, value) => board.SaveRam[addr] = value);
@ -483,7 +504,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo
cart = new CartInfo(); cart = new CartInfo();
var fdsboard = new FDS(); var fdsboard = new FDS();
fdsboard.biosrom = fdsbios; fdsboard.biosrom = fdsbios;
fdsboard.diskimage = rom; fdsboard.SetDiskImage(rom);
fdsboard.Create(this); fdsboard.Create(this);
fdsboard.Configure(origin); fdsboard.Configure(origin);