using System; using System.Collections.Generic; using BizHawk.Common; using BizHawk.Emulation.Common.Components; namespace BizHawk.Emulation.Cores.Nintendo.NES { //mapper 85 //If you change any of the IRQ logic here, be sure to change it in VRC 2/3/4/6 as well. public sealed class VRC7 : NES.NESBoardBase { //configuration int prg_bank_mask_8k, chr_bank_mask_1k; Func remap; //state YM2413 fm; //= new Sound.YM2413(Sound.YM2413.ChipType.VRC7); ByteBuffer prg_banks_8k = new ByteBuffer(4); ByteBuffer chr_banks_1k = new ByteBuffer(8); bool irq_mode; bool irq_enabled, irq_pending, irq_autoen; byte irq_reload; byte irq_counter; int irq_prescaler; public override void Dispose() { base.Dispose(); prg_banks_8k.Dispose(); chr_banks_1k.Dispose(); } public override void SyncState(Serializer ser) { base.SyncState(ser); if (fm != null) fm.SyncState(ser); ser.Sync("prg_banks_8k", ref prg_banks_8k); ser.Sync("chr_banks_1k", ref chr_banks_1k); ser.Sync("irq_mode", ref irq_mode); ser.Sync("irq_enabled", ref irq_enabled); ser.Sync("irq_pending", ref irq_pending); ser.Sync("irq_autoen", ref irq_autoen); ser.Sync("irq_reload", ref irq_reload); ser.Sync("irq_counter", ref irq_counter); ser.Sync("irq_prescaler", ref irq_prescaler); SyncIRQ(); } void SyncIRQ() { IRQSignal = (irq_pending && irq_enabled); } static int RemapM117(int addr) { //addr &= 0x7007; // i don't know all of which bits are decoded, but this breaks stuff switch (addr) { //prg case 0x0000: return 0x0000; case 0x0001: return 0x0001; case 0x0002: return 0x1000; //chr case 0x2000: return 0x2000; case 0x2001: return 0x2001; case 0x2002: return 0x3000; case 0x2003: return 0x3001; case 0x2004: return 0x4000; case 0x2005: return 0x4001; case 0x2006: return 0x5000; case 0x2007: return 0x5001; //irq // fake addressees to activate different irq handling logic case 0x4001: return 0x10001; case 0x4002: return 0x10002; case 0x4003: return 0x10003; case 0x6000: return 0x10004; //mir case 0x5000: return 0x6000; //probably nothing at all default: return 0xffff; } } public override bool Configure(NES.EDetectionOrigin origin) { switch (Cart.board_type) { case "MAPPER085": // presumably the only reason a homebrew would use mapper085 is for the sound? // so initialize like lagrange point remap = (addr) => ((addr & 0xF000) | ((addr & 0x30) >> 4)); fm = new YM2413(YM2413.ChipType.VRC7); break; case "KONAMI-VRC-7": AssertPrg(128, 512); AssertChr(0, 128); AssertVram(0, 8); AssertWram(0, 8); if (Cart.pcb == "353429") { //tiny toons 2 remap = (addr) => ((addr & 0xF000) | ((addr & 0x8) >> 3)); // there is no resonator or crystal on the board for the fm chip fm = null; } else if (Cart.pcb == "352402") { //lagrange point remap = (addr) => ((addr & 0xF000) | ((addr & 0x30) >> 4)); fm = new YM2413(YM2413.ChipType.VRC7); } else throw new Exception("Unknown PCB type for VRC7"); break; case "MAPPER117": // not sure quite what this is // different address mapping, and somewhat different irq logic Cart.vram_size = 0; Cart.wram_size = 0; remap = RemapM117; fm = null; break; default: return false; } prg_bank_mask_8k = Cart.prg_size / 8 - 1; chr_bank_mask_1k = Cart.chr_size - 1; SetMirrorType(EMirrorType.Vertical); prg_banks_8k[3] = (byte)(0xFF & prg_bank_mask_8k); return true; } public override byte ReadPRG(int addr) { int bank_8k = addr >> 13; int ofs = addr & ((1 << 13) - 1); bank_8k = prg_banks_8k[bank_8k]; addr = (bank_8k << 13) | ofs; return ROM[addr]; } int Map_PPU(int addr) { int bank_1k = addr >> 10; int ofs = addr & ((1 << 10) - 1); bank_1k = chr_banks_1k[bank_1k]; addr = (bank_1k << 10) | ofs; return addr; } public override byte ReadPPU(int addr) { if (addr < 0x2000) { addr = Map_PPU(addr); if (Cart.vram_size != 0) return base.ReadPPU(addr); else return VROM[addr]; } else return base.ReadPPU(addr); } public override void WritePPU(int addr, byte value) { if (addr < 0x2000) { base.WritePPU(Map_PPU(addr),value); } else base.WritePPU(addr, value); } public override void ApplyCustomAudio(short[] samples) { if (fm != null) { short[] fmsamples = new short[samples.Length]; fm.GetSamples(fmsamples); //naive mixing. need to study more int len = samples.Length; for (int i = 0; i < len; i++) { short fmsamp = fmsamples[i]; samples[i] = (short)(samples[i] + fmsamp); } } } public override void WritePRG(int addr, byte value) { //Console.WriteLine(" mapping {0:X4} = {1:X2}", addr, value); addr = remap(addr); //Console.WriteLine("- remapping {0:X4} = {1:X2}", addr, value); switch (addr) { case 0x0000: prg_banks_8k[0] = (byte)(value & prg_bank_mask_8k); break; case 0x0001: prg_banks_8k[1] = (byte)(value & prg_bank_mask_8k); break; case 0x1000: prg_banks_8k[2] = (byte)(value & prg_bank_mask_8k); break; case 0x1001: //sound address port if (fm != null) fm.RegisterLatch = value; break; case 0x1003: //sound data port if (fm != null) fm.Write(value); break; //a bit creepy to mask this for lagrange point which has no VROM, but the mask will be 0xFFFFFFFF so its OK case 0x2000: chr_banks_1k[0] = (byte)(value & chr_bank_mask_1k); break; case 0x2001: chr_banks_1k[1] = (byte)(value & chr_bank_mask_1k); break; case 0x3000: chr_banks_1k[2] = (byte)(value & chr_bank_mask_1k); break; case 0x3001: chr_banks_1k[3] = (byte)(value & chr_bank_mask_1k); break; case 0x4000: chr_banks_1k[4] = (byte)(value & chr_bank_mask_1k); break; case 0x4001: chr_banks_1k[5] = (byte)(value & chr_bank_mask_1k); break; case 0x5000: chr_banks_1k[6] = (byte)(value & chr_bank_mask_1k); break; case 0x5001: chr_banks_1k[7] = (byte)(value & chr_bank_mask_1k); break; case 0x6000: switch (value & 3) { case 0: SetMirrorType(NES.NESBoardBase.EMirrorType.Vertical); break; case 1: SetMirrorType(NES.NESBoardBase.EMirrorType.Horizontal); break; case 2: SetMirrorType(NES.NESBoardBase.EMirrorType.OneScreenA); break; case 3: SetMirrorType(NES.NESBoardBase.EMirrorType.OneScreenB); break; } break; case 0x6001: //(reload) irq_reload = value; break; case 0x7000: //(control) irq_mode = value.Bit(2); irq_autoen = value.Bit(0); if (value.Bit(1)) { //enabled irq_enabled = true; irq_counter = irq_reload; irq_prescaler = 341; } else { //disabled irq_enabled = false; } //acknowledge irq_pending = false; SyncIRQ(); break; case 0x7001: //(ack) irq_pending = false; irq_enabled = irq_autoen; SyncIRQ(); break; // special irq logic for M117 // RemapM117() sends some addresses to these "virtual addresses" for irq handling case 0x10001: irq_reload = (byte)(237 - value); // what break; case 0x10002: irq_pending = false; SyncIRQ(); break; case 0x10003: irq_counter = irq_reload; break; case 0x10004: irq_enabled = value.Bit(0); irq_pending = false; SyncIRQ(); break; } } void ClockIRQ() { if (irq_counter == 0xFF) { irq_pending = true; irq_counter = irq_reload; SyncIRQ(); } else irq_counter++; } public override void ClockCPU() { if (!irq_enabled) return; if (irq_mode) { ClockIRQ(); //throw new InvalidOperationException("needed a test case for this; you found one!"); } else { irq_prescaler -= 3; if (irq_prescaler <= 0) { irq_prescaler += 341; ClockIRQ(); } } } } }