play around with garbodev core. probably worse than meteor, in general. managed. small compile-time change in MainForm.cs is needed to switch between the two. garbodev source files don't have any license on them at all, so 99.99% chance that this will be axed. why did i do it?

This commit is contained in:
goyuken 2012-12-10 19:32:18 +00:00
parent c3a74edd7f
commit 4834f40f6c
14 changed files with 30876 additions and 1 deletions

View File

@ -231,6 +231,18 @@
<Compile Include="Consoles\Nintendo\Gameboy\Gambatte.cs" />
<Compile Include="Consoles\Nintendo\Gameboy\GBColors.cs" />
<Compile Include="Consoles\Nintendo\Gameboy\LibGambatte.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\Arm7Processor.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\ArmCore.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\FastArmCore.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\FastDispatchCore.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\GbaManager.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\IRenderer.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\Memory.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\Renderer.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\Renderers.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\SoundManager.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\ThumbCore.cs" />
<Compile Include="Consoles\Nintendo\GBAAlt\VideoManager.cs" />
<Compile Include="Consoles\Nintendo\GBA\LibMeteor.cs" />
<Compile Include="Consoles\Nintendo\GBA\Meteor.cs" />
<Compile Include="Consoles\Nintendo\NES\APU.cs" />

View File

@ -0,0 +1,501 @@
namespace GarboDev
{
using System;
using System.Collections.Generic;
using System.Text;
public class Arm7Processor
{
private Memory memory = null;
private FastArmCore armCore = null;
private ThumbCore thumbCore = null;
private Dictionary<uint, bool> breakpoints = null;
private int cycles = 0;
private int timerCycles = 0;
private int soundCycles = 0;
// CPU mode definitions
public const uint USR = 0x10;
public const uint FIQ = 0x11;
public const uint IRQ = 0x12;
public const uint SVC = 0x13;
public const uint ABT = 0x17;
public const uint UND = 0x1B;
public const uint SYS = 0x1F;
// CPSR bit definitions
public const int N_BIT = 31;
public const int Z_BIT = 30;
public const int C_BIT = 29;
public const int V_BIT = 28;
public const int I_BIT = 7;
public const int F_BIT = 6;
public const int T_BIT = 5;
public const uint N_MASK = (uint)(1U << N_BIT);
public const uint Z_MASK = (uint)(1U << Z_BIT);
public const uint C_MASK = (uint)(1U << C_BIT);
public const uint V_MASK = (uint)(1U << V_BIT);
public const uint I_MASK = (uint)(1U << I_BIT);
public const uint F_MASK = (uint)(1U << F_BIT);
public const uint T_MASK = (uint)(1U << T_BIT);
// Standard registers
private uint[] registers = new uint[16];
private uint cpsr = 0;
// Banked registers
private uint[] bankedFIQ = new uint[7];
private uint[] bankedIRQ = new uint[2];
private uint[] bankedSVC = new uint[2];
private uint[] bankedABT = new uint[2];
private uint[] bankedUND = new uint[2];
// Saved CPSR's
private uint spsrFIQ = 0;
private uint spsrIRQ = 0;
private uint spsrSVC = 0;
private uint spsrABT = 0;
private uint spsrUND = 0;
private ushort keyState;
private bool breakpointHit = false;
private bool cpuHalted = false;
public ushort KeyState
{
set { this.keyState = value; }
}
public int Cycles
{
get { return this.cycles; }
set { this.cycles = value; }
}
public bool ArmState
{
get { return (this.cpsr & Arm7Processor.T_MASK) != Arm7Processor.T_MASK; }
}
public uint[] Registers
{
get { return this.registers; }
}
public uint CPSR
{
get { return this.cpsr; }
set { this.cpsr = value; }
}
public bool SPSRExists
{
get
{
switch (this.cpsr & 0x1F)
{
case USR:
case SYS:
return false;
case FIQ:
case SVC:
case ABT:
case IRQ:
case UND:
return true;
default:
return false;
}
}
}
public uint SPSR
{
get
{
switch (this.cpsr & 0x1F)
{
case USR:
case SYS:
return 0xFFFFFFFF;
case FIQ:
return this.spsrFIQ;
case SVC:
return this.spsrSVC;
case ABT:
return this.spsrABT;
case IRQ:
return this.spsrIRQ;
case UND:
return this.spsrUND;
default:
throw new Exception("Unhandled CPSR state...");
}
}
set
{
switch (this.cpsr & 0x1F)
{
case USR:
case SYS:
break;
case FIQ:
this.spsrFIQ = value;
break;
case SVC:
this.spsrSVC = value;
break;
case ABT:
this.spsrABT = value;
break;
case IRQ:
this.spsrIRQ = value;
break;
case UND:
this.spsrUND = value;
break;
default:
throw new Exception("Unhandled CPSR state...");
}
}
}
public Dictionary<uint, bool> Breakpoints
{
get
{
return this.breakpoints;
}
}
public bool BreakpointHit
{
get { return this.breakpointHit; }
set { this.breakpointHit = value; }
}
public Arm7Processor(Memory memory)
{
this.memory = memory;
this.memory.Processor = this;
this.armCore = new FastArmCore(this, this.memory);
this.thumbCore = new ThumbCore(this, this.memory);
this.breakpoints = new Dictionary<uint, bool>();
this.breakpointHit = false;
}
private void SwapRegsHelper(uint[] swapRegs)
{
for (int i = 14; i > 14 - swapRegs.Length; i--)
{
uint tmp = this.registers[i];
this.registers[i] = swapRegs[swapRegs.Length - (14 - i) - 1];
swapRegs[swapRegs.Length - (14 - i) - 1] = tmp;
}
}
private void SwapRegisters(uint bank)
{
switch (bank & 0x1F)
{
case FIQ:
this.SwapRegsHelper(this.bankedFIQ);
break;
case SVC:
this.SwapRegsHelper(this.bankedSVC);
break;
case ABT:
this.SwapRegsHelper(this.bankedABT);
break;
case IRQ:
this.SwapRegsHelper(this.bankedIRQ);
break;
case UND:
this.SwapRegsHelper(this.bankedUND);
break;
}
}
public void WriteCpsr(uint newCpsr)
{
if ((newCpsr & 0x1F) != (this.cpsr & 0x1F))
{
// Swap out the old registers
this.SwapRegisters(this.cpsr);
// Swap in the new registers
this.SwapRegisters(newCpsr);
}
this.cpsr = newCpsr;
}
public void EnterException(uint mode, uint vector, bool interruptsDisabled, bool fiqDisabled)
{
uint oldCpsr = this.cpsr;
if ((oldCpsr & Arm7Processor.T_MASK) != 0)
{
registers[15] += 2U;
}
// Clear T bit, and set mode
uint newCpsr = (oldCpsr & ~0x3FU) | mode;
if (interruptsDisabled) newCpsr |= 1 << 7;
if (fiqDisabled) newCpsr |= 1 << 6;
this.WriteCpsr(newCpsr);
this.SPSR = oldCpsr;
registers[14] = registers[15];
registers[15] = vector;
this.ReloadQueue();
}
public void RequestIrq(int irq)
{
ushort iflag = Memory.ReadU16(this.memory.IORam, Memory.IF);
iflag |= (ushort)(1 << irq);
Memory.WriteU16(this.memory.IORam, Memory.IF, iflag);
}
public void FireIrq()
{
ushort ime = Memory.ReadU16(this.memory.IORam, Memory.IME);
ushort ie = Memory.ReadU16(this.memory.IORam, Memory.IE);
ushort iflag = Memory.ReadU16(this.memory.IORam, Memory.IF);
if ((ie & (iflag)) != 0 && (ime & 1) != 0 && (this.cpsr & (1 << 7)) == 0)
{
// Off to the irq exception vector
this.EnterException(Arm7Processor.IRQ, 0x18, true, false);
}
}
public void Reset(bool skipBios)
{
this.breakpointHit = false;
this.cpuHalted = false;
// Default to ARM state
this.cycles = 0;
this.timerCycles = 0;
this.soundCycles = 0;
this.bankedSVC[0] = 0x03007FE0;
this.bankedIRQ[0] = 0x03007FA0;
this.cpsr = SYS;
this.spsrSVC = this.cpsr;
for (int i = 0; i < 15; i++) this.registers[i] = 0;
if (skipBios)
{
this.registers[15] = 0x8000000;
}
else
{
this.registers[15] = 0;
}
this.armCore.BeginExecution();
}
public void Halt()
{
this.cpuHalted = true;
this.cycles = 0;
}
public void Step()
{
this.breakpointHit = false;
if ((this.cpsr & Arm7Processor.T_MASK) == Arm7Processor.T_MASK)
{
this.thumbCore.Step();
}
else
{
this.armCore.Step();
}
this.UpdateTimers();
}
public void ReloadQueue()
{
if ((this.cpsr & Arm7Processor.T_MASK) == Arm7Processor.T_MASK)
{
this.thumbCore.BeginExecution();
}
else
{
this.armCore.BeginExecution();
}
}
private void UpdateTimer(int timer, int cycles, bool countUp)
{
ushort control = Memory.ReadU16(this.memory.IORam, Memory.TM0CNT + (uint)(timer * 4));
// Make sure timer is enabled, or count up is disabled
if ((control & (1 << 7)) == 0) return;
if (!countUp && (control & (1 << 2)) != 0) return;
if (!countUp)
{
switch (control & 3)
{
case 0: cycles *= 1 << 10; break;
case 1: cycles *= 1 << 4; break;
case 2: cycles *= 1 << 2; break;
// Don't need to do anything for case 3
}
}
this.memory.TimerCnt[timer] += (uint)cycles;
uint timerCnt = this.memory.TimerCnt[timer] >> 10;
if (timerCnt > 0xffff)
{
ushort soundCntX = Memory.ReadU16(this.memory.IORam, Memory.SOUNDCNT_X);
if ((soundCntX & (1 << 7)) != 0)
{
ushort soundCntH = Memory.ReadU16(this.memory.IORam, Memory.SOUNDCNT_H);
if (timer == ((soundCntH >> 10) & 1))
{
// FIFO A overflow
this.memory.SoundManager.DequeueA();
if (this.memory.SoundManager.QueueSizeA < 16)
{
this.memory.FifoDma(1);
// TODO
if (this.memory.SoundManager.QueueSizeA < 16)
{
}
}
}
if (timer == ((soundCntH >> 14) & 1))
{
// FIFO B overflow
this.memory.SoundManager.DequeueB();
if (this.memory.SoundManager.QueueSizeB < 16)
{
this.memory.FifoDma(2);
}
}
}
// Overflow, attempt to fire IRQ
if ((control & (1 << 6)) != 0)
{
this.RequestIrq(3 + timer);
}
if (timer < 3)
{
ushort control2 = Memory.ReadU16(this.memory.IORam, Memory.TM0CNT + (uint)((timer + 1) * 4));
if ((control2 & (1 << 2)) != 0)
{
// Count-up
this.UpdateTimer(timer + 1, (int)((timerCnt >> 16) << 10), true);
}
}
// Reset the original value
uint count = Memory.ReadU16(this.memory.IORam, Memory.TM0D + (uint)(timer * 4));
this.memory.TimerCnt[timer] = count << 10;
}
}
public void UpdateTimers()
{
int cycles = this.timerCycles - this.cycles;
for (int i = 0; i < 4; i++)
{
this.UpdateTimer(i, cycles, false);
}
this.timerCycles = this.cycles;
}
public void UpdateKeyState()
{
ushort KEYCNT = this.memory.ReadU16Debug(Memory.REG_BASE + Memory.KEYCNT);
if ((KEYCNT & (1 << 14)) != 0)
{
if ((KEYCNT & (1 << 15)) != 0)
{
KEYCNT &= 0x3FF;
if (((~this.keyState) & KEYCNT) == KEYCNT)
this.RequestIrq(12);
}
else
{
KEYCNT &= 0x3FF;
if (((~this.keyState) & KEYCNT) != 0)
this.RequestIrq(12);
}
}
this.memory.KeyState = this.keyState;
}
public void UpdateSound()
{
this.memory.SoundManager.Mix(this.soundCycles);
this.soundCycles = 0;
}
public void Execute(int cycles)
{
this.cycles += cycles;
this.timerCycles += cycles;
this.soundCycles += cycles;
this.breakpointHit = false;
if (this.cpuHalted)
{
ushort ie = Memory.ReadU16(this.memory.IORam, Memory.IE);
ushort iflag = Memory.ReadU16(this.memory.IORam, Memory.IF);
if ((ie & iflag) != 0)
{
this.cpuHalted = false;
}
else
{
this.cycles = 0;
this.UpdateTimers();
this.UpdateSound();
return;
}
}
while (this.cycles > 0)
{
if ((this.cpsr & Arm7Processor.T_MASK) == Arm7Processor.T_MASK)
{
this.thumbCore.Execute();
}
else
{
this.armCore.Execute();
}
this.UpdateTimers();
this.UpdateSound();
if (this.breakpointHit)
{
break;
}
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,482 @@
//#define ARM_DEBUG
namespace GarboDev
{
using System;
using System.IO;
using System.Threading;
using System.Timers;
using System.Windows.Forms;
using System.Collections.Generic;
using BizHawk.Emulation;
using BizHawk;
public class GbaManager : IEmulator, ISoundProvider, IVideoProvider
{
public const int cpuFreq = 16 * 1024 * 1024;
private int framesRendered;
private Arm7Processor arm7 = null;
private Memory memory = null;
private VideoManager videoManager = null;
private SoundManager soundManager = null;
private bool skipBios = false;
public delegate void CpuUpdateDelegate(Arm7Processor processor, Memory memory);
private event CpuUpdateDelegate onCpuUpdate = null;
public Arm7Processor Arm7
{
get { return this.arm7; }
}
public VideoManager VideoManager
{
get { return this.videoManager; }
}
public SoundManager SoundManager
{
get { return this.soundManager; }
}
public Memory Memory
{
get { return this.memory; }
}
public Dictionary<uint, bool> Breakpoints
{
get { return this.arm7.Breakpoints; }
}
public ushort KeyState
{
get
{
if (this.memory != null)
{
return this.memory.KeyState;
}
return 0x3FF;
}
set
{
this.arm7.KeyState = value;
}
}
public int FramesRendered
{
get { return this.framesRendered; }
set { this.framesRendered = value; }
}
public event CpuUpdateDelegate OnCpuUpdate
{
add
{
this.onCpuUpdate += value;
this.onCpuUpdate(this.arm7, this.memory);
}
remove
{
this.onCpuUpdate -= value;
}
}
public bool SkipBios
{
get { return this.skipBios; }
set { this.skipBios = value; }
}
public GbaManager(CoreComm comm)
{
_corecomm = comm;
this.memory = new Memory();
this.arm7 = new Arm7Processor(this.memory);
this.videoManager = new VideoManager(this);
this.videoManager.Memory = this.memory;
this.soundManager = new SoundManager(this.memory, 44100);
this.framesRendered = 0;
Renderer renderer = new Renderer();
renderer.Initialize(null);
VideoManager.Renderer = renderer;
videoManager.Presenter = delegate(uint[] data)
{
Buffer.BlockCopy(data, 0, this.vbuf, 0, 240 * 160 * 4);
};
}
public void Load(byte[] rom, byte[] bios)
{
LoadBios(bios);
LoadRom(rom);
}
public void Reset()
{
//this.Halt();
this.arm7.Reset(this.skipBios);
this.memory.Reset();
this.videoManager.Reset();
}
public void AudioMixerStereo(short[] buffer, int length)
{
// even = left, odd = right
if (this.soundManager.SamplesMixed > Math.Max(500, length))
{
this.soundManager.GetSamples(buffer, length);
}
}
public void LoadState(BinaryReader state)
{
}
public void SaveState(BinaryWriter state)
{
state.Write("GARB");
}
public void LoadBios(byte[] biosRom)
{
this.memory.LoadBios(biosRom);
if (this.onCpuUpdate != null)
{
this.onCpuUpdate(this.arm7, this.memory);
}
}
public void LoadRom(byte[] cartRom)
{
//this.Halt();
/*
byte[] logo = new byte[]
{
0x24,0xff,0xae,0x51,0x69,0x9a,0xa2,0x21,
0x3d,0x84,0x82,0x0a,0x84,0xe4,0x09,0xad,
0x11,0x24,0x8b,0x98,0xc0,0x81,0x7f,0x21,
0xa3,0x52,0xbe,0x19,0x93,0x09,0xce,0x20,
0x10,0x46,0x4a,0x4a,0xf8,0x27,0x31,0xec,
0x58,0xc7,0xe8,0x33,0x82,0xe3,0xce,0xbf,
0x85,0xf4,0xdf,0x94,0xce,0x4b,0x09,0xc1,
0x94,0x56,0x8a,0xc0,0x13,0x72,0xa7,0xfc,
0x9f,0x84,0x4d,0x73,0xa3,0xca,0x9a,0x61,
0x58,0x97,0xa3,0x27,0xfc,0x03,0x98,0x76,
0x23,0x1d,0xc7,0x61,0x03,0x04,0xae,0x56,
0xbf,0x38,0x84,0x00,0x40,0xa7,0x0e,0xfd,
0xff,0x52,0xfe,0x03,0x6f,0x95,0x30,0xf1,
0x97,0xfb,0xc0,0x85,0x60,0xd6,0x80,0x25,
0xa9,0x63,0xbe,0x03,0x01,0x4e,0x38,0xe2,
0xf9,0xa2,0x34,0xff,0xbb,0x3e,0x03,0x44,
0x78,0x00,0x90,0xcb,0x88,0x11,0x3a,0x94,
0x65,0xc0,0x7c,0x63,0x87,0xf0,0x3c,0xaf,
0xd6,0x25,0xe4,0x8b,0x38,0x0a,0xac,0x72,
0x21,0xd4,0xf8,0x07
};
Array.Copy(logo, 0, cartRom, 4, logo.Length);
cartRom[0xB2] = 0x96;
cartRom[0xBD] = 0;
for (int i = 0xA0; i <= 0xBC; i++) cartRom[0xBD] = (byte)(cartRom[0xBD] - cartRom[i]);
cartRom[0xBD] = (byte)((cartRom[0xBD] - 0x19) & 0xFF);
*/
this.memory.LoadCartridge(cartRom);
this.Reset();
if (this.onCpuUpdate != null)
{
this.onCpuUpdate(this.arm7, this.memory);
}
}
public void Step()
{
//this.Halt();
this.arm7.Step();
if (this.onCpuUpdate != null)
{
this.onCpuUpdate(this.arm7, this.memory);
}
}
public void StepScanline()
{
//this.Halt();
this.arm7.Execute(960);
this.videoManager.RenderLine();
this.videoManager.EnterHBlank(this.arm7);
this.arm7.Execute(272);
this.videoManager.LeaveHBlank(this.arm7);
if (this.onCpuUpdate != null)
{
this.onCpuUpdate(this.arm7, this.memory);
}
}
void UpdateInputState()
{
ushort ret = 0;
if (_controller["Up"]) ret |= 64;
if (_controller["Down"]) ret |= 128;
if (_controller["Left"]) ret |= 32;
if (_controller["Right"]) ret |= 16;
if (_controller["Select"]) ret |= 4;
if (_controller["Start"]) ret |= 8;
if (_controller["B"]) ret |= 2;
if (_controller["A"]) ret |= 1;
if (_controller["L"]) ret |= 512;
if (_controller["R"]) ret |= 256;
ret ^= 0x3ff;
KeyState = ret;
}
private void StepFrame()
{
UpdateInputState();
if (_controller["Power"])
Reset();
int vramCycles = 0;
bool inHblank = false;
//HighPerformanceTimer profileTimer = new HighPerformanceTimer();
while (true)
{
const int cycleStep = 123;
if (vramCycles <= 0)
{
if (inHblank)
{
vramCycles += 960;
bool HitVBlank = this.videoManager.LeaveHBlank(this.arm7);
inHblank = false;
if (HitVBlank)
break;
}
else
{
vramCycles += 272;
this.videoManager.RenderLine();
this.videoManager.EnterHBlank(this.arm7);
inHblank = true;
}
}
this.arm7.Execute(cycleStep);
#if ARM_DEBUG
if (this.arm7.BreakpointHit)
{
this.waitingToHalt = true;
Monitor.Wait(this);
}
#endif
vramCycles -= cycleStep;
this.arm7.FireIrq();
}
}
IVideoProvider IEmulator.VideoProvider
{
get { return this; }
}
ISoundProvider IEmulator.SoundProvider
{
get { return this; }
}
ISyncSoundProvider IEmulator.SyncSoundProvider
{
get { return new FakeSyncSound(this, 735); }
}
bool IEmulator.StartAsyncSound()
{
return false;
}
void IEmulator.EndAsyncSound()
{
}
ControllerDefinition IEmulator.ControllerDefinition
{
get { return BizHawk.Emulation.Consoles.Nintendo.GBA.GBA.GBAController; }
}
IController _controller;
IController IEmulator.Controller
{
get { return _controller; }
set { _controller = value; }
}
void IEmulator.FrameAdvance(bool render, bool rendersound)
{
StepFrame();
}
int IEmulator.Frame
{
get { return 0; }
}
int IEmulator.LagCount
{
get
{
return 0;
}
set
{
}
}
bool IEmulator.IsLagFrame
{
get { return false; }
}
string IEmulator.SystemId
{
get { return "GBA"; }
}
bool IEmulator.DeterministicEmulation
{
get { return true; }
}
byte[] IEmulator.ReadSaveRam()
{
return new byte[0];
}
void IEmulator.StoreSaveRam(byte[] data)
{
}
void IEmulator.ClearSaveRam()
{
}
bool IEmulator.SaveRamModified
{
get
{
return false;
}
set
{
}
}
void IEmulator.ResetFrameCounter()
{
}
void IEmulator.SaveStateText(TextWriter writer)
{
}
void IEmulator.LoadStateText(TextReader reader)
{
}
void IEmulator.SaveStateBinary(BinaryWriter writer)
{
}
void IEmulator.LoadStateBinary(BinaryReader reader)
{
}
byte[] IEmulator.SaveStateBinary()
{
return new byte[16];
}
CoreComm _corecomm;
CoreComm IEmulator.CoreComm
{
get { return _corecomm; }
}
IList<MemoryDomain> IEmulator.MemoryDomains
{
get { return new List<MemoryDomain>(); }
}
MemoryDomain IEmulator.MainMemory
{
get { return null; }
}
void IDisposable.Dispose()
{
}
void ISoundProvider.GetSamples(short[] samples)
{
AudioMixerStereo(samples, samples.Length);
}
void ISoundProvider.DiscardSamples()
{
}
int ISoundProvider.MaxVolume
{
get;
set;
}
int[] vbuf = new int[240 * 160];
int[] IVideoProvider.GetVideoBuffer() { return vbuf; }
int IVideoProvider.VirtualWidth { get { return 240; } }
int IVideoProvider.BufferWidth { get { return 240; } }
int IVideoProvider.BufferHeight { get { return 160; } }
int IVideoProvider.BackgroundColor { get { return unchecked((int)0xff000000); } }
}
}

View File

@ -0,0 +1,13 @@
namespace GarboDev
{
using System;
public interface IRenderer
{
Memory Memory { set; }
void Initialize(object data);
void Reset();
void RenderLine(int line);
uint[] ShowFrame();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,952 @@
namespace GarboDev
{
using System;
using System.Collections.Generic;
using System.Text;
public partial class Renderer : IRenderer
{
private Memory memory;
private uint[] scanline = new uint[240];
private byte[] blend = new byte[240];
private byte[] windowCover = new byte[240];
private uint[] back = new uint[240 * 160];
//private uint[] front = new uint[240 * 160];
private const uint pitch = 240;
// Convenience variable as I use it everywhere, set once in RenderLine
private ushort dispCnt;
// Window helper variables
private byte win0x1, win0x2, win0y1, win0y2;
private byte win1x1, win1x2, win1y1, win1y2;
private byte win0Enabled, win1Enabled, winObjEnabled, winOutEnabled;
private bool winEnabled;
private byte blendSource, blendTarget;
private byte blendA, blendB, blendY;
private int blendType;
private int curLine = 0;
private static uint[] colorLUT;
static Renderer()
{
colorLUT = new uint[0x10000];
// Pre-calculate the color LUT
for (uint i = 0; i <= 0xFFFF; i++)
{
uint r = (i & 0x1FU);
uint g = (i & 0x3E0U) >> 5;
uint b = (i & 0x7C00U) >> 10;
r = (r << 3) | (r >> 2);
g = (g << 3) | (g >> 2);
b = (b << 3) | (b >> 2);
colorLUT[i] = (r << 16) | (g << 8) | b;
}
}
public Memory Memory
{
set { this.memory = value; }
}
public void Initialize(object data)
{
}
public void Reset()
{
}
public uint[] ShowFrame()
{
//Array.Copy(this.back, this.front, this.front.Length);
//return this.front;
return this.back;
}
public void RenderLine(int line)
{
this.curLine = line;
// Render the line
this.dispCnt = Memory.ReadU16(this.memory.IORam, Memory.DISPCNT);
if ((this.dispCnt & (1 << 7)) != 0)
{
uint bgColor = Renderer.GbaTo32((ushort)0x7FFF);
for (int i = 0; i < 240; i++) this.scanline[i] = bgColor;
}
else
{
this.winEnabled = false;
if ((this.dispCnt & (1 << 13)) != 0)
{
// Calculate window 0 information
ushort winy = Memory.ReadU16(this.memory.IORam, Memory.WIN0V);
this.win0y1 = (byte)(winy >> 8);
this.win0y2 = (byte)(winy & 0xff);
ushort winx = Memory.ReadU16(this.memory.IORam, Memory.WIN0H);
this.win0x1 = (byte)(winx >> 8);
this.win0x2 = (byte)(winx & 0xff);
if (this.win0x2 > 240 || this.win0x1 > this.win0x2)
{
this.win0x2 = 240;
}
if (this.win0y2 > 160 || this.win0y1 > this.win0y2)
{
this.win0y2 = 160;
}
this.win0Enabled = this.memory.IORam[Memory.WININ];
this.winEnabled = true;
}
if ((this.dispCnt & (1 << 14)) != 0)
{
// Calculate window 1 information
ushort winy = Memory.ReadU16(this.memory.IORam, Memory.WIN1V);
this.win1y1 = (byte)(winy >> 8);
this.win1y2 = (byte)(winy & 0xff);
ushort winx = Memory.ReadU16(this.memory.IORam, Memory.WIN1H);
this.win1x1 = (byte)(winx >> 8);
this.win1x2 = (byte)(winx & 0xff);
if (this.win1x2 > 240 || this.win1x1 > this.win1x2)
{
this.win1x2 = 240;
}
if (this.win1y2 > 160 || this.win1y1 > this.win1y2)
{
this.win1y2 = 160;
}
this.win1Enabled = this.memory.IORam[Memory.WININ + 1];
this.winEnabled = true;
}
if ((this.dispCnt & (1 << 15)) != 0 && (this.dispCnt & (1 << 12)) != 0)
{
// Object windows are enabled
this.winObjEnabled = this.memory.IORam[Memory.WINOUT + 1];
this.winEnabled = true;
}
if (this.winEnabled)
{
this.winOutEnabled = this.memory.IORam[Memory.WINOUT];
}
// Calculate blending information
ushort bldcnt = Memory.ReadU16(this.memory.IORam, Memory.BLDCNT);
this.blendType = (bldcnt >> 6) & 0x3;
this.blendSource = (byte)(bldcnt & 0x3F);
this.blendTarget = (byte)((bldcnt >> 8) & 0x3F);
ushort bldalpha = Memory.ReadU16(this.memory.IORam, Memory.BLDALPHA);
this.blendA = (byte)(bldalpha & 0x1F);
if (this.blendA > 0x10) this.blendA = 0x10;
this.blendB = (byte)((bldalpha >> 8) & 0x1F);
if (this.blendB > 0x10) this.blendB = 0x10;
this.blendY = (byte)(this.memory.IORam[Memory.BLDY] & 0x1F);
if (this.blendY > 0x10) this.blendY = 0x10;
switch (this.dispCnt & 0x7)
{
case 0: this.RenderMode0Line(); break;
case 1: this.RenderMode1Line(); break;
case 2: this.RenderMode2Line(); break;
case 3: this.RenderMode3Line(); break;
case 4: this.RenderMode4Line(); break;
case 5: this.RenderMode5Line(); break;
}
}
Array.Copy(this.scanline, 0, this.back, this.curLine * Renderer.pitch, Renderer.pitch);
}
private void DrawBackdrop()
{
byte[] palette = this.memory.PaletteRam;
// Initialize window coverage buffer if neccesary
if (this.winEnabled)
{
for (int i = 0; i < 240; i++)
{
this.windowCover[i] = this.winOutEnabled;
}
if ((this.dispCnt & (1 << 15)) != 0)
{
// Sprite window
this.DrawSpriteWindows();
}
if ((this.dispCnt & (1 << 14)) != 0)
{
// Window 1
if (this.curLine >= this.win1y1 && this.curLine < this.win1y2)
{
for (int i = this.win1x1; i < this.win1x2; i++)
{
this.windowCover[i] = this.win1Enabled;
}
}
}
if ((this.dispCnt & (1 << 13)) != 0)
{
// Window 0
if (this.curLine >= this.win0y1 && this.curLine < this.win0y2)
{
for (int i = this.win0x1; i < this.win0x2; i++)
{
this.windowCover[i] = this.win0Enabled;
}
}
}
}
// Draw backdrop first
uint bgColor = Renderer.GbaTo32((ushort)(palette[0] | (palette[1] << 8)));
uint modColor = bgColor;
if (this.blendType == 2 && (this.blendSource & (1 << 5)) != 0)
{
// Brightness increase
uint r = bgColor & 0xFF;
uint g = (bgColor >> 8) & 0xFF;
uint b = (bgColor >> 16) & 0xFF;
r = r + (((0xFF - r) * this.blendY) >> 4);
g = g + (((0xFF - g) * this.blendY) >> 4);
b = b + (((0xFF - b) * this.blendY) >> 4);
modColor = r | (g << 8) | (b << 16);
}
else if (this.blendType == 3 && (this.blendSource & (1 << 5)) != 0)
{
// Brightness decrease
uint r = bgColor & 0xFF;
uint g = (bgColor >> 8) & 0xFF;
uint b = (bgColor >> 16) & 0xFF;
r = r - ((r * this.blendY) >> 4);
g = g - ((g * this.blendY) >> 4);
b = b - ((b * this.blendY) >> 4);
modColor = r | (g << 8) | (b << 16);
}
if (this.winEnabled)
{
for (int i = 0; i < 240; i++)
{
if ((this.windowCover[i] & (1 << 5)) != 0)
{
this.scanline[i] = modColor;
}
else
{
this.scanline[i] = bgColor;
}
this.blend[i] = 1 << 5;
}
}
else
{
for (int i = 0; i < 240; i++)
{
this.scanline[i] = modColor;
this.blend[i] = 1 << 5;
}
}
}
private void RenderTextBg(int bg)
{
if (this.winEnabled)
{
switch (this.blendType)
{
case 0:
this.RenderTextBgWindow(bg);
break;
case 1:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderTextBgWindowBlend(bg);
else
this.RenderTextBgWindow(bg);
break;
case 2:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderTextBgWindowBrightInc(bg);
else
this.RenderTextBgWindow(bg);
break;
case 3:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderTextBgWindowBrightDec(bg);
else
this.RenderTextBgWindow(bg);
break;
}
}
else
{
switch (this.blendType)
{
case 0:
this.RenderTextBgNormal(bg);
break;
case 1:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderTextBgBlend(bg);
else
this.RenderTextBgNormal(bg);
break;
case 2:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderTextBgBrightInc(bg);
else
this.RenderTextBgNormal(bg);
break;
case 3:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderTextBgBrightDec(bg);
else
this.RenderTextBgNormal(bg);
break;
}
}
}
private void RenderRotScaleBg(int bg)
{
if (this.winEnabled)
{
switch (this.blendType)
{
case 0:
this.RenderRotScaleBgWindow(bg);
break;
case 1:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderRotScaleBgWindowBlend(bg);
else
this.RenderRotScaleBgWindow(bg);
break;
case 2:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderRotScaleBgWindowBrightInc(bg);
else
this.RenderRotScaleBgWindow(bg);
break;
case 3:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderRotScaleBgWindowBrightDec(bg);
else
this.RenderRotScaleBgWindow(bg);
break;
}
}
else
{
switch (this.blendType)
{
case 0:
this.RenderRotScaleBgNormal(bg);
break;
case 1:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderRotScaleBgBlend(bg);
else
this.RenderRotScaleBgNormal(bg);
break;
case 2:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderRotScaleBgBrightInc(bg);
else
this.RenderRotScaleBgNormal(bg);
break;
case 3:
if ((this.blendSource & (1 << bg)) != 0)
this.RenderRotScaleBgBrightDec(bg);
else
this.RenderRotScaleBgNormal(bg);
break;
}
}
}
private void DrawSprites(int pri)
{
if (this.winEnabled)
{
switch (this.blendType)
{
case 0:
this.DrawSpritesWindow(pri);
break;
case 1:
if ((this.blendSource & (1 << 4)) != 0)
this.DrawSpritesWindowBlend(pri);
else
this.DrawSpritesWindow(pri);
break;
case 2:
if ((this.blendSource & (1 << 4)) != 0)
this.DrawSpritesWindowBrightInc(pri);
else
this.DrawSpritesWindow(pri);
break;
case 3:
if ((this.blendSource & (1 << 4)) != 0)
this.DrawSpritesWindowBrightDec(pri);
else
this.DrawSpritesWindow(pri);
break;
}
}
else
{
switch (this.blendType)
{
case 0:
this.DrawSpritesNormal(pri);
break;
case 1:
if ((this.blendSource & (1 << 4)) != 0)
this.DrawSpritesBlend(pri);
else
this.DrawSpritesNormal(pri);
break;
case 2:
if ((this.blendSource & (1 << 4)) != 0)
this.DrawSpritesBrightInc(pri);
else
this.DrawSpritesNormal(pri);
break;
case 3:
if ((this.blendSource & (1 << 4)) != 0)
this.DrawSpritesBrightDec(pri);
else
this.DrawSpritesNormal(pri);
break;
}
}
}
private void RenderMode0Line()
{
byte[] palette = this.memory.PaletteRam;
byte[] vram = this.memory.VideoRam;
this.DrawBackdrop();
for (int pri = 3; pri >= 0; pri--)
{
for (int i = 3; i >= 0; i--)
{
if ((this.dispCnt & (1 << (8 + i))) != 0)
{
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);
if ((bgcnt & 0x3) == pri)
{
this.RenderTextBg(i);
}
}
}
this.DrawSprites(pri);
}
}
private void RenderMode1Line()
{
byte[] palette = this.memory.PaletteRam;
byte[] vram = this.memory.VideoRam;
this.DrawBackdrop();
for (int pri = 3; pri >= 0; pri--)
{
if ((this.dispCnt & (1 << (8 + 2))) != 0)
{
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
if ((bgcnt & 0x3) == pri)
{
this.RenderRotScaleBg(2);
}
}
for (int i = 1; i >= 0; i--)
{
if ((this.dispCnt & (1 << (8 + i))) != 0)
{
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);
if ((bgcnt & 0x3) == pri)
{
this.RenderTextBg(i);
}
}
}
this.DrawSprites(pri);
}
}
private void RenderMode2Line()
{
byte[] palette = this.memory.PaletteRam;
byte[] vram = this.memory.VideoRam;
this.DrawBackdrop();
for (int pri = 3; pri >= 0; pri--)
{
for (int i = 3; i >= 2; i--)
{
if ((this.dispCnt & (1 << (8 + i))) != 0)
{
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);
if ((bgcnt & 0x3) == pri)
{
this.RenderRotScaleBg(i);
}
}
}
this.DrawSprites(pri);
}
}
private void RenderMode3Line()
{
ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
byte[] palette = this.memory.PaletteRam;
byte[] vram = this.memory.VideoRam;
this.DrawBackdrop();
byte blendMaskType = (byte)(1 << 2);
int bgPri = bg2Cnt & 0x3;
for (int pri = 3; pri > bgPri; pri--)
{
this.DrawSprites(pri);
}
if ((this.dispCnt & (1 << 10)) != 0)
{
// Background enabled, render it
int x = this.memory.Bgx[0];
int y = this.memory.Bgy[0];
short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);
for (int i = 0; i < 240; i++)
{
int ax = ((int)x) >> 8;
int ay = ((int)y) >> 8;
if (ax >= 0 && ax < 240 && ay >= 0 && ay < 160)
{
int curIdx = ((ay * 240) + ax) * 2;
this.scanline[i] = Renderer.GbaTo32((ushort)(vram[curIdx] | (vram[curIdx + 1] << 8)));
this.blend[i] = blendMaskType;
}
x += dx;
y += dy;
}
}
for (int pri = bgPri; pri >= 0; pri--)
{
this.DrawSprites(pri);
}
}
private void RenderMode4Line()
{
ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
byte[] palette = this.memory.PaletteRam;
byte[] vram = this.memory.VideoRam;
this.DrawBackdrop();
byte blendMaskType = (byte)(1 << 2);
int bgPri = bg2Cnt & 0x3;
for (int pri = 3; pri > bgPri; pri--)
{
this.DrawSprites(pri);
}
if ((this.dispCnt & (1 << 10)) != 0)
{
// Background enabled, render it
int baseIdx = 0;
if ((this.dispCnt & (1 << 4)) == 1 << 4) baseIdx = 0xA000;
int x = this.memory.Bgx[0];
int y = this.memory.Bgy[0];
short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);
for (int i = 0; i < 240; i++)
{
int ax = ((int)x) >> 8;
int ay = ((int)y) >> 8;
if (ax >= 0 && ax < 240 && ay >= 0 && ay < 160)
{
int lookup = vram[baseIdx + (ay * 240) + ax];
if (lookup != 0)
{
this.scanline[i] = Renderer.GbaTo32((ushort)(palette[lookup * 2] | (palette[lookup * 2 + 1] << 8)));
this.blend[i] = blendMaskType;
}
}
x += dx;
y += dy;
}
}
for (int pri = bgPri; pri >= 0; pri--)
{
this.DrawSprites(pri);
}
}
private void RenderMode5Line()
{
ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
byte[] palette = this.memory.PaletteRam;
byte[] vram = this.memory.VideoRam;
this.DrawBackdrop();
byte blendMaskType = (byte)(1 << 2);
int bgPri = bg2Cnt & 0x3;
for (int pri = 3; pri > bgPri; pri--)
{
this.DrawSprites(pri);
}
if ((this.dispCnt & (1 << 10)) != 0)
{
// Background enabled, render it
int baseIdx = 0;
if ((this.dispCnt & (1 << 4)) == 1 << 4) baseIdx += 160 * 128 * 2;
int x = this.memory.Bgx[0];
int y = this.memory.Bgy[0];
short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);
for (int i = 0; i < 240; i++)
{
int ax = ((int)x) >> 8;
int ay = ((int)y) >> 8;
if (ax >= 0 && ax < 160 && ay >= 0 && ay < 128)
{
int curIdx = (int)(ay * 160 + ax) * 2;
this.scanline[i] = Renderer.GbaTo32((ushort)(vram[baseIdx + curIdx] | (vram[baseIdx + curIdx + 1] << 8)));
this.blend[i] = blendMaskType;
}
x += dx;
y += dy;
}
}
for (int pri = bgPri; pri >= 0; pri--)
{
this.DrawSprites(pri);
}
}
private void DrawSpriteWindows()
{
byte[] palette = this.memory.PaletteRam;
byte[] vram = this.memory.VideoRam;
// OBJ must be enabled in this.dispCnt
if ((this.dispCnt & (1 << 12)) == 0) return;
for (int oamNum = 127; oamNum >= 0; oamNum--)
{
ushort attr0 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 0);
// Not an object window, so continue
if (((attr0 >> 10) & 3) != 2) continue;
ushort attr1 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 2);
ushort attr2 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 4);
int x = attr1 & 0x1FF;
int y = attr0 & 0xFF;
int width = -1, height = -1;
switch ((attr0 >> 14) & 3)
{
case 0:
// Square
switch ((attr1 >> 14) & 3)
{
case 0: width = 8; height = 8; break;
case 1: width = 16; height = 16; break;
case 2: width = 32; height = 32; break;
case 3: width = 64; height = 64; break;
}
break;
case 1:
// Horizontal Rectangle
switch ((attr1 >> 14) & 3)
{
case 0: width = 16; height = 8; break;
case 1: width = 32; height = 8; break;
case 2: width = 32; height = 16; break;
case 3: width = 64; height = 32; break;
}
break;
case 2:
// Vertical Rectangle
switch ((attr1 >> 14) & 3)
{
case 0: width = 8; height = 16; break;
case 1: width = 8; height = 32; break;
case 2: width = 16; height = 32; break;
case 3: width = 32; height = 64; break;
}
break;
}
// Check double size flag here
int rwidth = width, rheight = height;
if ((attr0 & (1 << 8)) != 0)
{
// Rot-scale on
if ((attr0 & (1 << 9)) != 0)
{
rwidth *= 2;
rheight *= 2;
}
}
else
{
// Invalid sprite
if ((attr0 & (1 << 9)) != 0)
width = -1;
}
if (width == -1)
{
// Invalid sprite
continue;
}
// Y clipping
if (y > ((y + rheight) & 0xff))
{
if (this.curLine >= ((y + rheight) & 0xff) && !(y < this.curLine)) continue;
}
else
{
if (this.curLine < y || this.curLine >= ((y + rheight) & 0xff)) continue;
}
int scale = 1;
if ((attr0 & (1 << 13)) != 0) scale = 2;
int spritey = this.curLine - y;
if (spritey < 0) spritey += 256;
if ((attr0 & (1 << 8)) == 0)
{
if ((attr1 & (1 << 13)) != 0) spritey = (height - 1) - spritey;
int baseSprite;
if ((this.dispCnt & (1 << 6)) != 0)
{
// 1 dimensional
baseSprite = (attr2 & 0x3FF) + ((spritey / 8) * (width / 8)) * scale;
}
else
{
// 2 dimensional
baseSprite = (attr2 & 0x3FF) + ((spritey / 8) * 0x20);
}
int baseInc = scale;
if ((attr1 & (1 << 12)) != 0)
{
baseSprite += ((width / 8) * scale) - scale;
baseInc = -baseInc;
}
if ((attr0 & (1 << 13)) != 0)
{
// 256 colors
for (int i = x; i < x + width; i++)
{
if ((i & 0x1ff) < 240)
{
int tx = (i - x) & 7;
if ((attr1 & (1 << 12)) != 0) tx = 7 - tx;
int curIdx = baseSprite * 32 + ((spritey & 7) * 8) + tx;
int lookup = vram[0x10000 + curIdx];
if (lookup != 0)
{
this.windowCover[i & 0x1ff] = this.winObjEnabled;
}
}
if (((i - x) & 7) == 7) baseSprite += baseInc;
}
}
else
{
// 16 colors
int palIdx = 0x200 + (((attr2 >> 12) & 0xF) * 16 * 2);
for (int i = x; i < x + width; i++)
{
if ((i & 0x1ff) < 240)
{
int tx = (i - x) & 7;
if ((attr1 & (1 << 12)) != 0) tx = 7 - tx;
int curIdx = baseSprite * 32 + ((spritey & 7) * 4) + (tx / 2);
int lookup = vram[0x10000 + curIdx];
if ((tx & 1) == 0)
{
lookup &= 0xf;
}
else
{
lookup >>= 4;
}
if (lookup != 0)
{
this.windowCover[i & 0x1ff] = this.winObjEnabled;
}
}
if (((i - x) & 7) == 7) baseSprite += baseInc;
}
}
}
else
{
int rotScaleParam = (attr1 >> 9) & 0x1F;
short dx = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x6);
short dmx = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0xE);
short dy = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x16);
short dmy = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x1E);
int cx = rwidth / 2;
int cy = rheight / 2;
int baseSprite = attr2 & 0x3FF;
int pitch;
if ((this.dispCnt & (1 << 6)) != 0)
{
// 1 dimensional
pitch = (width / 8) * scale;
}
else
{
// 2 dimensional
pitch = 0x20;
}
short rx = (short)((dmx * (spritey - cy)) - (cx * dx) + (width << 7));
short ry = (short)((dmy * (spritey - cy)) - (cx * dy) + (height << 7));
// Draw a rot/scale sprite
if ((attr0 & (1 << 13)) != 0)
{
// 256 colors
for (int i = x; i < x + rwidth; i++)
{
int tx = rx >> 8;
int ty = ry >> 8;
if ((i & 0x1ff) < 240 && tx >= 0 && tx < width && ty >= 0 && ty < height)
{
int curIdx = (baseSprite + ((ty / 8) * pitch) + ((tx / 8) * scale)) * 32 + ((ty & 7) * 8) + (tx & 7);
int lookup = vram[0x10000 + curIdx];
if (lookup != 0)
{
this.windowCover[i & 0x1ff] = this.winObjEnabled;
}
}
rx += dx;
ry += dy;
}
}
else
{
// 16 colors
int palIdx = 0x200 + (((attr2 >> 12) & 0xF) * 16 * 2);
for (int i = x; i < x + rwidth; i++)
{
int tx = rx >> 8;
int ty = ry >> 8;
if ((i & 0x1ff) < 240 && tx >= 0 && tx < width && ty >= 0 && ty < height)
{
int curIdx = (baseSprite + ((ty / 8) * pitch) + ((tx / 8) * scale)) * 32 + ((ty & 7) * 4) + ((tx & 7) / 2);
int lookup = vram[0x10000 + curIdx];
if ((tx & 1) == 0)
{
lookup &= 0xf;
}
else
{
lookup >>= 4;
}
if (lookup != 0)
{
this.windowCover[i & 0x1ff] = this.winObjEnabled;
}
}
rx += dx;
ry += dy;
}
}
}
}
}
public static uint GbaTo32(ushort color)
{
// more accurate, but slower :(
// return colorLUT[color];
return ((color & 0x1FU) << 19) | ((color & 0x3E0U) << 6) | ((color & 0x7C00U) >> 7);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,191 @@
namespace GarboDev
{
using System;
using System.Collections.Generic;
using System.Text;
public class SoundManager
{
private Memory memory = null;
private Queue<byte>[] soundQueue = new Queue<byte>[2];
private byte latchedA, latchedB;
private int frequency, cyclesPerSample;
private int leftover = 0;
private short[] soundBuffer = new short[40000];
private int soundBufferPos = 0;
private int lastSoundBufferPos = 0;
public SoundManager(Memory memory, int frequency)
{
this.Frequency = frequency;
this.memory = memory;
this.memory.SoundManager = this;
this.soundQueue[0] = new Queue<byte>(32);
this.soundQueue[1] = new Queue<byte>(32);
}
#region Public Properties
public int Frequency
{
get { return this.frequency; }
set
{
this.frequency = value;
this.cyclesPerSample = (GbaManager.cpuFreq << 5) / this.frequency;
}
}
public int QueueSizeA
{
get { return this.soundQueue[0].Count; }
}
public int QueueSizeB
{
get { return this.soundQueue[1].Count; }
}
public int SamplesMixed
{
get
{
int value = this.soundBufferPos - this.lastSoundBufferPos;
if (value < 0) value += this.soundBuffer.Length;
return value;
}
}
#endregion
#region Public Methods
public void GetSamples(short[] buffer, int length)
{
for (int i = 0; i < length; i++)
{
if (this.lastSoundBufferPos == this.soundBuffer.Length)
{
this.lastSoundBufferPos = 0;
}
buffer[i] = this.soundBuffer[this.lastSoundBufferPos++];
}
}
public void Mix(int cycles)
{
ushort soundCntH = Memory.ReadU16(this.memory.IORam, Memory.SOUNDCNT_H);
ushort soundCntX = Memory.ReadU16(this.memory.IORam, Memory.SOUNDCNT_X);
cycles <<= 5;
cycles += this.leftover;
if (cycles > 0)
{
// Precompute loop invariants
short directA = (short)(sbyte)(this.latchedA);
short directB = (short)(sbyte)(this.latchedB);
if ((soundCntH & (1 << 2)) == 0)
{
directA >>= 1;
}
if ((soundCntH & (1 << 3)) == 0)
{
directB >>= 1;
}
while (cycles > 0)
{
short l = 0, r = 0;
cycles -= this.cyclesPerSample;
// Mixing
if ((soundCntX & (1 << 7)) != 0)
{
if ((soundCntH & (1 << 8)) != 0)
{
r += directA;
}
if ((soundCntH & (1 << 9)) != 0)
{
l += directA;
}
if ((soundCntH & (1 << 12)) != 0)
{
r += directB;
}
if ((soundCntH & (1 << 13)) != 0)
{
l += directB;
}
}
if (this.soundBufferPos == this.soundBuffer.Length)
{
this.soundBufferPos = 0;
}
this.soundBuffer[this.soundBufferPos++] = (short)(l << 6);
this.soundBuffer[this.soundBufferPos++] = (short)(r << 6);
}
}
this.leftover = cycles;
}
public void ResetFifoA()
{
this.soundQueue[0].Clear();
this.latchedA = 0;
}
public void ResetFifoB()
{
this.soundQueue[1].Clear();
this.latchedB = 0;
}
public void IncrementFifoA()
{
for (int i = 0; i < 4; i++)
{
this.EnqueueDSoundSample(0, this.memory.IORam[Memory.FIFO_A_L + i]);
}
}
public void IncrementFifoB()
{
for (int i = 0; i < 4; i++)
{
this.EnqueueDSoundSample(1, this.memory.IORam[Memory.FIFO_B_L + i]);
}
}
public void DequeueA()
{
if (this.soundQueue[0].Count > 0)
{
this.latchedA = this.soundQueue[0].Dequeue();
}
}
public void DequeueB()
{
if (this.soundQueue[1].Count > 0)
{
this.latchedB = this.soundQueue[1].Dequeue();
}
}
#endregion Public Methods
private void EnqueueDSoundSample(int channel, byte sample)
{
if (this.soundQueue[channel].Count < 32)
{
this.soundQueue[channel].Enqueue(sample);
}
}
}
}

View File

@ -0,0 +1,981 @@
//#define ARM_DEBUG
namespace GarboDev
{
using System;
using System.Collections.Generic;
using System.Text;
public class ThumbCore
{
private const int COND_EQ = 0; // Z set
private const int COND_NE = 1; // Z clear
private const int COND_CS = 2; // C set
private const int COND_CC = 3; // C clear
private const int COND_MI = 4; // N set
private const int COND_PL = 5; // N clear
private const int COND_VS = 6; // V set
private const int COND_VC = 7; // V clear
private const int COND_HI = 8; // C set and Z clear
private const int COND_LS = 9; // C clear or Z set
private const int COND_GE = 10; // N equals V
private const int COND_LT = 11; // N not equal to V
private const int COND_GT = 12; // Z clear AND (N equals V)
private const int COND_LE = 13; // Z set OR (N not equal to V)
private const int COND_AL = 14; // Always
private const int COND_NV = 15; // Never execute
private const int OP_AND = 0x0;
private const int OP_EOR = 0x1;
private const int OP_LSL = 0x2;
private const int OP_LSR = 0x3;
private const int OP_ASR = 0x4;
private const int OP_ADC = 0x5;
private const int OP_SBC = 0x6;
private const int OP_ROR = 0x7;
private const int OP_TST = 0x8;
private const int OP_NEG = 0x9;
private const int OP_CMP = 0xA;
private const int OP_CMN = 0xB;
private const int OP_ORR = 0xC;
private const int OP_MUL = 0xD;
private const int OP_BIC = 0xE;
private const int OP_MVN = 0xF;
private Arm7Processor parent;
private Memory memory;
private uint[] registers;
// CPU flags
private uint zero, carry, negative, overflow;
private ushort curInstruction, instructionQueue;
private delegate void ExecuteInstruction();
private ExecuteInstruction[] NormalOps = null;
public ThumbCore(Arm7Processor parent, Memory memory)
{
this.parent = parent;
this.memory = memory;
this.registers = this.parent.Registers;
this.NormalOps = new ExecuteInstruction[256]
{
OpLslImm, OpLslImm, OpLslImm, OpLslImm, OpLslImm, OpLslImm, OpLslImm, OpLslImm,
OpLsrImm, OpLsrImm, OpLsrImm, OpLsrImm, OpLsrImm, OpLsrImm, OpLsrImm, OpLsrImm,
OpAsrImm, OpAsrImm, OpAsrImm, OpAsrImm, OpAsrImm, OpAsrImm, OpAsrImm, OpAsrImm,
OpAddRegReg, OpAddRegReg, OpSubRegReg, OpSubRegReg, OpAddRegImm, OpAddRegImm, OpSubRegImm, OpSubRegImm,
OpMovImm, OpMovImm, OpMovImm, OpMovImm, OpMovImm, OpMovImm, OpMovImm, OpMovImm,
OpCmpImm, OpCmpImm, OpCmpImm, OpCmpImm, OpCmpImm, OpCmpImm, OpCmpImm, OpCmpImm,
OpAddImm, OpAddImm, OpAddImm, OpAddImm, OpAddImm, OpAddImm, OpAddImm, OpAddImm,
OpSubImm, OpSubImm, OpSubImm, OpSubImm, OpSubImm, OpSubImm, OpSubImm, OpSubImm,
OpArith, OpArith, OpArith, OpArith, OpAddHi, OpCmpHi, OpMovHi, OpBx,
OpLdrPc, OpLdrPc, OpLdrPc, OpLdrPc, OpLdrPc, OpLdrPc, OpLdrPc, OpLdrPc,
OpStrReg, OpStrReg, OpStrhReg, OpStrhReg, OpStrbReg, OpStrbReg, OpLdrsbReg, OpLdrsbReg,
OpLdrReg, OpLdrReg, OpLdrhReg, OpLdrhReg, OpLdrbReg, OpLdrbReg, OpLdrshReg, OpLdrshReg,
OpStrImm, OpStrImm, OpStrImm, OpStrImm, OpStrImm, OpStrImm, OpStrImm, OpStrImm,
OpLdrImm, OpLdrImm, OpLdrImm, OpLdrImm, OpLdrImm, OpLdrImm, OpLdrImm, OpLdrImm,
OpStrbImm, OpStrbImm, OpStrbImm, OpStrbImm, OpStrbImm, OpStrbImm, OpStrbImm, OpStrbImm,
OpLdrbImm, OpLdrbImm, OpLdrbImm, OpLdrbImm, OpLdrbImm, OpLdrbImm, OpLdrbImm, OpLdrbImm,
OpStrhImm, OpStrhImm, OpStrhImm, OpStrhImm, OpStrhImm, OpStrhImm, OpStrhImm, OpStrhImm,
OpLdrhImm, OpLdrhImm, OpLdrhImm, OpLdrhImm, OpLdrhImm, OpLdrhImm, OpLdrhImm, OpLdrhImm,
OpStrSp, OpStrSp, OpStrSp, OpStrSp, OpStrSp, OpStrSp, OpStrSp, OpStrSp,
OpLdrSp, OpLdrSp, OpLdrSp, OpLdrSp, OpLdrSp, OpLdrSp, OpLdrSp, OpLdrSp,
OpAddPc, OpAddPc, OpAddPc, OpAddPc, OpAddPc, OpAddPc, OpAddPc, OpAddPc,
OpAddSp, OpAddSp, OpAddSp, OpAddSp, OpAddSp, OpAddSp, OpAddSp, OpAddSp,
OpSubSp, OpUnd, OpUnd, OpUnd, OpPush, OpPushLr, OpUnd, OpUnd,
OpUnd, OpUnd, OpUnd, OpUnd, OpPop, OpPopPc, OpUnd, OpUnd,
OpStmia, OpStmia, OpStmia, OpStmia, OpStmia, OpStmia, OpStmia, OpStmia,
OpLdmia, OpLdmia, OpLdmia, OpLdmia, OpLdmia, OpLdmia, OpLdmia, OpLdmia,
OpBCond, OpBCond, OpBCond, OpBCond, OpBCond, OpBCond, OpBCond, OpBCond,
OpBCond, OpBCond, OpBCond, OpBCond, OpBCond, OpBCond, OpUnd, OpSwi,
OpB, OpB, OpB, OpB, OpB, OpB, OpB, OpB,
OpUnd, OpUnd, OpUnd, OpUnd, OpUnd, OpUnd, OpUnd, OpUnd,
OpBl1, OpBl1, OpBl1, OpBl1, OpBl1, OpBl1, OpBl1, OpBl1,
OpBl2, OpBl2, OpBl2, OpBl2, OpBl2, OpBl2, OpBl2, OpBl2
};
}
public void BeginExecution()
{
this.FlushQueue();
}
public void Step()
{
this.UnpackFlags();
this.curInstruction = this.instructionQueue;
this.instructionQueue = this.memory.ReadU16(registers[15]);
registers[15] += 2;
// Execute the instruction
this.NormalOps[this.curInstruction >> 8]();
this.parent.Cycles -= this.memory.WaitCycles;
if ((this.parent.CPSR & Arm7Processor.T_MASK) != Arm7Processor.T_MASK)
{
if ((this.curInstruction >> 8) != 0xDF) this.parent.ReloadQueue();
}
this.PackFlags();
}
public void Execute()
{
this.UnpackFlags();
while (this.parent.Cycles > 0)
{
this.curInstruction = this.instructionQueue;
this.instructionQueue = this.memory.ReadU16(registers[15]);
registers[15] += 2;
// Execute the instruction
this.NormalOps[this.curInstruction >> 8]();
this.parent.Cycles -= this.memory.WaitCycles;
if ((this.parent.CPSR & Arm7Processor.T_MASK) != Arm7Processor.T_MASK)
{
if ((this.curInstruction >> 8) != 0xDF) this.parent.ReloadQueue();
break;
}
// Check the current PC
#if ARM_DEBUG
if (this.parent.Breakpoints.ContainsKey(registers[15] - 2U))
{
this.parent.BreakpointHit = true;
break;
}
#endif
}
this.PackFlags();
}
#region Flag helpers
public void OverflowCarryAdd(uint a, uint b, uint r)
{
overflow = ((a & b & ~r) | (~a & ~b & r)) >> 31;
carry = ((a & b) | (a & ~r) | (b & ~r)) >> 31;
}
public void OverflowCarrySub(uint a, uint b, uint r)
{
overflow = ((a & ~b & ~r) | (~a & b & r)) >> 31;
carry = ((a & ~b) | (a & ~r) | (~b & ~r)) >> 31;
}
#endregion
#region Opcodes
private void OpLslImm()
{
// 0x00 - 0x07
// lsl rd, rm, #immed
int rd = this.curInstruction & 0x7;
int rm = (this.curInstruction >> 3) & 0x7;
int immed = (this.curInstruction >> 6) & 0x1F;
if (immed == 0)
{
registers[rd] = registers[rm];
} else
{
carry = (registers[rm] >> (32 - immed)) & 0x1;
registers[rd] = registers[rm] << immed;
}
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpLsrImm()
{
// 0x08 - 0x0F
// lsr rd, rm, #immed
int rd = this.curInstruction & 0x7;
int rm = (this.curInstruction >> 3) & 0x7;
int immed = (this.curInstruction >> 6) & 0x1F;
if (immed == 0)
{
carry = registers[rm] >> 31;
registers[rd] = 0;
}
else
{
carry = (registers[rm] >> (immed - 1)) & 0x1;
registers[rd] = registers[rm] >> immed;
}
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpAsrImm()
{
// asr rd, rm, #immed
int rd = this.curInstruction & 0x7;
int rm = (this.curInstruction >> 3) & 0x7;
int immed = (this.curInstruction >> 6) & 0x1F;
if (immed == 0)
{
carry = registers[rm] >> 31;
if (carry == 1) registers[rd] = 0xFFFFFFFF;
else registers[rd] = 0;
}
else
{
carry = (registers[rm] >> (immed - 1)) & 0x1;
registers[rd] = (uint)(((int)registers[rm]) >> immed);
}
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpAddRegReg()
{
// add rd, rn, rm
int rd = this.curInstruction & 0x7;
int rn = (this.curInstruction >> 3) & 0x7;
int rm = (this.curInstruction >> 6) & 0x7;
uint orn = registers[rn];
uint orm = registers[rm];
registers[rd] = orn + orm;
this.OverflowCarryAdd(orn, orm, registers[rd]);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpSubRegReg()
{
// sub rd, rn, rm
int rd = this.curInstruction & 0x7;
int rn = (this.curInstruction >> 3) & 0x7;
int rm = (this.curInstruction >> 6) & 0x7;
uint orn = registers[rn];
uint orm = registers[rm];
registers[rd] = orn - orm;
this.OverflowCarrySub(orn, orm, registers[rd]);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpAddRegImm()
{
// add rd, rn, #immed
int rd = this.curInstruction & 0x7;
int rn = (this.curInstruction >> 3) & 0x7;
uint immed = (uint)((this.curInstruction >> 6) & 0x7);
uint orn = registers[rn];
registers[rd] = orn + immed;
this.OverflowCarryAdd(orn, immed, registers[rd]);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpSubRegImm()
{
// sub rd, rn, #immed
int rd = this.curInstruction & 0x7;
int rn = (this.curInstruction >> 3) & 0x7;
uint immed = (uint)((this.curInstruction >> 6) & 0x7);
uint orn = registers[rn];
registers[rd] = orn - immed;
this.OverflowCarrySub(orn, immed, registers[rd]);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpMovImm()
{
// mov rd, #immed
int rd = (this.curInstruction >> 8) & 0x7;
registers[rd] = (uint)(this.curInstruction & 0xFF);
negative = 0;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpCmpImm()
{
// cmp rn, #immed
int rn = (this.curInstruction >> 8) & 0x7;
uint alu = registers[rn] - (uint)(this.curInstruction & 0xFF);
this.OverflowCarrySub(registers[rn], (uint)(this.curInstruction & 0xFF), alu);
negative = alu >> 31;
zero = alu == 0 ? 1U : 0U;
}
private void OpAddImm()
{
// add rd, #immed
int rd = (this.curInstruction >> 8) & 0x7;
uint ord = registers[rd];
registers[rd] += (uint)(this.curInstruction & 0xFF);
this.OverflowCarryAdd(ord, (uint)(this.curInstruction & 0xFF), registers[rd]);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpSubImm()
{
// sub rd, #immed
int rd = (this.curInstruction >> 8) & 0x7;
uint ord = registers[rd];
registers[rd] -= (uint)(this.curInstruction & 0xFF);
this.OverflowCarrySub(ord, (uint)(this.curInstruction & 0xFF), registers[rd]);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
}
private void OpArith()
{
int rd = this.curInstruction & 0x7;
uint rn = registers[(this.curInstruction >> 3) & 0x7];
uint orig, alu;
int shiftAmt;
switch ((this.curInstruction >> 6) & 0xF)
{
case OP_ADC:
orig = registers[rd];
registers[rd] += rn + carry;
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
this.OverflowCarryAdd(orig, rn, registers[rd]);
break;
case OP_AND:
registers[rd] &= rn;
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_ASR:
shiftAmt = (int)(rn & 0xFF);
if (shiftAmt == 0)
{
// Do nothing
}
else if (shiftAmt < 32)
{
carry = (registers[rd] >> (shiftAmt - 1)) & 0x1;
registers[rd] = (uint)(((int)registers[rd]) >> shiftAmt);
}
else
{
carry = (registers[rd] >> 31) & 1;
if (carry == 1) registers[rd] = 0xFFFFFFFF;
else registers[rd] = 0;
}
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_BIC:
registers[rd] &= ~rn;
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_CMN:
alu = registers[rd] + rn;
negative = alu >> 31;
zero = alu == 0 ? 1U : 0U;
this.OverflowCarryAdd(registers[rd], rn, alu);
break;
case OP_CMP:
alu = registers[rd] - rn;
negative = alu >> 31;
zero = alu == 0 ? 1U : 0U;
this.OverflowCarrySub(registers[rd], rn, alu);
break;
case OP_EOR:
registers[rd] ^= rn;
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_LSL:
shiftAmt = (int)(rn & 0xFF);
if (shiftAmt == 0)
{
// Do nothing
}
else if (shiftAmt < 32)
{
carry = (registers[rd] >> (32 - shiftAmt)) & 0x1;
registers[rd] <<= shiftAmt;
}
else if (shiftAmt == 32)
{
carry = registers[rd] & 0x1;
registers[rd] = 0;
}
else
{
carry = 0;
registers[rd] = 0;
}
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_LSR:
shiftAmt = (int)(rn & 0xFF);
if (shiftAmt == 0)
{
// Do nothing
}
else if (shiftAmt < 32)
{
carry = (registers[rd] >> (shiftAmt - 1)) & 0x1;
registers[rd] >>= shiftAmt;
}
else if (shiftAmt == 32)
{
carry = (registers[rd] >> 31) & 0x1;
registers[rd] = 0;
}
else
{
carry = 0;
registers[rd] = 0;
}
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_MUL:
int mulCycles = 4;
// Multiply cycle calculations
if ((rn & 0xFFFFFF00) == 0 || (rn & 0xFFFFFF00) == 0xFFFFFF00)
{
mulCycles = 1;
}
else if ((rn & 0xFFFF0000) == 0 || (rn & 0xFFFF0000) == 0xFFFF0000)
{
mulCycles = 2;
}
else if ((rn & 0xFF000000) == 0 || (rn & 0xFF000000) == 0xFF000000)
{
mulCycles = 3;
}
this.parent.Cycles -= mulCycles;
registers[rd] *= rn;
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_MVN:
registers[rd] = ~rn;
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_NEG:
registers[rd] = 0 - rn;
this.OverflowCarrySub(0, rn, registers[rd]);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_ORR:
registers[rd] |= rn;
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_ROR:
shiftAmt = (int)(rn & 0xFF);
if (shiftAmt == 0)
{
// Do nothing
}
else if ((shiftAmt & 0x1F) == 0)
{
carry = registers[rd] >> 31;
}
else
{
shiftAmt &= 0x1F;
carry = (registers[rd] >> (shiftAmt - 1)) & 0x1;
registers[rd] = (registers[rd] >> shiftAmt) | (registers[rd] << (32 - shiftAmt));
}
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
break;
case OP_SBC:
orig = registers[rd];
registers[rd] = (registers[rd] - rn) - (1U - carry);
negative = registers[rd] >> 31;
zero = registers[rd] == 0 ? 1U : 0U;
this.OverflowCarrySub(orig, rn, registers[rd]);
break;
case OP_TST:
alu = registers[rd] & rn;
negative = alu >> 31;
zero = alu == 0 ? 1U : 0U;
break;
default:
throw new Exception("The coder screwed up on the thumb alu op...");
}
}
private void OpAddHi()
{
int rd = ((this.curInstruction & (1 << 7)) >> 4) | (this.curInstruction & 0x7);
int rm = (this.curInstruction >> 3) & 0xF;
registers[rd] += registers[rm];
if (rd == 15)
{
registers[rd] &= ~1U;
this.FlushQueue();
}
}
private void OpCmpHi()
{
int rd = ((this.curInstruction & (1 << 7)) >> 4) | (this.curInstruction & 0x7);
int rm = (this.curInstruction >> 3) & 0xF;
uint alu = registers[rd] - registers[rm];
negative = alu >> 31;
zero = alu == 0 ? 1U : 0U;
this.OverflowCarrySub(registers[rd], registers[rm], alu);
}
private void OpMovHi()
{
int rd = ((this.curInstruction & (1 << 7)) >> 4) | (this.curInstruction & 0x7);
int rm = (this.curInstruction >> 3) & 0xF;
registers[rd] = registers[rm];
if (rd == 15)
{
registers[rd] &= ~1U;
this.FlushQueue();
}
}
private void OpBx()
{
int rm = (this.curInstruction >> 3) & 0xf;
this.PackFlags();
this.parent.CPSR &= ~Arm7Processor.T_MASK;
this.parent.CPSR |= (registers[rm] & 1) << Arm7Processor.T_BIT;
registers[15] = registers[rm] & (~1U);
this.UnpackFlags();
// Check for branch back to Arm Mode
if ((this.parent.CPSR & Arm7Processor.T_MASK) != Arm7Processor.T_MASK)
{
return;
}
this.FlushQueue();
}
private void OpLdrPc()
{
int rd = (this.curInstruction >> 8) & 0x7;
registers[rd] = this.memory.ReadU32((registers[15] & ~2U) + (uint)((this.curInstruction & 0xFF) * 4));
this.parent.Cycles--;
}
private void OpStrReg()
{
this.memory.WriteU32(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7],
registers[this.curInstruction & 0x7]);
}
private void OpStrhReg()
{
this.memory.WriteU16(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7],
(ushort)(registers[this.curInstruction & 0x7] & 0xFFFF));
}
private void OpStrbReg()
{
this.memory.WriteU8(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7],
(byte)(registers[this.curInstruction & 0x7] & 0xFF));
}
private void OpLdrsbReg()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU8(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7]);
if ((registers[this.curInstruction & 0x7] & (1 << 7)) != 0)
{
registers[this.curInstruction & 0x7] |= 0xFFFFFF00;
}
this.parent.Cycles--;
}
private void OpLdrReg()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU32(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7]);
this.parent.Cycles--;
}
private void OpLdrhReg()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU16(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7]);
this.parent.Cycles--;
}
private void OpLdrbReg()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU8(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7]);
this.parent.Cycles--;
}
private void OpLdrshReg()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU16(registers[(this.curInstruction >> 3) & 0x7] + registers[(this.curInstruction >> 6) & 0x7]);
if ((registers[this.curInstruction & 0x7] & (1 << 15)) != 0)
{
registers[this.curInstruction & 0x7] |= 0xFFFF0000;
}
this.parent.Cycles--;
}
private void OpStrImm()
{
this.memory.WriteU32(registers[(this.curInstruction >> 3) & 0x7] + (uint)(((this.curInstruction >> 6) & 0x1F) * 4),
registers[this.curInstruction & 0x7]);
}
private void OpLdrImm()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU32(registers[(this.curInstruction >> 3) & 0x7] + (uint)(((this.curInstruction >> 6) & 0x1F) * 4));
this.parent.Cycles--;
}
private void OpStrbImm()
{
this.memory.WriteU8(registers[(this.curInstruction >> 3) & 0x7] + (uint)((this.curInstruction >> 6) & 0x1F),
(byte)(registers[this.curInstruction & 0x7] & 0xFF));
}
private void OpLdrbImm()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU8(registers[(this.curInstruction >> 3) & 0x7] + (uint)((this.curInstruction >> 6) & 0x1F));
this.parent.Cycles--;
}
private void OpStrhImm()
{
this.memory.WriteU16(registers[(this.curInstruction >> 3) & 0x7] + (uint)(((this.curInstruction >> 6) & 0x1F) * 2),
(ushort)(registers[this.curInstruction & 0x7] & 0xFFFF));
}
private void OpLdrhImm()
{
registers[this.curInstruction & 0x7] =
this.memory.ReadU16(registers[(this.curInstruction >> 3) & 0x7] + (uint)(((this.curInstruction >> 6) & 0x1F) * 2));
this.parent.Cycles--;
}
private void OpStrSp()
{
this.memory.WriteU32(registers[13] + (uint)((this.curInstruction & 0xFF) * 4),
registers[(this.curInstruction >> 8) & 0x7]);
}
private void OpLdrSp()
{
registers[(this.curInstruction >> 8) & 0x7] =
this.memory.ReadU32(registers[13] + (uint)((this.curInstruction & 0xFF) * 4));
}
private void OpAddPc()
{
registers[(this.curInstruction >> 8) & 0x7] =
(registers[15] & ~2U) + (uint)((this.curInstruction & 0xFF) * 4);
}
private void OpAddSp()
{
registers[(this.curInstruction >> 8) & 0x7] =
registers[13] + (uint)((this.curInstruction & 0xFF) * 4);
}
private void OpSubSp()
{
if ((this.curInstruction & (1 << 7)) != 0)
registers[13] -= (uint)((this.curInstruction & 0x7F) * 4);
else
registers[13] += (uint)((this.curInstruction & 0x7F) * 4);
}
private void OpPush()
{
for (int i = 7; i >= 0; i--)
{
if (((this.curInstruction >> i) & 1) != 0)
{
registers[13] -= 4;
this.memory.WriteU32(registers[13], registers[i]);
}
}
}
private void OpPushLr()
{
registers[13] -= 4;
this.memory.WriteU32(registers[13], registers[14]);
for (int i = 7; i >= 0; i--)
{
if (((this.curInstruction >> i) & 1) != 0)
{
registers[13] -= 4;
this.memory.WriteU32(registers[13], registers[i]);
}
}
}
private void OpPop()
{
for (int i = 0; i < 8; i++)
{
if (((this.curInstruction >> i) & 1) != 0)
{
registers[i] = this.memory.ReadU32(registers[13]);
registers[13] += 4;
}
}
this.parent.Cycles--;
}
private void OpPopPc()
{
for (int i = 0; i < 8; i++)
{
if (((this.curInstruction >> i) & 1) != 0)
{
registers[i] = this.memory.ReadU32(registers[13]);
registers[13] += 4;
}
}
registers[15] = this.memory.ReadU32(registers[13]) & (~1U);
registers[13] += 4;
// ARM9 check here
this.FlushQueue();
this.parent.Cycles--;
}
private void OpStmia()
{
int rn = (this.curInstruction >> 8) & 0x7;
for (int i = 0; i < 8; i++)
{
if (((this.curInstruction >> i) & 1) != 0)
{
this.memory.WriteU32(registers[rn] & (~3U), registers[i]);
registers[rn] += 4;
}
}
}
private void OpLdmia()
{
int rn = (this.curInstruction >> 8) & 0x7;
uint address = registers[rn];
for (int i = 0; i < 8; i++)
{
if (((this.curInstruction >> i) & 1) != 0)
{
registers[i] = this.memory.ReadU32Aligned(address & (~3U));
address += 4;
}
}
if (((this.curInstruction >> rn) & 1) == 0)
{
registers[rn] = address;
}
}
private void OpBCond()
{
uint cond = 0;
switch ((this.curInstruction >> 8) & 0xF)
{
case COND_AL: cond = 1; break;
case COND_EQ: cond = zero; break;
case COND_NE: cond = 1 - zero; break;
case COND_CS: cond = carry; break;
case COND_CC: cond = 1 - carry; break;
case COND_MI: cond = negative; break;
case COND_PL: cond = 1 - negative; break;
case COND_VS: cond = overflow; break;
case COND_VC: cond = 1 - overflow; break;
case COND_HI: cond = carry & (1 - zero); break;
case COND_LS: cond = (1 - carry) | zero; break;
case COND_GE: cond = (1 - negative) ^ overflow; break;
case COND_LT: cond = negative ^ overflow; break;
case COND_GT: cond = (1 - zero) & (negative ^ (1 - overflow)); break;
case COND_LE: cond = (negative ^ overflow) | zero; break;
}
if (cond == 1)
{
uint offset = (uint)(this.curInstruction & 0xFF);
if ((offset & (1 << 7)) != 0) offset |= 0xFFFFFF00;
registers[15] += offset << 1;
this.FlushQueue();
}
}
private void OpSwi()
{
registers[15] -= 4U;
this.parent.EnterException(Arm7Processor.SVC, 0x8, false, false);
}
private void OpB()
{
uint offset = (uint)(this.curInstruction & 0x7FF);
if ((offset & (1 << 10)) != 0) offset |= 0xFFFFF800;
registers[15] += offset << 1;
this.FlushQueue();
}
private void OpBl1()
{
uint offset = (uint)(this.curInstruction & 0x7FF);
if ((offset & (1 << 10)) != 0) offset |= 0xFFFFF800;
registers[14] = registers[15] + (offset << 12);
}
private void OpBl2()
{
uint tmp = registers[15];
registers[15] = registers[14] + (uint)((this.curInstruction & 0x7FF) << 1);
registers[14] = (tmp - 2U) | 1;
this.FlushQueue();
}
private void OpUnd()
{
throw new Exception("Unknown opcode");
}
#endregion
private void PackFlags()
{
this.parent.CPSR &= 0x0FFFFFFF;
this.parent.CPSR |= this.negative << Arm7Processor.N_BIT;
this.parent.CPSR |= this.zero << Arm7Processor.Z_BIT;
this.parent.CPSR |= this.carry << Arm7Processor.C_BIT;
this.parent.CPSR |= this.overflow << Arm7Processor.V_BIT;
}
private void UnpackFlags()
{
this.negative = (this.parent.CPSR >> Arm7Processor.N_BIT) & 1;
this.zero = (this.parent.CPSR >> Arm7Processor.Z_BIT) & 1;
this.carry = (this.parent.CPSR >> Arm7Processor.C_BIT) & 1;
this.overflow = (this.parent.CPSR >> Arm7Processor.V_BIT) & 1;
}
private void FlushQueue()
{
this.instructionQueue = this.memory.ReadU16(registers[15]);
registers[15] += 2;
}
}
}

View File

@ -0,0 +1,164 @@
namespace GarboDev
{
public class VideoManager
{
public delegate void OnPresent(uint[] data);
private Memory memory = null;
private IRenderer renderer = null;
private OnPresent presenter;
private GbaManager gbaManager;
private int curLine;
public Memory Memory
{
set{ this.memory = value; }
}
public IRenderer Renderer
{
set
{
this.renderer = value;
this.renderer.Memory = this.memory;
}
}
public OnPresent Presenter
{
set { this.presenter = value; }
}
public VideoManager(GbaManager gbaManager)
{
this.gbaManager = gbaManager;
}
public void Reset()
{
this.curLine = 0;
this.renderer.Memory = memory;
this.renderer.Reset();
}
private void EnterVBlank(Arm7Processor processor)
{
ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT);
dispstat |= 1;
Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat);
// Render the frame
this.gbaManager.FramesRendered++;
this.presenter(this.renderer.ShowFrame());
if ((dispstat & (1 << 3)) != 0)
{
// Fire the vblank irq
processor.RequestIrq(0);
}
// Check for DMA triggers
this.memory.VBlankDma();
}
private void LeaveVBlank(Arm7Processor processor)
{
ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT);
dispstat &= 0xFFFE;
Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat);
processor.UpdateKeyState();
// Update the rot/scale values
this.memory.Bgx[0] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG2X_L);
this.memory.Bgx[1] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG3X_L);
this.memory.Bgy[0] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG2Y_L);
this.memory.Bgy[1] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG3Y_L);
}
public void EnterHBlank(Arm7Processor processor)
{
ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT);
dispstat |= 1 << 1;
Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat);
// Advance the bgx registers
for (int bg = 0; bg <= 1; bg++)
{
short dmx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PB + (uint)bg * 0x10);
short dmy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PD + (uint)bg * 0x10);
this.memory.Bgx[bg] += dmx;
this.memory.Bgy[bg] += dmy;
}
if (this.curLine < 160)
{
this.memory.HBlankDma();
// Trigger hblank irq
if ((dispstat & (1 << 4)) != 0)
{
processor.RequestIrq(1);
}
}
}
/// <summary>
///
/// </summary>
/// <param name="processor"></param>
/// <returns>true if end of frame</returns>
public bool LeaveHBlank(Arm7Processor processor)
{
bool ret = false;
ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT);
dispstat &= 0xFFF9;
Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat);
// Move to the next line
this.curLine++;
if (this.curLine >= 228)
{
// Start again at the beginning
this.curLine = 0;
}
// Update registers
Memory.WriteU16(this.memory.IORam, Memory.VCOUNT, (ushort)this.curLine);
// Check for vblank
if (this.curLine == 160)
{
this.EnterVBlank(processor);
ret = true;
}
else if (this.curLine == 0)
{
this.LeaveVBlank(processor);
}
// Check y-line trigger
if (((dispstat >> 8) & 0xff) == this.curLine)
{
dispstat = (ushort)(Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT) | (1 << 2));
Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat);
if ((dispstat & (1 << 5)) != 0)
{
processor.RequestIrq(2);
}
}
return ret;
}
public void RenderLine()
{
if (this.curLine < 160)
{
this.renderer.RenderLine(this.curLine);
}
}
}
}

View File

@ -1928,7 +1928,8 @@ namespace BizHawk.MultiClient
MessageBox.Show("Unable to find the required GBA BIOS file - \n" + gbabios, "Unable to load BIOS", MessageBoxButtons.OK, MessageBoxIcon.Error);
throw new Exception();
}
GBA gba = new GBA(nextComm);
//GBA gba = new GBA(nextComm);
var gba = new GarboDev.GbaManager(nextComm);
gba.Load(rom.RomData, gbabios);
nextEmulator = gba;
}