From 8d3e10b56ea0ab82852a91def24f9c06dcd2b0ae Mon Sep 17 00:00:00 2001 From: nattthebear Date: Wed, 16 Mar 2016 19:56:26 -0400 Subject: [PATCH] Support UNIF DripGame http://www.qmtpro.com/~nes/drip/. Everything works except the nametable viewer (which does not account for the exattributes) --- .../BizHawk.Emulation.Cores.csproj | 1 + .../NES/Boards/UNIF/UNIF_UNL_DripGame.cs | 405 ++++++++++++++++++ .../Consoles/Nintendo/NES/NES.BoardSystem.cs | 2 +- 3 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL_DripGame.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 808ac2af7f..ef47c6b15e 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -726,6 +726,7 @@ + Code diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL_DripGame.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL_DripGame.cs new file mode 100644 index 0000000000..687caabc3e --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/Boards/UNIF/UNIF_UNL_DripGame.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BizHawk.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.NES +{ + // http://www.qmtpro.com/~nes/drip/dripmap.txt + // http://wiki.nesdev.com/w/index.php/UNIF/UNL-DripGame + public class UNIF_UNL_DripGame : NES.NESBoardBase + { + [MapperProp] + public bool DripGameDipSwitch; + + // cycles the hardware takes to prep; exact value not known + const int WarmupTime = 1000000; + + // hardware warmup clock + int warmupclock = WarmupTime; + + // 16k prg bank + int[] prg = new int[2]; + // 2k chr bank + int[] chr = new int[4]; + // prg bank mask + int prgmask; + // chr bank mask + int chrmask; + + // 4096 bits of attribute ram (only bottom two bits of each byte are valid) + byte[] exram = new byte[2048]; + + // nt mirroring (we track internally because we have to use it specially) + int[] nt = new int[4]; + + // true if exattr is active + bool exattr; // 800a.2 + // true if sram is writeable + bool sramwrite; // 800a.3 + + // 8 bit latch of value written to 8008 + byte irqbuffer; // 8008 + // current irq timer value (15 bit) + int irqvalue; + + // last ppu read in 2xxx that was a nametable read, mapped for nt mirroring to 000:7ff + // internally, this might be implemented without a latch (you can figure everything directly from the address) + int lastntread; + + // sound hardware + SoundChannel sound0; + SoundChannel sound1; + + public override void SyncState(Serializer ser) + { + base.SyncState(ser); + + ser.Sync("DripGameDipSwitch", ref DripGameDipSwitch); + + ser.Sync("warmupclock", ref warmupclock); + ser.Sync("prg", ref prg, false); + ser.Sync("chr", ref chr, false); + + ser.Sync("exram", ref exram, false); + ser.Sync("nt", ref nt, false); + ser.Sync("exattr", ref exattr); + ser.Sync("sramwrite", ref sramwrite); + ser.Sync("irqbuffer", ref irqbuffer); + ser.Sync("irqvalue", ref irqvalue); + + ser.Sync("lastntread", ref lastntread); // technically not needed if states are always at frame boundary + + ser.BeginSection("sound0"); + sound0.SyncState(ser); + ser.EndSection(); + ser.BeginSection("sound1"); + sound1.SyncState(ser); + ser.EndSection(); + } + + public override bool Configure(NES.EDetectionOrigin origin) + { + switch (Cart.board_type) + { + case "UNIF_UNL-DRIPGAME": + break; + default: + return false; + } + Cart.wram_size = 8; + Cart.wram_battery = false; + AssertPrg(16, 32, 64, 128, 256); // 4 bits x 16 + AssertChr(8, 16, 32); // 4 bits x 2 + + AutoMapperProps.Apply(this); + + prgmask = Cart.prg_size / 16 - 1; + chrmask = Cart.prg_size / 2 - 1; + + prg[1] = prgmask; + SetMirror(0); + + if (NES.apu != null) // don't start up sound when in configurator + { + sound0 = new SoundChannel(NES.apu.ExternalQueue); + sound1 = new SoundChannel(NES.apu.ExternalQueue); + } + + return true; + } + + private void SetMirror(int mirr) + { + switch (mirr) + { + case 0: + nt[0] = 0; nt[1] = 1; nt[2] = 0; nt[3] = 1; break; // V + case 1: + nt[0] = 0; nt[1] = 0; nt[2] = 1; nt[3] = 1; break; // H + case 2: + nt[0] = 0; nt[1] = 0; nt[2] = 0; nt[3] = 0; break; // 1a + case 3: + nt[0] = 1; nt[1] = 1; nt[2] = 1; nt[3] = 1; break; // 1b + } + } + + public override void ClockCPU() + { + if (irqvalue > 0) + { + irqvalue--; + if (irqvalue == 0) + IRQSignal = true; + } + if (warmupclock > 0) + { + warmupclock--; + } + sound0.Clock(); + sound1.Clock(); + } + + public override byte ReadEXP(int addr) + { + switch (addr & 0xf800) + { + case 0x0800: // status + byte ret = warmupclock == 0 ? (byte)0x64 : (byte)0x7f; + if (DripGameDipSwitch) + ret |= 0x80; + return ret; + + case 0x1000: // sound1 status + return sound0.Read(); + case 0x1800: // sound2 status + return sound1.Read(); + default: + return NES.DB; + } + } + + public override void WritePRG(int addr, byte value) + { + if (addr < 0x4000) // regs + { + switch (addr & 15) + { + case 0: // sc0 silence + case 1: // sc0 data + case 2: // sc0 p low + case 3: // sc0 p hi + sound0.Write(addr & 3, value); + return; + case 4: // sc0 silence + case 5: // sc0 data + case 6: // sc0 p low + case 7: // sc0 p hi + sound1.Write(addr & 3, value); + return; + + case 8: // irql + irqbuffer = value; + return; + case 9: // irqh + IRQSignal = false; // ack + if ((value & 0x80) != 0) // enable + { + irqvalue = value << 8 & 0x7f00 | irqbuffer; + } + else + { + irqvalue = 0; + } + return; + + case 10: // control + SetMirror(value & 3); + exattr = (value & 4) != 0; + sramwrite = (value & 8) != 0; + return; + + case 11: + prg[0] = value & prgmask; + return; + + case 12: + case 13: + case 14: + case 15: + chr[addr & 3] = value & chrmask; + return; + } + } + else // exattr + { + // mirror the two bottom bits to make rendering quicker + value &= 3; + value |= (byte)(value << 2); + value |= (byte)(value << 4); + exram[addr & 0x7ff] = value; + } + } + + public override byte ReadPRG(int addr) + { + return ROM[addr & 0x3fff | prg[addr >> 14] << 14]; + } + + public override byte ReadPPU(int addr) + { + if (addr < 0x2000) + { + return VROM[addr & 0x7ff | chr[addr >> 11] << 11]; + } + else + { + int mappedaddr = addr & 0x3ff | nt[addr >> 10 & 3] << 10; + if (exattr && (addr & 0x3ff) >= 0x3c0) + { + // pull palette data from exattr instead + return exram[lastntread]; + } + else + { + lastntread = mappedaddr; + return NES.CIRAM[mappedaddr]; + } + } + } + + public override void WritePPU(int addr, byte value) + { + if (addr >= 0x2000) + NES.CIRAM[addr & 0x3ff | nt[addr >> 10 & 3] << 10] = value; + } + + public override void WriteWRAM(int addr, byte value) + { + if (sramwrite) + base.WriteWRAM(addr, value); + } + + private class SoundChannel + { + public SoundChannel(Action enqueuer) + { + this.enqueuer = enqueuer; + } + + public void SyncState(Serializer ser) + { + ser.Sync("fifo", ref fifo, false); + ser.Sync("active", ref active); + ser.Sync("writecursor", ref writecursor); + ser.Sync("readcursor", ref readcursor); + + ser.Sync("timer", ref timer); + ser.Sync("period", ref period); + ser.Sync("volume", ref volume); + ser.Sync("sample", ref sample); + ser.Sync("latched", ref latched); // not needed + + ser.Sync("volumeChangePending", ref volumeChangePending); // very not needed + } + + // sound data fifo + byte[] fifo = new byte[256]; + // true if channel is currently running + bool active = false; + + // where the next byte will be written to + int writecursor; + // where the next byte will be read from + int readcursor; + + // current phase clock + int timer; + // phase reload value + int period; + + // volume register + int volume; + // last read sample + byte sample; + // latched output + int latched; + + // communicate with APU + Action enqueuer; + // true if V has been written and we need to check to change something + bool volumeChangePending; + + void CalcLatch() + { + int n = volume * sample * 9; // 9 is magic master volume level + if (n != latched) + { + enqueuer(n - latched); + latched = n; + } + } + + bool Empty { get { return writecursor == readcursor; } } + bool Full { get { return readcursor + 256 == writecursor; } } + + public void Write(int addr, byte value) + { + switch (addr) + { + case 0: // clear + writecursor = 0; + readcursor = 0; + Array.Clear(fifo, 0, 256); + active = false; + break; + case 1: // write + // TODO: is a write disallowed if we're full? + if (!Full) + { + fifo[writecursor & 255] = value; + writecursor++; + if (!active) + { + active = true; + timer = period; + //Console.WriteLine("Enter with period {0}", period); + } + } + break; + case 2: // set period low + period &= 0xf00; + period |= value; + break; + case 3: // period high + volume + period &= 0xff; + period |= value << 8 & 0xf00; + volume = value >> 4; + // we can't change the latched value right now because it's illegal + // to enqueue to the APU when outside clockcpu method + volumeChangePending = true; + break; + } + } + + public void Clock() + { + if (active && timer > 0) + { + timer--; + if (timer == 0) + { + if (Empty) + { + active = false; + //Console.WriteLine("Exhaust"); + } + else + { + sample = fifo[readcursor & 255]; + readcursor++; + timer = period; + CalcLatch(); + } + } + } + if (volumeChangePending) + { + volumeChangePending = false; + CalcLatch(); + } + } + + public byte Read() + { + byte ret = 0; + if (Empty) + ret |= 0x40; + if (Full) + ret |= 0x80; + return ret; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.BoardSystem.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.BoardSystem.cs index c4216e3230..7741bb9776 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.BoardSystem.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.BoardSystem.cs @@ -770,7 +770,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES if (e is InvalidCastException || e is FormatException || e is OverflowException) throw new InvalidDataException("Auto Mapper Properties were in a bad format!", e); else - throw e; + throw; } } break;