2012-10-21 15:58:24 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
|
|
|
|
{
|
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
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// fds bios image; should be 8192 bytes
|
|
|
|
|
/// </summary>
|
|
|
|
|
public byte[] biosrom;
|
|
|
|
|
|
2012-10-21 19:22:22 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// .fds disk image
|
|
|
|
|
/// </summary>
|
|
|
|
|
public byte[] diskimage;
|
|
|
|
|
|
|
|
|
|
RamAdapter diskdrive;
|
2012-10-22 16:10:19 +00:00
|
|
|
|
FDSAudio audio;
|
|
|
|
|
int audioclock;
|
2012-10-21 19:22:22 +00:00
|
|
|
|
|
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)
|
|
|
|
|
throw new Exception("FDS bios image needed!");
|
|
|
|
|
|
|
|
|
|
Cart.vram_size = 8;
|
|
|
|
|
Cart.wram_size = 32;
|
|
|
|
|
Cart.wram_battery = false;
|
|
|
|
|
Cart.system = "FDS";
|
|
|
|
|
Cart.board_type = "FAMICOM_DISK_SYSTEM";
|
2012-10-21 19:22:22 +00:00
|
|
|
|
|
|
|
|
|
diskdrive = new RamAdapter();
|
2012-10-22 16:10:19 +00:00
|
|
|
|
audio = new FDSAudio();
|
2012-10-21 19:22:22 +00:00
|
|
|
|
|
|
|
|
|
InsertSide(0);
|
|
|
|
|
|
2012-10-21 15:58:24 +00:00
|
|
|
|
// set mirroring
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-21 19:22:22 +00:00
|
|
|
|
public int NumSides
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return diskimage[4];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Eject()
|
|
|
|
|
{
|
|
|
|
|
diskdrive.Eject();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void InsertSide(int side)
|
|
|
|
|
{
|
|
|
|
|
byte[] buf = new byte[65500];
|
|
|
|
|
Buffer.BlockCopy(diskimage, 16 + side * 65500, buf, 0, 65500);
|
|
|
|
|
diskdrive.InsertBrokenImage(buf, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetIRQ()
|
|
|
|
|
{
|
|
|
|
|
IRQSignal = _diskirq || _timerirq;
|
|
|
|
|
}
|
|
|
|
|
bool _diskirq;
|
|
|
|
|
bool _timerirq;
|
|
|
|
|
bool diskirq { get { return _diskirq; } set { _diskirq = value; SetIRQ(); } }
|
|
|
|
|
bool timerirq { get { return _timerirq; } set { _timerirq = value; SetIRQ(); } }
|
|
|
|
|
|
|
|
|
|
bool diskenable = false;
|
|
|
|
|
bool soundenable = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int timerlatch;
|
|
|
|
|
int timervalue;
|
2012-10-22 02:50:43 +00:00
|
|
|
|
byte timerreg;
|
2012-10-21 19:22:22 +00:00
|
|
|
|
|
|
|
|
|
byte reg4026;
|
|
|
|
|
|
|
|
|
|
public override void WriteEXP(int addr, byte value)
|
|
|
|
|
{
|
2012-10-22 00:57:28 +00:00
|
|
|
|
Console.WriteLine("W{0:x4}:{1:x2} {2:x4}", addr + 0x4000, value, NES.cpu.PC);
|
|
|
|
|
|
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);
|
|
|
|
|
timervalue = timerlatch * 3;
|
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;
|
|
|
|
|
ret &= 0x80; // set battery flag
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
diskirq = diskdrive.irq;
|
2012-10-22 00:57:28 +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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void ClockPPU()
|
|
|
|
|
{
|
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)
|
|
|
|
|
{
|
|
|
|
|
timervalue = timerlatch * 3;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
timerreg &= unchecked((byte)~2);
|
|
|
|
|
timervalue = 0;
|
|
|
|
|
timerlatch = 0;
|
|
|
|
|
}
|
2012-10-21 19:22:22 +00:00
|
|
|
|
timerirq = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
diskdrive.Clock();
|
|
|
|
|
diskirq = diskdrive.irq;
|
2012-10-22 16:10:19 +00:00
|
|
|
|
audioclock++;
|
|
|
|
|
if (audioclock == 3)
|
|
|
|
|
{
|
|
|
|
|
audioclock = 0;
|
|
|
|
|
audio.Clock();
|
|
|
|
|
}
|
2012-10-21 19:22:22 +00:00
|
|
|
|
}
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-22 16:10:19 +00:00
|
|
|
|
public override void ApplyCustomAudio(short[] samples)
|
|
|
|
|
{
|
|
|
|
|
audio.ApplyCustomAudio(samples);
|
|
|
|
|
}
|
2012-10-21 15:58:24 +00:00
|
|
|
|
}
|
|
|
|
|
}
|