OptimeGBA 一致基本归档
This commit is contained in:
parent
45f904172d
commit
3af171bdc3
File diff suppressed because it is too large
Load Diff
@ -1,600 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Iris.CPU
|
||||
{
|
||||
public sealed class CPU_Core
|
||||
{
|
||||
public enum Model
|
||||
{
|
||||
ARM7TDMI,
|
||||
ARM946ES
|
||||
}
|
||||
|
||||
public delegate Byte Read8_Delegate(UInt32 address);
|
||||
public delegate UInt16 Read16_Delegate(UInt32 address);
|
||||
public delegate UInt32 Read32_Delegate(UInt32 address);
|
||||
public delegate void Write8_Delegate(UInt32 address, Byte value);
|
||||
public delegate void Write16_Delegate(UInt32 address, UInt16 value);
|
||||
public delegate void Write32_Delegate(UInt32 address, UInt32 value);
|
||||
public delegate UInt64 HandleSWI_Delegate();
|
||||
public delegate UInt64 HandleIRQ_Delegate();
|
||||
|
||||
// could have used function pointers (delegate*) for performance instead of delegates but it's less flexible (cannot use non-static function for instance)
|
||||
//public readonly struct CallbackInterface
|
||||
//(
|
||||
// Read8_Delegate read8,
|
||||
// Read16_Delegate read16,
|
||||
// Read32_Delegate read32,
|
||||
// Write8_Delegate write8,
|
||||
// Write16_Delegate write16,
|
||||
// Write32_Delegate write32,
|
||||
// HandleSWI_Delegate handleSWI,
|
||||
// HandleIRQ_Delegate handleIRQ
|
||||
//)
|
||||
//{
|
||||
// internal readonly Read8_Delegate _read8 = read8;
|
||||
// internal readonly Read16_Delegate _read16 = read16;
|
||||
// internal readonly Read32_Delegate _read32 = read32;
|
||||
// internal readonly Write8_Delegate _write8 = write8;
|
||||
// internal readonly Write16_Delegate _write16 = write16;
|
||||
// internal readonly Write32_Delegate _write32 = write32;
|
||||
// internal readonly HandleSWI_Delegate _handleSWI = handleSWI;
|
||||
// internal readonly HandleIRQ_Delegate _handleIRQ = handleIRQ;
|
||||
//}
|
||||
|
||||
public readonly struct CallbackInterface
|
||||
{
|
||||
public CallbackInterface
|
||||
(
|
||||
Read8_Delegate read8,
|
||||
Read16_Delegate read16,
|
||||
Read32_Delegate read32,
|
||||
Write8_Delegate write8,
|
||||
Write16_Delegate write16,
|
||||
Write32_Delegate write32,
|
||||
HandleSWI_Delegate handleSWI,
|
||||
HandleIRQ_Delegate handleIRQ
|
||||
)
|
||||
{
|
||||
_read8 = read8;
|
||||
_read16 = read16;
|
||||
_read32 = read32;
|
||||
_write8 = write8;
|
||||
_write16 = write16;
|
||||
_write32 = write32;
|
||||
_handleSWI = handleSWI;
|
||||
_handleIRQ = handleIRQ;
|
||||
}
|
||||
|
||||
internal readonly Read8_Delegate _read8;
|
||||
internal readonly Read16_Delegate _read16;
|
||||
internal readonly Read32_Delegate _read32;
|
||||
internal readonly Write8_Delegate _write8;
|
||||
internal readonly Write16_Delegate _write16;
|
||||
internal readonly Write32_Delegate _write32;
|
||||
internal readonly HandleSWI_Delegate _handleSWI;
|
||||
internal readonly HandleIRQ_Delegate _handleIRQ;
|
||||
}
|
||||
|
||||
public enum Signal
|
||||
{
|
||||
High,
|
||||
Low
|
||||
}
|
||||
|
||||
internal enum Flag
|
||||
{
|
||||
V = 28,
|
||||
C = 29,
|
||||
Z = 30,
|
||||
N = 31
|
||||
}
|
||||
|
||||
//internal unsafe readonly struct InstructionListEntry1<T>(T mask, T expected, delegate*<CPU_Core, T, UInt64> handler, List<Model> modelList)
|
||||
//{
|
||||
// internal readonly T _mask = mask;
|
||||
// internal readonly T _expected = expected;
|
||||
// internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler = handler;
|
||||
// internal readonly List<Model> _modelList = modelList;
|
||||
//}
|
||||
|
||||
internal unsafe readonly struct InstructionListEntry<T>
|
||||
{
|
||||
internal InstructionListEntry(T mask, T expected, delegate*<CPU_Core, T, UInt64> handler, List<Model> modelList)
|
||||
{
|
||||
_mask = mask;
|
||||
_expected = expected;
|
||||
_handler = handler;
|
||||
_modelList = modelList;
|
||||
}
|
||||
|
||||
internal readonly T _mask;
|
||||
internal readonly T _expected;
|
||||
internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler;
|
||||
internal readonly List<Model> _modelList;
|
||||
}
|
||||
|
||||
//internal unsafe readonly struct InstructionLUTEntry<T>(delegate*<CPU_Core, T, UInt64> handler)
|
||||
//{
|
||||
// internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler = handler;
|
||||
//}
|
||||
|
||||
|
||||
internal unsafe readonly struct InstructionLUTEntry<T>
|
||||
{
|
||||
internal InstructionLUTEntry(delegate*<CPU_Core, T, UInt64> handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
internal unsafe readonly delegate*<CPU_Core, T, UInt64> _handler;
|
||||
}
|
||||
|
||||
internal const UInt32 ModeMask = 0b1_1111;
|
||||
internal const UInt32 UserMode = 0b1_0000;
|
||||
internal const UInt32 SystemMode = 0b1_1111;
|
||||
internal const UInt32 SupervisorMode = 0b1_0011;
|
||||
internal const UInt32 AbortMode = 0b1_0111;
|
||||
internal const UInt32 UndefinedMode = 0b1_1011;
|
||||
internal const UInt32 InterruptMode = 0b1_0010;
|
||||
internal const UInt32 FastInterruptMode = 0b1_0001;
|
||||
|
||||
public const UInt32 SP = 13;
|
||||
public const UInt32 LR = 14;
|
||||
public const UInt32 PC = 15;
|
||||
|
||||
public readonly UInt32[] Reg = new UInt32[16];
|
||||
public UInt32 CPSR;
|
||||
public UInt32 SPSR;
|
||||
|
||||
public UInt32 Reg8_usr, Reg9_usr, Reg10_usr, Reg11_usr, Reg12_usr, Reg13_usr, Reg14_usr;
|
||||
public UInt32 Reg13_svc, Reg14_svc;
|
||||
public UInt32 Reg13_abt, Reg14_abt;
|
||||
public UInt32 Reg13_und, Reg14_und;
|
||||
public UInt32 Reg13_irq, Reg14_irq;
|
||||
public UInt32 Reg8_fiq, Reg9_fiq, Reg10_fiq, Reg11_fiq, Reg12_fiq, Reg13_fiq, Reg14_fiq;
|
||||
public UInt32 SPSR_svc, SPSR_abt, SPSR_und, SPSR_irq, SPSR_fiq;
|
||||
|
||||
internal readonly Model _model;
|
||||
internal readonly CallbackInterface _callbackInterface;
|
||||
|
||||
private readonly ARM_Interpreter _armInterpreter;
|
||||
private readonly THUMB_Interpreter _thumbInterpreter;
|
||||
|
||||
public UInt32 NextInstructionAddress;
|
||||
public Signal NIRQ;
|
||||
|
||||
public CPU_Core(Model model, CallbackInterface callbackInterface)
|
||||
{
|
||||
_model = model;
|
||||
_callbackInterface = callbackInterface;
|
||||
_armInterpreter = new(this);
|
||||
_thumbInterpreter = new(this);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Array.Clear(Reg,0,Reg.Length);
|
||||
|
||||
CPSR = 0b1_0000;
|
||||
SPSR = 0;
|
||||
|
||||
Reg8_usr = 0;
|
||||
Reg9_usr = 0;
|
||||
Reg10_usr = 0;
|
||||
Reg11_usr = 0;
|
||||
Reg12_usr = 0;
|
||||
Reg13_usr = 0;
|
||||
Reg14_usr = 0;
|
||||
|
||||
Reg13_svc = 0;
|
||||
Reg14_svc = 0;
|
||||
|
||||
Reg13_abt = 0;
|
||||
Reg14_abt = 0;
|
||||
|
||||
Reg13_und = 0;
|
||||
Reg14_und = 0;
|
||||
|
||||
Reg13_irq = 0;
|
||||
Reg14_irq = 0;
|
||||
|
||||
Reg8_fiq = 0;
|
||||
Reg9_fiq = 0;
|
||||
Reg10_fiq = 0;
|
||||
Reg11_fiq = 0;
|
||||
Reg12_fiq = 0;
|
||||
Reg13_fiq = 0;
|
||||
Reg14_fiq = 0;
|
||||
|
||||
SPSR_svc = 0;
|
||||
SPSR_abt = 0;
|
||||
SPSR_und = 0;
|
||||
SPSR_irq = 0;
|
||||
SPSR_fiq = 0;
|
||||
|
||||
NextInstructionAddress = 0;
|
||||
NIRQ = Signal.High;
|
||||
}
|
||||
|
||||
public void LoadState(BinaryReader reader)
|
||||
{
|
||||
foreach (ref UInt32 reg in Reg.AsSpan())
|
||||
reg = reader.ReadUInt32();
|
||||
|
||||
CPSR = reader.ReadUInt32();
|
||||
SPSR = reader.ReadUInt32();
|
||||
|
||||
Reg8_usr = reader.ReadUInt32();
|
||||
Reg9_usr = reader.ReadUInt32();
|
||||
Reg10_usr = reader.ReadUInt32();
|
||||
Reg11_usr = reader.ReadUInt32();
|
||||
Reg12_usr = reader.ReadUInt32();
|
||||
Reg13_usr = reader.ReadUInt32();
|
||||
Reg14_usr = reader.ReadUInt32();
|
||||
|
||||
Reg13_svc = reader.ReadUInt32();
|
||||
Reg14_svc = reader.ReadUInt32();
|
||||
|
||||
Reg13_abt = reader.ReadUInt32();
|
||||
Reg14_abt = reader.ReadUInt32();
|
||||
|
||||
Reg13_und = reader.ReadUInt32();
|
||||
Reg14_und = reader.ReadUInt32();
|
||||
|
||||
Reg13_irq = reader.ReadUInt32();
|
||||
Reg14_irq = reader.ReadUInt32();
|
||||
|
||||
Reg8_fiq = reader.ReadUInt32();
|
||||
Reg9_fiq = reader.ReadUInt32();
|
||||
Reg10_fiq = reader.ReadUInt32();
|
||||
Reg11_fiq = reader.ReadUInt32();
|
||||
Reg12_fiq = reader.ReadUInt32();
|
||||
Reg13_fiq = reader.ReadUInt32();
|
||||
Reg14_fiq = reader.ReadUInt32();
|
||||
|
||||
SPSR_svc = reader.ReadUInt32();
|
||||
SPSR_abt = reader.ReadUInt32();
|
||||
SPSR_und = reader.ReadUInt32();
|
||||
SPSR_irq = reader.ReadUInt32();
|
||||
SPSR_fiq = reader.ReadUInt32();
|
||||
|
||||
NextInstructionAddress = reader.ReadUInt32();
|
||||
NIRQ = (Signal)reader.ReadInt32();
|
||||
}
|
||||
|
||||
public void SaveState(BinaryWriter writer)
|
||||
{
|
||||
foreach (UInt32 reg in Reg)
|
||||
writer.Write(reg);
|
||||
|
||||
writer.Write(CPSR);
|
||||
writer.Write(SPSR);
|
||||
|
||||
writer.Write(Reg8_usr);
|
||||
writer.Write(Reg9_usr);
|
||||
writer.Write(Reg10_usr);
|
||||
writer.Write(Reg11_usr);
|
||||
writer.Write(Reg12_usr);
|
||||
writer.Write(Reg13_usr);
|
||||
writer.Write(Reg14_usr);
|
||||
|
||||
writer.Write(Reg13_svc);
|
||||
writer.Write(Reg14_svc);
|
||||
|
||||
writer.Write(Reg13_abt);
|
||||
writer.Write(Reg14_abt);
|
||||
|
||||
writer.Write(Reg13_und);
|
||||
writer.Write(Reg14_und);
|
||||
|
||||
writer.Write(Reg13_irq);
|
||||
writer.Write(Reg14_irq);
|
||||
|
||||
writer.Write(Reg8_fiq);
|
||||
writer.Write(Reg9_fiq);
|
||||
writer.Write(Reg10_fiq);
|
||||
writer.Write(Reg11_fiq);
|
||||
writer.Write(Reg12_fiq);
|
||||
writer.Write(Reg13_fiq);
|
||||
writer.Write(Reg14_fiq);
|
||||
|
||||
writer.Write(SPSR_svc);
|
||||
writer.Write(SPSR_abt);
|
||||
writer.Write(SPSR_und);
|
||||
writer.Write(SPSR_irq);
|
||||
writer.Write(SPSR_fiq);
|
||||
|
||||
writer.Write(NextInstructionAddress);
|
||||
writer.Write((int)NIRQ);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UInt64 Step()
|
||||
{
|
||||
UInt32 i = (CPSR >> 7) & 1;
|
||||
|
||||
if ((i == 0) && (NIRQ == Signal.Low))
|
||||
return _callbackInterface._handleIRQ();
|
||||
|
||||
UInt32 t = (CPSR >> 5) & 1;
|
||||
|
||||
if (t == 0)
|
||||
return _armInterpreter.Step();
|
||||
else
|
||||
return _thumbInterpreter.Step();
|
||||
}
|
||||
|
||||
public void SetCPSR(UInt32 value)
|
||||
{
|
||||
UInt32 previousMode = CPSR & ModeMask;
|
||||
UInt32 newMode = value & ModeMask;
|
||||
|
||||
CPSR = value | 0b1_0000;
|
||||
if (previousMode != newMode)
|
||||
{
|
||||
ref UInt32 regDataRef = ref MyUnSafeCommon.GetArrayDataReference(Reg);
|
||||
ref UInt32 reg8 = ref Unsafe.Add(ref regDataRef, 8);
|
||||
ref UInt32 reg9 = ref Unsafe.Add(ref regDataRef, 9);
|
||||
ref UInt32 reg10 = ref Unsafe.Add(ref regDataRef, 10);
|
||||
ref UInt32 reg11 = ref Unsafe.Add(ref regDataRef, 11);
|
||||
ref UInt32 reg12 = ref Unsafe.Add(ref regDataRef, 12);
|
||||
ref UInt32 reg13 = ref Unsafe.Add(ref regDataRef, 13);
|
||||
ref UInt32 reg14 = ref Unsafe.Add(ref regDataRef, 14);
|
||||
|
||||
// save previous mode registers
|
||||
switch (previousMode)
|
||||
{
|
||||
case UserMode:
|
||||
case SystemMode:
|
||||
Reg8_usr = reg8;
|
||||
Reg9_usr = reg9;
|
||||
Reg10_usr = reg10;
|
||||
Reg11_usr = reg11;
|
||||
Reg12_usr = reg12;
|
||||
Reg13_usr = reg13;
|
||||
Reg14_usr = reg14;
|
||||
break;
|
||||
case SupervisorMode:
|
||||
Reg8_usr = reg8;
|
||||
Reg9_usr = reg9;
|
||||
Reg10_usr = reg10;
|
||||
Reg11_usr = reg11;
|
||||
Reg12_usr = reg12;
|
||||
Reg13_svc = reg13;
|
||||
Reg14_svc = reg14;
|
||||
SPSR_svc = SPSR;
|
||||
break;
|
||||
case AbortMode:
|
||||
Reg8_usr = reg8;
|
||||
Reg9_usr = reg9;
|
||||
Reg10_usr = reg10;
|
||||
Reg11_usr = reg11;
|
||||
Reg12_usr = reg12;
|
||||
Reg13_abt = reg13;
|
||||
Reg14_abt = reg14;
|
||||
SPSR_abt = SPSR;
|
||||
break;
|
||||
case UndefinedMode:
|
||||
Reg8_usr = reg8;
|
||||
Reg9_usr = reg9;
|
||||
Reg10_usr = reg10;
|
||||
Reg11_usr = reg11;
|
||||
Reg12_usr = reg12;
|
||||
Reg13_und = reg13;
|
||||
Reg14_und = reg14;
|
||||
SPSR_und = SPSR;
|
||||
break;
|
||||
case InterruptMode:
|
||||
Reg8_usr = reg8;
|
||||
Reg9_usr = reg9;
|
||||
Reg10_usr = reg10;
|
||||
Reg11_usr = reg11;
|
||||
Reg12_usr = reg12;
|
||||
Reg13_irq = reg13;
|
||||
Reg14_irq = reg14;
|
||||
SPSR_irq = SPSR;
|
||||
break;
|
||||
case FastInterruptMode:
|
||||
Reg8_fiq = reg8;
|
||||
Reg9_fiq = reg9;
|
||||
Reg10_fiq = reg10;
|
||||
Reg11_fiq = reg11;
|
||||
Reg12_fiq = reg12;
|
||||
Reg13_fiq = reg13;
|
||||
Reg14_fiq = reg14;
|
||||
SPSR_fiq = SPSR;
|
||||
break;
|
||||
}
|
||||
|
||||
// load new mode registers
|
||||
switch (newMode)
|
||||
{
|
||||
case UserMode:
|
||||
case SystemMode:
|
||||
reg8 = Reg8_usr;
|
||||
reg9 = Reg9_usr;
|
||||
reg10 = Reg10_usr;
|
||||
reg11 = Reg11_usr;
|
||||
reg12 = Reg12_usr;
|
||||
reg13 = Reg13_usr;
|
||||
reg14 = Reg14_usr;
|
||||
break;
|
||||
case SupervisorMode:
|
||||
reg8 = Reg8_usr;
|
||||
reg9 = Reg9_usr;
|
||||
reg10 = Reg10_usr;
|
||||
reg11 = Reg11_usr;
|
||||
reg12 = Reg12_usr;
|
||||
reg13 = Reg13_svc;
|
||||
reg14 = Reg14_svc;
|
||||
SPSR = SPSR_svc;
|
||||
break;
|
||||
case AbortMode:
|
||||
reg8 = Reg8_usr;
|
||||
reg9 = Reg9_usr;
|
||||
reg10 = Reg10_usr;
|
||||
reg11 = Reg11_usr;
|
||||
reg12 = Reg12_usr;
|
||||
reg13 = Reg13_abt;
|
||||
reg14 = Reg14_abt;
|
||||
SPSR = SPSR_abt;
|
||||
break;
|
||||
case UndefinedMode:
|
||||
reg8 = Reg8_usr;
|
||||
reg9 = Reg9_usr;
|
||||
reg10 = Reg10_usr;
|
||||
reg11 = Reg11_usr;
|
||||
reg12 = Reg12_usr;
|
||||
reg13 = Reg13_und;
|
||||
reg14 = Reg14_und;
|
||||
SPSR = SPSR_und;
|
||||
break;
|
||||
case InterruptMode:
|
||||
reg8 = Reg8_usr;
|
||||
reg9 = Reg9_usr;
|
||||
reg10 = Reg10_usr;
|
||||
reg11 = Reg11_usr;
|
||||
reg12 = Reg12_usr;
|
||||
reg13 = Reg13_irq;
|
||||
reg14 = Reg14_irq;
|
||||
SPSR = SPSR_irq;
|
||||
break;
|
||||
case FastInterruptMode:
|
||||
reg8 = Reg8_fiq;
|
||||
reg9 = Reg9_fiq;
|
||||
reg10 = Reg10_fiq;
|
||||
reg11 = Reg11_fiq;
|
||||
reg12 = Reg12_fiq;
|
||||
reg13 = Reg13_fiq;
|
||||
reg14 = Reg14_fiq;
|
||||
SPSR = SPSR_fiq;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal UInt32 GetFlag(Flag flag)
|
||||
{
|
||||
return (CPSR >> (int)flag) & 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void SetFlag(Flag flag, UInt32 value)
|
||||
{
|
||||
CPSR = (CPSR & ~(1u << (int)flag)) | (value << (int)flag);
|
||||
}
|
||||
|
||||
internal bool ConditionPassed(UInt32 cond)
|
||||
{
|
||||
return cond switch
|
||||
{
|
||||
// EQ
|
||||
0b0000 => GetFlag(Flag.Z) == 1,
|
||||
// NE
|
||||
0b0001 => GetFlag(Flag.Z) == 0,
|
||||
// CS/HS
|
||||
0b0010 => GetFlag(Flag.C) == 1,
|
||||
// CC/LO
|
||||
0b0011 => GetFlag(Flag.C) == 0,
|
||||
// MI
|
||||
0b0100 => GetFlag(Flag.N) == 1,
|
||||
// PL
|
||||
0b0101 => GetFlag(Flag.N) == 0,
|
||||
// VS
|
||||
0b0110 => GetFlag(Flag.V) == 1,
|
||||
// VC
|
||||
0b0111 => GetFlag(Flag.V) == 0,
|
||||
// HI
|
||||
0b1000 => (GetFlag(Flag.C) == 1) && (GetFlag(Flag.Z) == 0),
|
||||
// LS
|
||||
0b1001 => (GetFlag(Flag.C) == 0) || (GetFlag(Flag.Z) == 1),
|
||||
// GE
|
||||
0b1010 => GetFlag(Flag.N) == GetFlag(Flag.V),
|
||||
// LT
|
||||
0b1011 => GetFlag(Flag.N) != GetFlag(Flag.V),
|
||||
// GT
|
||||
0b1100 => (GetFlag(Flag.Z) == 0) && (GetFlag(Flag.N) == GetFlag(Flag.V)),
|
||||
// LE
|
||||
0b1101 => (GetFlag(Flag.Z) == 1) || (GetFlag(Flag.N) != GetFlag(Flag.V)),
|
||||
// AL
|
||||
0b1110 => true,
|
||||
// NV
|
||||
0b1111 => false,
|
||||
// should never happen
|
||||
_ => throw new Exception($"Iris.CPU.CPU_Core: Wrong condition code {cond}"),
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static UInt32 Not(UInt32 flag)
|
||||
{
|
||||
return flag ^ 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static UInt32 CarryFrom(UInt64 result)
|
||||
{
|
||||
return (result > 0xffff_ffff) ? 1u : 0u;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static UInt32 BorrowFrom(UInt64 result)
|
||||
{
|
||||
return (result >= 0x8000_0000_0000_0000) ? 1u : 0u;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static UInt32 OverflowFrom_Addition(UInt32 leftOperand, UInt32 rightOperand, UInt32 result)
|
||||
{
|
||||
return (((leftOperand >> 31) == (rightOperand >> 31))
|
||||
&& ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static UInt32 OverflowFrom_Subtraction(UInt32 leftOperand, UInt32 rightOperand, UInt32 result)
|
||||
{
|
||||
return (((leftOperand >> 31) != (rightOperand >> 31))
|
||||
&& ((leftOperand >> 31) != (result >> 31))) ? 1u : 0u;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static UInt32 ArithmeticShiftRight(UInt32 value, int shiftAmount)
|
||||
{
|
||||
return (UInt32)((Int32)value >> shiftAmount);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static UInt32 SignExtend(UInt32 value, int size)
|
||||
{
|
||||
return value | ~((value & (1u << (size - 1))) - 1);
|
||||
}
|
||||
|
||||
internal static UInt64 ComputeMultiplicationCycleCount(UInt32 leftMultiplier, UInt32 rightMultiplier)
|
||||
{
|
||||
static UInt64 ComputeMultiplierCycleCount(UInt32 multiplier)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static bool CheckMultiplierAgainstMask(UInt32 multiplier, UInt32 mask)
|
||||
{
|
||||
UInt32 masked = multiplier & mask;
|
||||
return (masked == 0) || (masked == mask);
|
||||
}
|
||||
|
||||
if (CheckMultiplierAgainstMask(multiplier, 0xffff_ff00))
|
||||
return 1;
|
||||
else if (CheckMultiplierAgainstMask(multiplier, 0xffff_0000))
|
||||
return 2;
|
||||
else if (CheckMultiplierAgainstMask(multiplier, 0xff00_0000))
|
||||
return 3;
|
||||
else
|
||||
return 4;
|
||||
}
|
||||
|
||||
return Math.Max(ComputeMultiplierCycleCount(leftMultiplier), ComputeMultiplierCycleCount(rightMultiplier));
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,152 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Iris.Common
|
||||
{
|
||||
public sealed class Scheduler
|
||||
{
|
||||
public Scheduler(int taskListSize, int scheduledTaskListSize)
|
||||
{
|
||||
_taskList = new Task_Delegate[taskListSize];
|
||||
_scheduledTaskList = new ScheduledTaskListEntry[scheduledTaskListSize];
|
||||
}
|
||||
public delegate void Task_Delegate(UInt64 cycleCountDelay);
|
||||
private readonly Task_Delegate[] _taskList;
|
||||
//private readonly Task_Delegate[] _taskList = new Task_Delegate[taskListSize];
|
||||
|
||||
private struct ScheduledTaskListEntry
|
||||
{
|
||||
internal int _id;
|
||||
internal UInt64 _cycleCount;
|
||||
}
|
||||
|
||||
private readonly ScheduledTaskListEntry[] _scheduledTaskList; // sorted by _cycleCount from smallest to largest
|
||||
private int _scheduledTaskCount;
|
||||
|
||||
private UInt64 _cycleCounter;
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
_scheduledTaskCount = 0;
|
||||
_cycleCounter = 0;
|
||||
}
|
||||
|
||||
public void LoadState(BinaryReader reader)
|
||||
{
|
||||
foreach (ref ScheduledTaskListEntry entry in _scheduledTaskList.AsSpan())
|
||||
{
|
||||
entry._id = reader.ReadInt32();
|
||||
entry._cycleCount = reader.ReadUInt64();
|
||||
}
|
||||
|
||||
_scheduledTaskCount = reader.ReadInt32();
|
||||
_cycleCounter = reader.ReadUInt64();
|
||||
}
|
||||
|
||||
public void SaveState(BinaryWriter writer)
|
||||
{
|
||||
foreach (ScheduledTaskListEntry entry in _scheduledTaskList)
|
||||
{
|
||||
writer.Write(entry._id);
|
||||
writer.Write(entry._cycleCount);
|
||||
}
|
||||
|
||||
writer.Write(_scheduledTaskCount);
|
||||
writer.Write(_cycleCounter);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UInt64 GetCycleCounter()
|
||||
{
|
||||
return _cycleCounter;
|
||||
}
|
||||
|
||||
public void AdvanceCycleCounter(UInt64 cycleCount)
|
||||
{
|
||||
_cycleCounter += cycleCount;
|
||||
|
||||
|
||||
// process tasks
|
||||
|
||||
//ref readonly ScheduledTaskListEntry firstEntry = ref UnSafeCommon.GetArrayDataReference(_scheduledTaskList);
|
||||
//ref Task_Delegate taskListDataRef = ref UnSafeCommon.GetArrayDataReference(_taskList);
|
||||
|
||||
ref readonly ScheduledTaskListEntry firstEntry = ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList);
|
||||
ref Task_Delegate taskListDataRef = ref MyUnSafeCommon.GetArrayDataReference(_taskList);
|
||||
|
||||
while ((_scheduledTaskCount > 0) && (firstEntry._cycleCount <= _cycleCounter))
|
||||
{
|
||||
// save the task
|
||||
ScheduledTaskListEntry entry = firstEntry;
|
||||
|
||||
// remove it from the list
|
||||
--_scheduledTaskCount;
|
||||
|
||||
if (_scheduledTaskCount > 0)
|
||||
Array.Copy(_scheduledTaskList, 1, _scheduledTaskList, 0, _scheduledTaskCount);
|
||||
|
||||
// execute it
|
||||
Unsafe.Add(ref taskListDataRef, entry._id)(_cycleCounter - entry._cycleCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterTask(int id, Task_Delegate task)
|
||||
{
|
||||
_taskList[id] = task;
|
||||
}
|
||||
|
||||
public void ScheduleTask(int id, UInt64 cycleCount)
|
||||
{
|
||||
// convert cycleCount from relative to absolute
|
||||
cycleCount += _cycleCounter;
|
||||
|
||||
// get the position and reference of the new task
|
||||
// (searching is done backward because a new task is more likely to be inserted towards the end)
|
||||
int index = _scheduledTaskCount;
|
||||
//ref ScheduledTaskListEntry entry = ref Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList), _scheduledTaskCount - 1);
|
||||
ref ScheduledTaskListEntry entry = ref Unsafe.Add(ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList), _scheduledTaskCount - 1);
|
||||
|
||||
while ((index > 0) && (entry._cycleCount > cycleCount))
|
||||
{
|
||||
--index;
|
||||
entry = ref Unsafe.Subtract(ref entry, 1);
|
||||
}
|
||||
|
||||
entry = ref Unsafe.Add(ref entry, 1);
|
||||
|
||||
// insert the new task
|
||||
if (index < _scheduledTaskCount)
|
||||
Array.Copy(_scheduledTaskList, index, _scheduledTaskList, index + 1, _scheduledTaskCount - index);
|
||||
|
||||
entry._id = id;
|
||||
entry._cycleCount = cycleCount;
|
||||
|
||||
++_scheduledTaskCount;
|
||||
}
|
||||
|
||||
public void CancelTask(int id)
|
||||
{
|
||||
int index = 0;
|
||||
//ref ScheduledTaskListEntry entry = ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList);
|
||||
ref ScheduledTaskListEntry entry = ref MyUnSafeCommon.GetArrayDataReference(_scheduledTaskList);
|
||||
|
||||
while (index < _scheduledTaskCount)
|
||||
{
|
||||
if (entry._id == id)
|
||||
{
|
||||
--_scheduledTaskCount;
|
||||
|
||||
if (index < _scheduledTaskCount)
|
||||
Array.Copy(_scheduledTaskList, index + 1, _scheduledTaskList, index, _scheduledTaskCount - index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
++index;
|
||||
entry = ref Unsafe.Add(ref entry, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Iris.Common
|
||||
{
|
||||
public abstract class System : IDisposable
|
||||
{
|
||||
public delegate void PollInput_Delegate();
|
||||
public delegate void PresentFrame_Delegate(UInt16[] frameBuffer);
|
||||
|
||||
public enum Key
|
||||
{
|
||||
A,
|
||||
B,
|
||||
Select,
|
||||
Start,
|
||||
Right,
|
||||
Left,
|
||||
Up,
|
||||
Down,
|
||||
R,
|
||||
L,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
public enum KeyStatus
|
||||
{
|
||||
Input = 0,
|
||||
NoInput = 1
|
||||
}
|
||||
|
||||
public abstract void Dispose();
|
||||
|
||||
public abstract void ResetState(bool skipIntro);
|
||||
public abstract void LoadState(BinaryReader reader);
|
||||
public abstract void SaveState(BinaryWriter writer);
|
||||
|
||||
public abstract void LoadROM(string filename);
|
||||
public abstract void SetKeyStatus(Key key, KeyStatus status);
|
||||
|
||||
public abstract bool IsRunning();
|
||||
public abstract void Run();
|
||||
public abstract void Pause();
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16247def3e95ffe48b0f4909962cd91a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,119 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class BIOS : IDisposable
|
||||
{
|
||||
private const int KB = 1024;
|
||||
private const int BIOS_Size = 16 * KB;
|
||||
private readonly IntPtr _bios = Marshal.AllocHGlobal(BIOS_Size);
|
||||
|
||||
private const UInt32 BIOS_StartAddress = 0x0000_0000;
|
||||
private const UInt32 BIOS_EndAddress = 0x0000_4000;
|
||||
|
||||
private CPU.CPU_Core _cpu;
|
||||
private bool _disposed;
|
||||
|
||||
internal BIOS()
|
||||
{
|
||||
Byte[] data;
|
||||
|
||||
try
|
||||
{
|
||||
data = File.ReadAllBytes("gba_bios.bin");
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
throw new Exception("Iris.GBA.BIOS: Could not find BIOS");
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new Exception("Iris.GBA.BIOS: Could not read BIOS");
|
||||
}
|
||||
|
||||
if (data.Length != BIOS_Size)
|
||||
throw new Exception("Iris.GBA.BIOS: Wrong BIOS size");
|
||||
|
||||
Marshal.Copy(data, 0, _bios, BIOS_Size);
|
||||
}
|
||||
|
||||
~BIOS()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
Marshal.FreeHGlobal(_bios);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
internal void Initialize(CPU.CPU_Core cpu, Memory memory)
|
||||
{
|
||||
_cpu = cpu;
|
||||
|
||||
memory.Map(_bios, BIOS_Size, BIOS_StartAddress, BIOS_EndAddress, Memory.Flag.AllRead);
|
||||
}
|
||||
|
||||
internal void Reset(bool skipIntro)
|
||||
{
|
||||
if (skipIntro)
|
||||
{
|
||||
_cpu.Reg[CPU.CPU_Core.SP] = 0x300_7f00;
|
||||
_cpu.Reg[CPU.CPU_Core.LR] = 0x800_0000;
|
||||
|
||||
_cpu.CPSR = 0x1f;
|
||||
|
||||
_cpu.Reg13_svc = 0x300_7fe0;
|
||||
_cpu.Reg13_irq = 0x300_7fa0;
|
||||
|
||||
_cpu.NextInstructionAddress = 0x800_0000;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cpu.CPSR = 0xd3;
|
||||
_cpu.NextInstructionAddress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal Byte Read8(UInt32 address)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal UInt16 Read16(UInt32 address)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal UInt32 Read32(UInt32 address)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal UInt64 HandleSWI()
|
||||
{
|
||||
_cpu.Reg14_svc = _cpu.NextInstructionAddress;
|
||||
_cpu.SPSR_svc = _cpu.CPSR;
|
||||
_cpu.SetCPSR((_cpu.CPSR & ~0xbfu) | 0x93u);
|
||||
_cpu.NextInstructionAddress = 0x08;
|
||||
return 3;
|
||||
}
|
||||
|
||||
internal UInt64 HandleIRQ()
|
||||
{
|
||||
_cpu.Reg14_irq = _cpu.NextInstructionAddress + 4;
|
||||
_cpu.SPSR_irq = _cpu.CPSR;
|
||||
_cpu.SetCPSR((_cpu.CPSR & ~0xbfu) | 0x92u);
|
||||
_cpu.NextInstructionAddress = 0x18;
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a44ddbc1235a2b9448cedcd04283cb60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,250 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class Communication
|
||||
{
|
||||
internal enum Register
|
||||
{
|
||||
SIODATA0,
|
||||
SIODATA1,
|
||||
SIODATA2,
|
||||
SIODATA3,
|
||||
|
||||
SIOCNT,
|
||||
SIODATA_SEND,
|
||||
|
||||
RCNT,
|
||||
|
||||
JOYCNT,
|
||||
|
||||
JOY_RECV_L,
|
||||
JOY_RECV_H,
|
||||
|
||||
JOY_TRANS_L,
|
||||
JOY_TRANS_H,
|
||||
|
||||
JOYSTAT
|
||||
}
|
||||
|
||||
private UInt16 _SIODATA0; // SIOMULTI0 / SIODATA32_L
|
||||
private UInt16 _SIODATA1; // SIOMULTI1 / SIODATA32_H
|
||||
private UInt16 _SIODATA2; // SIOMULTI2
|
||||
private UInt16 _SIODATA3; // SIOMULTI3
|
||||
|
||||
private UInt16 _SIOCNT;
|
||||
private UInt16 _SIODATA_SEND; // SIOMLT_SEND / SIODATA_8
|
||||
|
||||
private UInt16 _RCNT;
|
||||
|
||||
private UInt16 _JOYCNT;
|
||||
|
||||
private UInt16 _JOY_RECV_L;
|
||||
private UInt16 _JOY_RECV_H;
|
||||
|
||||
private UInt16 _JOY_TRANS_L;
|
||||
private UInt16 _JOY_TRANS_H;
|
||||
|
||||
private UInt16 _JOYSTAT;
|
||||
|
||||
private InterruptControl _interruptControl;
|
||||
|
||||
internal void Initialize(InterruptControl interruptControl)
|
||||
{
|
||||
_interruptControl = interruptControl;
|
||||
}
|
||||
|
||||
internal void ResetState()
|
||||
{
|
||||
_SIODATA0 = 0;
|
||||
_SIODATA1 = 0;
|
||||
_SIODATA2 = 0;
|
||||
_SIODATA3 = 0;
|
||||
|
||||
_SIOCNT = 0;
|
||||
_SIODATA_SEND = 0;
|
||||
|
||||
_RCNT = 0;
|
||||
|
||||
_JOYCNT = 0;
|
||||
|
||||
_JOY_RECV_L = 0;
|
||||
_JOY_RECV_H = 0;
|
||||
|
||||
_JOY_TRANS_L = 0;
|
||||
_JOY_TRANS_H = 0;
|
||||
|
||||
_JOYSTAT = 0;
|
||||
}
|
||||
|
||||
internal void LoadState(BinaryReader reader)
|
||||
{
|
||||
_SIODATA0 = reader.ReadUInt16();
|
||||
_SIODATA1 = reader.ReadUInt16();
|
||||
_SIODATA2 = reader.ReadUInt16();
|
||||
_SIODATA3 = reader.ReadUInt16();
|
||||
|
||||
_SIOCNT = reader.ReadUInt16();
|
||||
_SIODATA_SEND = reader.ReadUInt16();
|
||||
|
||||
_RCNT = reader.ReadUInt16();
|
||||
|
||||
_JOYCNT = reader.ReadUInt16();
|
||||
|
||||
_JOY_RECV_L = reader.ReadUInt16();
|
||||
_JOY_RECV_H = reader.ReadUInt16();
|
||||
|
||||
_JOY_TRANS_L = reader.ReadUInt16();
|
||||
_JOY_TRANS_H = reader.ReadUInt16();
|
||||
|
||||
_JOYSTAT = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
internal void SaveState(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(_SIODATA0);
|
||||
writer.Write(_SIODATA1);
|
||||
writer.Write(_SIODATA2);
|
||||
writer.Write(_SIODATA3);
|
||||
|
||||
writer.Write(_SIOCNT);
|
||||
writer.Write(_SIODATA_SEND);
|
||||
|
||||
writer.Write(_RCNT);
|
||||
|
||||
writer.Write(_JOYCNT);
|
||||
|
||||
writer.Write(_JOY_RECV_L);
|
||||
writer.Write(_JOY_RECV_H);
|
||||
|
||||
writer.Write(_JOY_TRANS_L);
|
||||
writer.Write(_JOY_TRANS_H);
|
||||
|
||||
writer.Write(_JOYSTAT);
|
||||
}
|
||||
|
||||
internal UInt16 ReadRegister(Register register)
|
||||
{
|
||||
return register switch
|
||||
{
|
||||
Register.SIODATA0 => _SIODATA0,
|
||||
Register.SIODATA1 => _SIODATA1,
|
||||
Register.SIODATA2 => _SIODATA2,
|
||||
Register.SIODATA3 => _SIODATA3,
|
||||
|
||||
Register.SIOCNT => _SIOCNT,
|
||||
Register.SIODATA_SEND => _SIODATA_SEND,
|
||||
|
||||
Register.RCNT => _RCNT,
|
||||
|
||||
Register.JOYCNT => _JOYCNT,
|
||||
|
||||
Register.JOY_RECV_L => _JOY_RECV_L,
|
||||
Register.JOY_RECV_H => _JOY_RECV_H,
|
||||
|
||||
Register.JOY_TRANS_L => _JOY_TRANS_L,
|
||||
Register.JOY_TRANS_H => _JOY_TRANS_H,
|
||||
|
||||
Register.JOYSTAT => _JOYSTAT,
|
||||
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.Communication: Register read error"),
|
||||
};
|
||||
}
|
||||
|
||||
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
case Register.SIODATA0:
|
||||
Memory.WriteRegisterHelper(ref _SIODATA0, value, mode);
|
||||
break;
|
||||
case Register.SIODATA1:
|
||||
Memory.WriteRegisterHelper(ref _SIODATA1, value, mode);
|
||||
break;
|
||||
case Register.SIODATA2:
|
||||
Memory.WriteRegisterHelper(ref _SIODATA2, value, mode);
|
||||
break;
|
||||
case Register.SIODATA3:
|
||||
Memory.WriteRegisterHelper(ref _SIODATA3, value, mode);
|
||||
break;
|
||||
|
||||
case Register.SIOCNT:
|
||||
Memory.WriteRegisterHelper(ref _SIOCNT, value, mode);
|
||||
CheckTransfer();
|
||||
break;
|
||||
case Register.SIODATA_SEND:
|
||||
Memory.WriteRegisterHelper(ref _SIODATA_SEND, value, mode);
|
||||
break;
|
||||
|
||||
case Register.RCNT:
|
||||
Memory.WriteRegisterHelper(ref _RCNT, value, mode);
|
||||
CheckTransfer();
|
||||
break;
|
||||
|
||||
case Register.JOYCNT:
|
||||
Memory.WriteRegisterHelper(ref _JOYCNT, value, mode);
|
||||
break;
|
||||
|
||||
case Register.JOY_RECV_L:
|
||||
Memory.WriteRegisterHelper(ref _JOY_RECV_L, value, mode);
|
||||
break;
|
||||
case Register.JOY_RECV_H:
|
||||
Memory.WriteRegisterHelper(ref _JOY_RECV_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.JOY_TRANS_L:
|
||||
Memory.WriteRegisterHelper(ref _JOY_TRANS_L, value, mode);
|
||||
break;
|
||||
case Register.JOY_TRANS_H:
|
||||
Memory.WriteRegisterHelper(ref _JOY_TRANS_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.JOYSTAT:
|
||||
Memory.WriteRegisterHelper(ref _JOYSTAT, value, mode);
|
||||
break;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.Communication: Register write error");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckTransfer()
|
||||
{
|
||||
switch ((_RCNT >> 14) & 0b11)
|
||||
{
|
||||
case 0b00:
|
||||
case 0b01:
|
||||
switch ((_SIOCNT >> 12) & 0b11)
|
||||
{
|
||||
case 0b00: // 8 bits normal serial communication
|
||||
case 0b01: // 32 bits normal serial communication
|
||||
case 0b10: // 16 bits multiplayer serial communication
|
||||
if ((_SIOCNT & 0x0080) == 0x0080)
|
||||
{
|
||||
_SIOCNT = (UInt16)(_SIOCNT & ~0x0080);
|
||||
|
||||
if ((_SIOCNT & 0x4000) == 0x4000)
|
||||
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.SIO);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0b11:
|
||||
Console.WriteLine("[Iris.GBA.Communication] UART communication not implemented");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0b10:
|
||||
Console.WriteLine("[Iris.GBA.Communication] General purpose communication not implemented");
|
||||
break;
|
||||
|
||||
case 0b11:
|
||||
Console.WriteLine("[Iris.GBA.Communication] JOY Bus communication not implemented");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2cb1b07716db45d45b2681382a0ff8fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,479 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class DMA
|
||||
{
|
||||
internal enum Register
|
||||
{
|
||||
DMA0SAD_L,
|
||||
DMA0SAD_H,
|
||||
|
||||
DMA0DAD_L,
|
||||
DMA0DAD_H,
|
||||
|
||||
DMA0CNT_L,
|
||||
DMA0CNT_H,
|
||||
|
||||
DMA1SAD_L,
|
||||
DMA1SAD_H,
|
||||
|
||||
DMA1DAD_L,
|
||||
DMA1DAD_H,
|
||||
|
||||
DMA1CNT_L,
|
||||
DMA1CNT_H,
|
||||
|
||||
DMA2SAD_L,
|
||||
DMA2SAD_H,
|
||||
|
||||
DMA2DAD_L,
|
||||
DMA2DAD_H,
|
||||
|
||||
DMA2CNT_L,
|
||||
DMA2CNT_H,
|
||||
|
||||
DMA3SAD_L,
|
||||
DMA3SAD_H,
|
||||
|
||||
DMA3DAD_L,
|
||||
DMA3DAD_H,
|
||||
|
||||
DMA3CNT_L,
|
||||
DMA3CNT_H
|
||||
}
|
||||
|
||||
internal enum StartTiming
|
||||
{
|
||||
Immediate = 0b00,
|
||||
VBlank = 0b01,
|
||||
HBlank = 0b10,
|
||||
Special = 0b11
|
||||
}
|
||||
|
||||
private readonly Common.Scheduler _scheduler;
|
||||
|
||||
private InterruptControl _interruptControl;
|
||||
private Memory _memory;
|
||||
|
||||
private struct Channel
|
||||
{
|
||||
internal UInt32 _source;
|
||||
internal UInt32 _sourceReload;
|
||||
internal UInt32 _destination;
|
||||
internal UInt32 _destinationReload;
|
||||
internal UInt32 _length;
|
||||
internal UInt16 _lengthReload;
|
||||
internal UInt16 _control;
|
||||
internal bool _running;
|
||||
}
|
||||
|
||||
private Channel _channel0;
|
||||
private Channel _channel1;
|
||||
private Channel _channel2;
|
||||
private Channel _channel3;
|
||||
|
||||
private const UInt32 MaxLengthChannel0 = 0x4000;
|
||||
private const UInt32 MaxLengthChannel1 = 0x4000;
|
||||
private const UInt32 MaxLengthChannel2 = 0x4000;
|
||||
private const UInt32 MaxLengthChannel3 = 0x1_0000;
|
||||
|
||||
internal DMA(Common.Scheduler scheduler)
|
||||
{
|
||||
_scheduler = scheduler;
|
||||
|
||||
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel0, _ => Start(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0));
|
||||
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel1, _ => Start(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1));
|
||||
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel2, _ => Start(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2));
|
||||
_scheduler.RegisterTask((int)GBA_System.TaskId.StartDMA_Channel3, _ => Start(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3));
|
||||
}
|
||||
|
||||
internal void Initialize(InterruptControl interruptControl, Memory memory)
|
||||
{
|
||||
_interruptControl = interruptControl;
|
||||
_memory = memory;
|
||||
}
|
||||
|
||||
internal void ResetState()
|
||||
{
|
||||
_channel0 = default;
|
||||
_channel1 = default;
|
||||
_channel2 = default;
|
||||
_channel3 = default;
|
||||
}
|
||||
|
||||
internal void LoadState(BinaryReader reader)
|
||||
{
|
||||
void LoadChannel(ref Channel channel)
|
||||
{
|
||||
channel._source = reader.ReadUInt32();
|
||||
channel._sourceReload = reader.ReadUInt32();
|
||||
channel._destination = reader.ReadUInt32();
|
||||
channel._destinationReload = reader.ReadUInt32();
|
||||
channel._length = reader.ReadUInt32();
|
||||
channel._lengthReload = reader.ReadUInt16();
|
||||
channel._control = reader.ReadUInt16();
|
||||
channel._running = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
LoadChannel(ref _channel0);
|
||||
LoadChannel(ref _channel1);
|
||||
LoadChannel(ref _channel2);
|
||||
LoadChannel(ref _channel3);
|
||||
}
|
||||
|
||||
internal void SaveState(BinaryWriter writer)
|
||||
{
|
||||
void SaveChannel(Channel channel)
|
||||
{
|
||||
writer.Write(channel._source);
|
||||
writer.Write(channel._sourceReload);
|
||||
writer.Write(channel._destination);
|
||||
writer.Write(channel._destinationReload);
|
||||
writer.Write(channel._length);
|
||||
writer.Write(channel._lengthReload);
|
||||
writer.Write(channel._control);
|
||||
writer.Write(channel._running);
|
||||
}
|
||||
|
||||
SaveChannel(_channel0);
|
||||
SaveChannel(_channel1);
|
||||
SaveChannel(_channel2);
|
||||
SaveChannel(_channel3);
|
||||
}
|
||||
|
||||
internal UInt16 ReadRegister(Register register)
|
||||
{
|
||||
return register switch
|
||||
{
|
||||
Register.DMA0CNT_H => _channel0._control,
|
||||
Register.DMA1CNT_H => _channel1._control,
|
||||
Register.DMA2CNT_H => _channel2._control,
|
||||
Register.DMA3CNT_H => _channel3._control,
|
||||
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.DMA: Register read error"),
|
||||
};
|
||||
}
|
||||
|
||||
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
||||
{
|
||||
void WriteSourceReload_Low(ref Channel channel)
|
||||
{
|
||||
UInt16 low = (UInt16)channel._sourceReload;
|
||||
Memory.WriteRegisterHelper(ref low, value, mode);
|
||||
channel._sourceReload = (channel._sourceReload & 0xffff_0000) | low;
|
||||
}
|
||||
|
||||
void WriteSourceReload_High(ref Channel channel, UInt16 mask)
|
||||
{
|
||||
UInt16 high = (UInt16)(channel._sourceReload >> 16);
|
||||
Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode);
|
||||
channel._sourceReload = (channel._sourceReload & 0x0000_ffff) | (UInt32)(high << 16);
|
||||
}
|
||||
|
||||
void WriteDestinationReload_Low(ref Channel channel)
|
||||
{
|
||||
UInt16 low = (UInt16)channel._destinationReload;
|
||||
Memory.WriteRegisterHelper(ref low, value, mode);
|
||||
channel._destinationReload = (channel._destinationReload & 0xffff_0000) | low;
|
||||
}
|
||||
|
||||
void WriteDestinationReload_High(ref Channel channel, UInt16 mask)
|
||||
{
|
||||
UInt16 high = (UInt16)(channel._destinationReload >> 16);
|
||||
Memory.WriteRegisterHelper(ref high, (UInt16)(value & mask), mode);
|
||||
channel._destinationReload = (channel._destinationReload & 0x0000_ffff) | (UInt32)(high << 16);
|
||||
}
|
||||
|
||||
void WriteLengthReload(ref Channel channel)
|
||||
{
|
||||
UInt16 reload = channel._lengthReload;
|
||||
Memory.WriteRegisterHelper(ref reload, value, mode);
|
||||
channel._lengthReload = reload;
|
||||
}
|
||||
|
||||
void WriteControl(ref Channel channel, GBA_System.TaskId startTaskId)
|
||||
{
|
||||
UInt16 previousControl = channel._control;
|
||||
|
||||
UInt16 newControl = channel._control;
|
||||
Memory.WriteRegisterHelper(ref newControl, value, mode);
|
||||
channel._control = newControl;
|
||||
|
||||
if ((previousControl & 0x8000) == 0)
|
||||
{
|
||||
if ((newControl & 0x8000) == 0x8000)
|
||||
_scheduler.ScheduleTask((int)startTaskId, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((newControl & 0x8000) == 0)
|
||||
{
|
||||
if (channel._running)
|
||||
channel._running = false;
|
||||
else
|
||||
_scheduler.CancelTask((int)startTaskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (register)
|
||||
{
|
||||
case Register.DMA0SAD_L:
|
||||
WriteSourceReload_Low(ref _channel0);
|
||||
break;
|
||||
case Register.DMA0SAD_H:
|
||||
WriteSourceReload_High(ref _channel0, 0x07ff);
|
||||
break;
|
||||
|
||||
case Register.DMA0DAD_L:
|
||||
WriteDestinationReload_Low(ref _channel0);
|
||||
break;
|
||||
case Register.DMA0DAD_H:
|
||||
WriteDestinationReload_High(ref _channel0, 0x07ff);
|
||||
break;
|
||||
|
||||
case Register.DMA0CNT_L:
|
||||
WriteLengthReload(ref _channel0);
|
||||
break;
|
||||
case Register.DMA0CNT_H:
|
||||
WriteControl(ref _channel0, GBA_System.TaskId.StartDMA_Channel0);
|
||||
break;
|
||||
|
||||
case Register.DMA1SAD_L:
|
||||
WriteSourceReload_Low(ref _channel1);
|
||||
break;
|
||||
case Register.DMA1SAD_H:
|
||||
WriteSourceReload_High(ref _channel1, 0x0fff);
|
||||
break;
|
||||
|
||||
case Register.DMA1DAD_L:
|
||||
WriteDestinationReload_Low(ref _channel1);
|
||||
break;
|
||||
case Register.DMA1DAD_H:
|
||||
WriteDestinationReload_High(ref _channel1, 0x07ff);
|
||||
break;
|
||||
|
||||
case Register.DMA1CNT_L:
|
||||
WriteLengthReload(ref _channel1);
|
||||
break;
|
||||
case Register.DMA1CNT_H:
|
||||
WriteControl(ref _channel1, GBA_System.TaskId.StartDMA_Channel1);
|
||||
break;
|
||||
|
||||
case Register.DMA2SAD_L:
|
||||
WriteSourceReload_Low(ref _channel2);
|
||||
break;
|
||||
case Register.DMA2SAD_H:
|
||||
WriteSourceReload_High(ref _channel2, 0x0fff);
|
||||
break;
|
||||
|
||||
case Register.DMA2DAD_L:
|
||||
WriteDestinationReload_Low(ref _channel2);
|
||||
break;
|
||||
case Register.DMA2DAD_H:
|
||||
WriteDestinationReload_High(ref _channel2, 0x07ff);
|
||||
break;
|
||||
|
||||
case Register.DMA2CNT_L:
|
||||
WriteLengthReload(ref _channel2);
|
||||
break;
|
||||
case Register.DMA2CNT_H:
|
||||
WriteControl(ref _channel2, GBA_System.TaskId.StartDMA_Channel2);
|
||||
break;
|
||||
|
||||
case Register.DMA3SAD_L:
|
||||
WriteSourceReload_Low(ref _channel3);
|
||||
break;
|
||||
case Register.DMA3SAD_H:
|
||||
WriteSourceReload_High(ref _channel3, 0x0fff);
|
||||
break;
|
||||
|
||||
case Register.DMA3DAD_L:
|
||||
WriteDestinationReload_Low(ref _channel3);
|
||||
break;
|
||||
case Register.DMA3DAD_H:
|
||||
WriteDestinationReload_High(ref _channel3, 0x0fff);
|
||||
break;
|
||||
|
||||
case Register.DMA3CNT_L:
|
||||
WriteLengthReload(ref _channel3);
|
||||
break;
|
||||
case Register.DMA3CNT_H:
|
||||
WriteControl(ref _channel3, GBA_System.TaskId.StartDMA_Channel3);
|
||||
break;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.DMA: Register write error");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void PerformVBlankTransfers()
|
||||
{
|
||||
if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
||||
PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0);
|
||||
|
||||
if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
||||
PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1);
|
||||
|
||||
if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
||||
PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2);
|
||||
|
||||
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.VBlank))
|
||||
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void PerformHBlankTransfers()
|
||||
{
|
||||
if (_channel0._running && (((_channel0._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
||||
PerformTransfer(ref _channel0, InterruptControl.Interrupt.DMA0, MaxLengthChannel0);
|
||||
|
||||
if (_channel1._running && (((_channel1._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
||||
PerformTransfer(ref _channel1, InterruptControl.Interrupt.DMA1, MaxLengthChannel1);
|
||||
|
||||
if (_channel2._running && (((_channel2._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
||||
PerformTransfer(ref _channel2, InterruptControl.Interrupt.DMA2, MaxLengthChannel2);
|
||||
|
||||
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.HBlank))
|
||||
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
|
||||
}
|
||||
|
||||
internal void PerformVideoTransfer(bool disable)
|
||||
{
|
||||
if (_channel3._running && (((_channel3._control >> 12) & 0b11) == (int)StartTiming.Special))
|
||||
{
|
||||
PerformTransfer(ref _channel3, InterruptControl.Interrupt.DMA3, MaxLengthChannel3);
|
||||
|
||||
if (disable)
|
||||
{
|
||||
_channel3._control = (UInt16)(_channel3._control & ~0x8000);
|
||||
_channel3._running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Start(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength)
|
||||
{
|
||||
channel._source = channel._sourceReload;
|
||||
channel._destination = channel._destinationReload;
|
||||
channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload;
|
||||
channel._running = true;
|
||||
|
||||
if (((channel._control >> 12) & 0b11) == (int)StartTiming.Immediate)
|
||||
PerformTransfer(ref channel, interrupt, maxLength);
|
||||
}
|
||||
|
||||
private void PerformTransfer(ref Channel channel, InterruptControl.Interrupt interrupt, UInt32 maxLength)
|
||||
{
|
||||
UInt16 sourceAddressControlFlag = (UInt16)((channel._control >> 7) & 0b11);
|
||||
UInt16 destinationAddressControlFlag = (UInt16)((channel._control >> 5) & 0b11);
|
||||
|
||||
int GetSourceIncrement(int dataUnitSize)
|
||||
{
|
||||
return sourceAddressControlFlag switch
|
||||
{
|
||||
// increment
|
||||
0b00 => dataUnitSize,
|
||||
// decrement
|
||||
0b01 => -dataUnitSize,
|
||||
// fixed
|
||||
0b10 => 0,
|
||||
// prohibited
|
||||
0b11 => 0,
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.DMA: Wrong source address control flag"),
|
||||
};
|
||||
}
|
||||
|
||||
(int destinationIncrement, bool reloadDestination) GetDestinationIncrement(int dataUnitSize)
|
||||
{
|
||||
return destinationAddressControlFlag switch
|
||||
{
|
||||
// increment
|
||||
0b00 => (dataUnitSize, false),
|
||||
// decrement
|
||||
0b01 => (-dataUnitSize, false),
|
||||
// fixed
|
||||
0b10 => (0, false),
|
||||
// increment+reload
|
||||
0b11 => (dataUnitSize, true),
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.DMA: Wrong destination address control flag"),
|
||||
};
|
||||
}
|
||||
|
||||
bool reloadDestination;
|
||||
|
||||
// 16 bits
|
||||
if ((channel._control & 0x0400) == 0)
|
||||
{
|
||||
const int DataUnitSize = 2;
|
||||
|
||||
int sourceIncrement = GetSourceIncrement(DataUnitSize);
|
||||
//(int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize);
|
||||
|
||||
var v2 = GetDestinationIncrement(DataUnitSize);
|
||||
int destinationIncrement = v2.destinationIncrement;
|
||||
reloadDestination = v2.reloadDestination;
|
||||
|
||||
for (; channel._length > 0; --channel._length)
|
||||
{
|
||||
//_memory.Write16(channel._destination, _memory.Read16(channel._source));
|
||||
_memory.Write16(channel._destination, _memory.Read16(channel._source));
|
||||
|
||||
channel._source = (UInt32)(channel._source + sourceIncrement);
|
||||
channel._destination = (UInt32)(channel._destination + destinationIncrement);
|
||||
|
||||
_scheduler.AdvanceCycleCounter(2);
|
||||
}
|
||||
}
|
||||
|
||||
// 32 bits
|
||||
else
|
||||
{
|
||||
const int DataUnitSize = 4;
|
||||
|
||||
int sourceIncrement = GetSourceIncrement(DataUnitSize);
|
||||
//(int destinationIncrement, reloadDestination) = GetDestinationIncrement(DataUnitSize);
|
||||
var v2 = GetDestinationIncrement(DataUnitSize);
|
||||
int destinationIncrement = v2.destinationIncrement;
|
||||
reloadDestination = v2.reloadDestination;
|
||||
|
||||
for (; channel._length > 0; --channel._length)
|
||||
{
|
||||
_memory.Write32(channel._destination, _memory.Read32(channel._source));
|
||||
|
||||
channel._source = (UInt32)(channel._source + sourceIncrement);
|
||||
channel._destination = (UInt32)(channel._destination + destinationIncrement);
|
||||
|
||||
_scheduler.AdvanceCycleCounter(2);
|
||||
}
|
||||
}
|
||||
|
||||
if ((channel._control & 0x4000) == 0x4000)
|
||||
_interruptControl.RequestInterrupt(interrupt);
|
||||
|
||||
// Repeat off
|
||||
if ((channel._control & 0x0200) == 0)
|
||||
{
|
||||
channel._control = (UInt16)(channel._control & ~0x8000);
|
||||
channel._running = false;
|
||||
}
|
||||
|
||||
// Repeat on
|
||||
else
|
||||
{
|
||||
if (reloadDestination)
|
||||
channel._destination = channel._destinationReload;
|
||||
|
||||
channel._length = (channel._lengthReload == 0) ? maxLength : channel._lengthReload;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0dcd66b296ec4434b836a1075fb8a12a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,180 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
public sealed class GBA_System : Common.System
|
||||
{
|
||||
internal enum TaskId
|
||||
{
|
||||
// ---- Timer ----
|
||||
StartTimer_Channel0,
|
||||
StartTimer_Channel1,
|
||||
StartTimer_Channel2,
|
||||
StartTimer_Channel3,
|
||||
|
||||
HandleTimerOverflow_Channel0,
|
||||
HandleTimerOverflow_Channel1,
|
||||
HandleTimerOverflow_Channel2,
|
||||
HandleTimerOverflow_Channel3,
|
||||
|
||||
// ---- DMA ----
|
||||
StartDMA_Channel0,
|
||||
StartDMA_Channel1,
|
||||
StartDMA_Channel2,
|
||||
StartDMA_Channel3,
|
||||
|
||||
// ---- KeyInput ----
|
||||
CheckKeyInterrupt,
|
||||
|
||||
// ---- Video ----
|
||||
StartHBlank,
|
||||
StartScanline
|
||||
}
|
||||
|
||||
private static readonly int s_taskIdCount = Enum.GetNames(typeof(TaskId)).Length;
|
||||
private readonly Common.Scheduler _scheduler = new(s_taskIdCount, 2 * s_taskIdCount);
|
||||
|
||||
private readonly CPU.CPU_Core _cpu;
|
||||
private readonly Communication _communication = new();
|
||||
private readonly Timer _timer;
|
||||
private readonly Sound _sound = new();
|
||||
private readonly DMA _dma;
|
||||
private readonly KeyInput _keyInput;
|
||||
private readonly SystemControl _systemControl = new();
|
||||
private readonly InterruptControl _interruptControl = new();
|
||||
private readonly Memory _memory = new();
|
||||
private readonly Video _video;
|
||||
private readonly BIOS _bios = new();
|
||||
|
||||
private string _romHash;
|
||||
private bool _running;
|
||||
|
||||
private const string StateSaveMagic = "IRISGBA";
|
||||
private const int StateSaveVersion = 1;
|
||||
|
||||
public GBA_System(PollInput_Delegate pollInputCallback, PresentFrame_Delegate presentFrameCallback)
|
||||
{
|
||||
CPU.CPU_Core.CallbackInterface cpuCallbackInterface = new(_memory.Read8, _memory.Read16, _memory.Read32, _memory.Write8, _memory.Write16, _memory.Write32, _bios.HandleSWI, _bios.HandleIRQ);
|
||||
|
||||
_cpu = new(CPU.CPU_Core.Model.ARM7TDMI, cpuCallbackInterface);
|
||||
_timer = new(_scheduler);
|
||||
_dma = new(_scheduler);
|
||||
_keyInput = new(_scheduler, pollInputCallback);
|
||||
_video = new(_scheduler, presentFrameCallback);
|
||||
|
||||
_communication.Initialize(_interruptControl);
|
||||
_timer.Initialize(_interruptControl);
|
||||
_dma.Initialize(_interruptControl, _memory);
|
||||
_keyInput.Initialize(_interruptControl);
|
||||
_interruptControl.Initialize(_cpu);
|
||||
_memory.Initialize(_communication, _timer, _sound, _dma, _keyInput, _systemControl, _interruptControl, _video, _bios);
|
||||
_video.Initialize(_dma, _interruptControl, _memory);
|
||||
_bios.Initialize(_cpu, _memory);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_memory.Dispose();
|
||||
_video.Dispose();
|
||||
_bios.Dispose();
|
||||
}
|
||||
|
||||
public override void ResetState(bool skipIntro)
|
||||
{
|
||||
_scheduler.ResetState(); // This has to be done first
|
||||
|
||||
_cpu.ResetState();
|
||||
_communication.ResetState();
|
||||
_timer.ResetState();
|
||||
_sound.ResetState();
|
||||
_dma.ResetState();
|
||||
_keyInput.ResetState();
|
||||
_systemControl.ResetState();
|
||||
_interruptControl.ResetState();
|
||||
_memory.ResetState();
|
||||
_video.ResetState();
|
||||
|
||||
_bios.Reset(skipIntro); // This has to be done last
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader)
|
||||
{
|
||||
if (reader.ReadString() != StateSaveMagic)
|
||||
throw new Exception("Iris.GBA.GBA_System: Wrong state save magic");
|
||||
|
||||
if (reader.ReadInt32() != StateSaveVersion)
|
||||
throw new Exception("Iris.GBA.GBA_System: Wrong state save version");
|
||||
|
||||
if (reader.ReadString() != _romHash)
|
||||
throw new Exception("Iris.GBA.GBA_System: Wrong ROM hash");
|
||||
|
||||
_scheduler.LoadState(reader);
|
||||
_cpu.LoadState(reader);
|
||||
_communication.LoadState(reader);
|
||||
_timer.LoadState(reader);
|
||||
_sound.LoadState(reader);
|
||||
_dma.LoadState(reader);
|
||||
_keyInput.LoadState(reader);
|
||||
_systemControl.LoadState(reader);
|
||||
_interruptControl.LoadState(reader);
|
||||
_memory.LoadState(reader);
|
||||
_video.LoadState(reader);
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(StateSaveMagic);
|
||||
writer.Write(StateSaveVersion);
|
||||
writer.Write(_romHash);
|
||||
|
||||
_scheduler.SaveState(writer);
|
||||
_cpu.SaveState(writer);
|
||||
_communication.SaveState(writer);
|
||||
_timer.SaveState(writer);
|
||||
_sound.SaveState(writer);
|
||||
_dma.SaveState(writer);
|
||||
_keyInput.SaveState(writer);
|
||||
_systemControl.SaveState(writer);
|
||||
_interruptControl.SaveState(writer);
|
||||
_memory.SaveState(writer);
|
||||
_video.SaveState(writer);
|
||||
}
|
||||
|
||||
public override void LoadROM(string filename)
|
||||
{
|
||||
_memory.LoadROM(filename);
|
||||
|
||||
using HashAlgorithm hashAlgorithm = SHA512.Create();
|
||||
using FileStream fileStream = File.OpenRead(filename);
|
||||
_romHash = BitConverter.ToString(hashAlgorithm.ComputeHash(fileStream));
|
||||
}
|
||||
|
||||
public override void SetKeyStatus(Key key, KeyStatus status)
|
||||
{
|
||||
_keyInput.SetKeyStatus(key, status);
|
||||
}
|
||||
|
||||
public override bool IsRunning()
|
||||
{
|
||||
return _running;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
_running = true;
|
||||
|
||||
while (_running)
|
||||
{
|
||||
UInt64 cycleCount = _cpu.Step();
|
||||
_scheduler.AdvanceCycleCounter(cycleCount);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bc3a1e87e31e9a4abef36e543dab2c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class InterruptControl
|
||||
{
|
||||
internal enum Register
|
||||
{
|
||||
IE,
|
||||
IF,
|
||||
IME
|
||||
}
|
||||
|
||||
internal enum Interrupt
|
||||
{
|
||||
VBlank = 1 << 0,
|
||||
HBlank = 1 << 1,
|
||||
VCountMatch = 1 << 2,
|
||||
Timer0 = 1 << 3,
|
||||
Timer1 = 1 << 4,
|
||||
Timer2 = 1 << 5,
|
||||
Timer3 = 1 << 6,
|
||||
SIO = 1 << 7,
|
||||
DMA0 = 1 << 8,
|
||||
DMA1 = 1 << 9,
|
||||
DMA2 = 1 << 10,
|
||||
DMA3 = 1 << 11,
|
||||
Key = 1 << 12,
|
||||
//GamePak = 1 << 13
|
||||
}
|
||||
|
||||
private UInt16 _IE;
|
||||
private UInt16 _IF;
|
||||
private UInt16 _IME;
|
||||
|
||||
private CPU.CPU_Core _cpu;
|
||||
|
||||
internal void Initialize(CPU.CPU_Core cpu)
|
||||
{
|
||||
_cpu = cpu;
|
||||
}
|
||||
|
||||
internal void ResetState()
|
||||
{
|
||||
_IE = 0;
|
||||
_IF = 0;
|
||||
_IME = 0;
|
||||
}
|
||||
|
||||
internal void LoadState(BinaryReader reader)
|
||||
{
|
||||
_IE = reader.ReadUInt16();
|
||||
_IF = reader.ReadUInt16();
|
||||
_IME = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
internal void SaveState(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(_IE);
|
||||
writer.Write(_IF);
|
||||
writer.Write(_IME);
|
||||
}
|
||||
|
||||
internal UInt16 ReadRegister(Register register)
|
||||
{
|
||||
return register switch
|
||||
{
|
||||
Register.IE => _IE,
|
||||
Register.IF => _IF,
|
||||
Register.IME => _IME,
|
||||
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.InterruptControl: Register read error"),
|
||||
};
|
||||
}
|
||||
|
||||
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
case Register.IE:
|
||||
Memory.WriteRegisterHelper(ref _IE, value, mode);
|
||||
break;
|
||||
|
||||
case Register.IF:
|
||||
switch (mode)
|
||||
{
|
||||
case Memory.RegisterWriteMode.LowByte:
|
||||
_IF &= (UInt16)~value;
|
||||
break;
|
||||
case Memory.RegisterWriteMode.HighByte:
|
||||
_IF &= (UInt16)~(value << 8);
|
||||
break;
|
||||
case Memory.RegisterWriteMode.HalfWord:
|
||||
_IF &= (UInt16)~value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Register.IME:
|
||||
Memory.WriteRegisterHelper(ref _IME, value, mode);
|
||||
break;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.InterruptControl: Register write error");
|
||||
}
|
||||
|
||||
CheckInterrupts();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RequestInterrupt(Interrupt interrupt)
|
||||
{
|
||||
_IF |= (UInt16)interrupt;
|
||||
CheckInterrupts();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CheckInterrupts()
|
||||
{
|
||||
_cpu.NIRQ = ((_IME == 0) || ((_IE & _IF) == 0)) ? CPU.CPU_Core.Signal.High : CPU.CPU_Core.Signal.Low;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c58052e2ebb4549418cb2d58b101d5ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,179 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class KeyInput
|
||||
{
|
||||
internal enum Register
|
||||
{
|
||||
KEYINPUT,
|
||||
KEYCNT
|
||||
}
|
||||
|
||||
private UInt16 _KEYINPUT;
|
||||
private UInt16 _KEYCNT;
|
||||
|
||||
private readonly Common.Scheduler _scheduler;
|
||||
private readonly Common.System.PollInput_Delegate _pollInputCallback;
|
||||
|
||||
private InterruptControl _interruptControl;
|
||||
|
||||
private const UInt64 CheckInterruptCycleCount = 280_896; // once per frame
|
||||
private bool _checkingInterrupt;
|
||||
|
||||
internal KeyInput(Common.Scheduler scheduler, Common.System.PollInput_Delegate pollInputCallback)
|
||||
{
|
||||
_scheduler = scheduler;
|
||||
_pollInputCallback = pollInputCallback;
|
||||
|
||||
_scheduler.RegisterTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterrupt);
|
||||
}
|
||||
|
||||
internal void Initialize(InterruptControl interruptControl)
|
||||
{
|
||||
_interruptControl = interruptControl;
|
||||
}
|
||||
|
||||
internal void ResetState()
|
||||
{
|
||||
_KEYINPUT = 0x03ff;
|
||||
_KEYCNT = 0;
|
||||
}
|
||||
|
||||
internal void LoadState(BinaryReader reader)
|
||||
{
|
||||
_KEYINPUT = reader.ReadUInt16();
|
||||
_KEYCNT = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
internal void SaveState(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(_KEYINPUT);
|
||||
writer.Write(_KEYCNT);
|
||||
}
|
||||
|
||||
internal UInt16 ReadRegister(Register register)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
case Register.KEYINPUT:
|
||||
_pollInputCallback();
|
||||
|
||||
if ((_KEYCNT & 0x4000) == 0x4000)
|
||||
CheckInterrupt();
|
||||
|
||||
return _KEYINPUT;
|
||||
|
||||
case Register.KEYCNT:
|
||||
return _KEYCNT;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.KeyInput: Register read error");
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
case Register.KEYCNT:
|
||||
Memory.WriteRegisterHelper(ref _KEYCNT, value, mode);
|
||||
|
||||
if ((_KEYCNT & 0x4000) == 0x4000)
|
||||
{
|
||||
_pollInputCallback();
|
||||
CheckInterrupt();
|
||||
|
||||
if (!_checkingInterrupt)
|
||||
{
|
||||
_checkingInterrupt = true;
|
||||
_scheduler.ScheduleTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterruptCycleCount);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.KeyInput: Register write error");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void SetKeyStatus(Common.System.Key key, Common.System.KeyStatus status)
|
||||
{
|
||||
int pos;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case Common.System.Key.A:
|
||||
pos = 0;
|
||||
break;
|
||||
case Common.System.Key.B:
|
||||
pos = 1;
|
||||
break;
|
||||
case Common.System.Key.Select:
|
||||
pos = 2;
|
||||
break;
|
||||
case Common.System.Key.Start:
|
||||
pos = 3;
|
||||
break;
|
||||
case Common.System.Key.Right:
|
||||
pos = 4;
|
||||
break;
|
||||
case Common.System.Key.Left:
|
||||
pos = 5;
|
||||
break;
|
||||
case Common.System.Key.Up:
|
||||
pos = 6;
|
||||
break;
|
||||
case Common.System.Key.Down:
|
||||
pos = 7;
|
||||
break;
|
||||
case Common.System.Key.R:
|
||||
pos = 8;
|
||||
break;
|
||||
case Common.System.Key.L:
|
||||
pos = 9;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
_KEYINPUT = (UInt16)((_KEYINPUT & ~(1 << pos)) | ((int)status << pos));
|
||||
}
|
||||
|
||||
private void CheckInterrupt(UInt64 cycleCountDelay)
|
||||
{
|
||||
if ((_KEYCNT & 0x4000) == 0)
|
||||
{
|
||||
_checkingInterrupt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_pollInputCallback();
|
||||
CheckInterrupt();
|
||||
|
||||
_scheduler.ScheduleTask((int)GBA_System.TaskId.CheckKeyInterrupt, CheckInterruptCycleCount - cycleCountDelay);
|
||||
}
|
||||
|
||||
private void CheckInterrupt()
|
||||
{
|
||||
UInt16 mask = (UInt16)(_KEYCNT & 0x03ff);
|
||||
|
||||
if ((_KEYCNT & 0x8000) == 0)
|
||||
{
|
||||
if ((~_KEYINPUT & mask) != 0)
|
||||
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: figure out what happens when mask == 0
|
||||
if ((~_KEYINPUT & mask) == mask)
|
||||
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee2423bcb9113dc4580cb958b890e91d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@ -1,351 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class Sound
|
||||
{
|
||||
internal enum Register
|
||||
{
|
||||
SOUND1CNT_L,
|
||||
SOUND1CNT_H,
|
||||
SOUND1CNT_X,
|
||||
|
||||
SOUND2CNT_L,
|
||||
SOUND2CNT_H,
|
||||
|
||||
SOUND3CNT_L,
|
||||
SOUND3CNT_H,
|
||||
SOUND3CNT_X,
|
||||
|
||||
SOUND4CNT_L,
|
||||
SOUND4CNT_H,
|
||||
|
||||
SOUNDCNT_L,
|
||||
SOUNDCNT_H,
|
||||
SOUNDCNT_X,
|
||||
|
||||
SOUNDBIAS,
|
||||
|
||||
WAVE_RAM0_L,
|
||||
WAVE_RAM0_H,
|
||||
|
||||
WAVE_RAM1_L,
|
||||
WAVE_RAM1_H,
|
||||
|
||||
WAVE_RAM2_L,
|
||||
WAVE_RAM2_H,
|
||||
|
||||
WAVE_RAM3_L,
|
||||
WAVE_RAM3_H,
|
||||
|
||||
FIFO_A_L,
|
||||
FIFO_A_H,
|
||||
|
||||
FIFO_B_L,
|
||||
FIFO_B_H
|
||||
}
|
||||
|
||||
private UInt16 _SOUND1CNT_L;
|
||||
private UInt16 _SOUND1CNT_H;
|
||||
private UInt16 _SOUND1CNT_X;
|
||||
|
||||
private UInt16 _SOUND2CNT_L;
|
||||
private UInt16 _SOUND2CNT_H;
|
||||
|
||||
private UInt16 _SOUND3CNT_L;
|
||||
private UInt16 _SOUND3CNT_H;
|
||||
private UInt16 _SOUND3CNT_X;
|
||||
|
||||
private UInt16 _SOUND4CNT_L;
|
||||
private UInt16 _SOUND4CNT_H;
|
||||
|
||||
private UInt16 _SOUNDCNT_L;
|
||||
private UInt16 _SOUNDCNT_H;
|
||||
private UInt16 _SOUNDCNT_X;
|
||||
|
||||
private UInt16 _SOUNDBIAS;
|
||||
|
||||
private UInt16 _WAVE_RAM0_L;
|
||||
private UInt16 _WAVE_RAM0_H;
|
||||
|
||||
private UInt16 _WAVE_RAM1_L;
|
||||
private UInt16 _WAVE_RAM1_H;
|
||||
|
||||
private UInt16 _WAVE_RAM2_L;
|
||||
private UInt16 _WAVE_RAM2_H;
|
||||
|
||||
private UInt16 _WAVE_RAM3_L;
|
||||
private UInt16 _WAVE_RAM3_H;
|
||||
|
||||
private UInt16 _FIFO_A_L;
|
||||
private UInt16 _FIFO_A_H;
|
||||
|
||||
private UInt16 _FIFO_B_L;
|
||||
private UInt16 _FIFO_B_H;
|
||||
|
||||
internal void ResetState()
|
||||
{
|
||||
_SOUND1CNT_L = 0;
|
||||
_SOUND1CNT_H = 0;
|
||||
_SOUND1CNT_X = 0;
|
||||
|
||||
_SOUND2CNT_L = 0;
|
||||
_SOUND2CNT_H = 0;
|
||||
|
||||
_SOUND3CNT_L = 0;
|
||||
_SOUND3CNT_H = 0;
|
||||
_SOUND3CNT_X = 0;
|
||||
|
||||
_SOUND4CNT_L = 0;
|
||||
_SOUND4CNT_H = 0;
|
||||
|
||||
_SOUNDCNT_L = 0;
|
||||
_SOUNDCNT_H = 0;
|
||||
_SOUNDCNT_X = 0;
|
||||
|
||||
_SOUNDBIAS = 0;
|
||||
|
||||
_WAVE_RAM0_L = 0;
|
||||
_WAVE_RAM0_H = 0;
|
||||
|
||||
_WAVE_RAM1_L = 0;
|
||||
_WAVE_RAM1_H = 0;
|
||||
|
||||
_WAVE_RAM2_L = 0;
|
||||
_WAVE_RAM2_H = 0;
|
||||
|
||||
_WAVE_RAM3_L = 0;
|
||||
_WAVE_RAM3_H = 0;
|
||||
|
||||
_FIFO_A_L = 0;
|
||||
_FIFO_A_H = 0;
|
||||
|
||||
_FIFO_B_L = 0;
|
||||
_FIFO_B_H = 0;
|
||||
}
|
||||
|
||||
internal void LoadState(BinaryReader reader)
|
||||
{
|
||||
_SOUND1CNT_L = reader.ReadUInt16();
|
||||
_SOUND1CNT_H = reader.ReadUInt16();
|
||||
_SOUND1CNT_X = reader.ReadUInt16();
|
||||
|
||||
_SOUND2CNT_L = reader.ReadUInt16();
|
||||
_SOUND2CNT_H = reader.ReadUInt16();
|
||||
|
||||
_SOUND3CNT_L = reader.ReadUInt16();
|
||||
_SOUND3CNT_H = reader.ReadUInt16();
|
||||
_SOUND3CNT_X = reader.ReadUInt16();
|
||||
|
||||
_SOUND4CNT_L = reader.ReadUInt16();
|
||||
_SOUND4CNT_H = reader.ReadUInt16();
|
||||
|
||||
_SOUNDCNT_L = reader.ReadUInt16();
|
||||
_SOUNDCNT_H = reader.ReadUInt16();
|
||||
_SOUNDCNT_X = reader.ReadUInt16();
|
||||
|
||||
_SOUNDBIAS = reader.ReadUInt16();
|
||||
|
||||
_WAVE_RAM0_L = reader.ReadUInt16();
|
||||
_WAVE_RAM0_H = reader.ReadUInt16();
|
||||
|
||||
_WAVE_RAM1_L = reader.ReadUInt16();
|
||||
_WAVE_RAM1_H = reader.ReadUInt16();
|
||||
|
||||
_WAVE_RAM2_L = reader.ReadUInt16();
|
||||
_WAVE_RAM2_H = reader.ReadUInt16();
|
||||
|
||||
_WAVE_RAM3_L = reader.ReadUInt16();
|
||||
_WAVE_RAM3_H = reader.ReadUInt16();
|
||||
|
||||
_FIFO_A_L = reader.ReadUInt16();
|
||||
_FIFO_A_H = reader.ReadUInt16();
|
||||
|
||||
_FIFO_B_L = reader.ReadUInt16();
|
||||
_FIFO_B_H = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
internal void SaveState(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(_SOUND1CNT_L);
|
||||
writer.Write(_SOUND1CNT_H);
|
||||
writer.Write(_SOUND1CNT_X);
|
||||
|
||||
writer.Write(_SOUND2CNT_L);
|
||||
writer.Write(_SOUND2CNT_H);
|
||||
|
||||
writer.Write(_SOUND3CNT_L);
|
||||
writer.Write(_SOUND3CNT_H);
|
||||
writer.Write(_SOUND3CNT_X);
|
||||
|
||||
writer.Write(_SOUND4CNT_L);
|
||||
writer.Write(_SOUND4CNT_H);
|
||||
|
||||
writer.Write(_SOUNDCNT_L);
|
||||
writer.Write(_SOUNDCNT_H);
|
||||
writer.Write(_SOUNDCNT_X);
|
||||
|
||||
writer.Write(_SOUNDBIAS);
|
||||
|
||||
writer.Write(_WAVE_RAM0_L);
|
||||
writer.Write(_WAVE_RAM0_H);
|
||||
|
||||
writer.Write(_WAVE_RAM1_L);
|
||||
writer.Write(_WAVE_RAM1_H);
|
||||
|
||||
writer.Write(_WAVE_RAM2_L);
|
||||
writer.Write(_WAVE_RAM2_H);
|
||||
|
||||
writer.Write(_WAVE_RAM3_L);
|
||||
writer.Write(_WAVE_RAM3_H);
|
||||
|
||||
writer.Write(_FIFO_A_L);
|
||||
writer.Write(_FIFO_A_H);
|
||||
|
||||
writer.Write(_FIFO_B_L);
|
||||
writer.Write(_FIFO_B_H);
|
||||
}
|
||||
|
||||
internal UInt16 ReadRegister(Register register)
|
||||
{
|
||||
return register switch
|
||||
{
|
||||
Register.SOUND1CNT_L => _SOUND1CNT_L,
|
||||
Register.SOUND1CNT_H => _SOUND1CNT_H,
|
||||
Register.SOUND1CNT_X => _SOUND1CNT_X,
|
||||
|
||||
Register.SOUND2CNT_L => _SOUND2CNT_L,
|
||||
Register.SOUND2CNT_H => _SOUND2CNT_H,
|
||||
|
||||
Register.SOUND3CNT_L => _SOUND3CNT_L,
|
||||
Register.SOUND3CNT_H => _SOUND3CNT_H,
|
||||
Register.SOUND3CNT_X => _SOUND3CNT_X,
|
||||
|
||||
Register.SOUND4CNT_L => _SOUND4CNT_L,
|
||||
Register.SOUND4CNT_H => _SOUND4CNT_H,
|
||||
|
||||
Register.SOUNDCNT_L => _SOUNDCNT_L,
|
||||
Register.SOUNDCNT_H => _SOUNDCNT_H,
|
||||
Register.SOUNDCNT_X => _SOUNDCNT_X,
|
||||
|
||||
Register.SOUNDBIAS => _SOUNDBIAS,
|
||||
|
||||
Register.WAVE_RAM0_L => _WAVE_RAM0_L,
|
||||
Register.WAVE_RAM0_H => _WAVE_RAM0_H,
|
||||
|
||||
Register.WAVE_RAM1_L => _WAVE_RAM1_L,
|
||||
Register.WAVE_RAM1_H => _WAVE_RAM1_H,
|
||||
|
||||
Register.WAVE_RAM2_L => _WAVE_RAM2_L,
|
||||
Register.WAVE_RAM2_H => _WAVE_RAM2_H,
|
||||
|
||||
Register.WAVE_RAM3_L => _WAVE_RAM3_L,
|
||||
Register.WAVE_RAM3_H => _WAVE_RAM3_H,
|
||||
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.Sound: Register read error"),
|
||||
};
|
||||
}
|
||||
|
||||
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
case Register.SOUND1CNT_L:
|
||||
Memory.WriteRegisterHelper(ref _SOUND1CNT_L, value, mode);
|
||||
break;
|
||||
case Register.SOUND1CNT_H:
|
||||
Memory.WriteRegisterHelper(ref _SOUND1CNT_H, value, mode);
|
||||
break;
|
||||
case Register.SOUND1CNT_X:
|
||||
Memory.WriteRegisterHelper(ref _SOUND1CNT_X, value, mode);
|
||||
break;
|
||||
|
||||
case Register.SOUND2CNT_L:
|
||||
Memory.WriteRegisterHelper(ref _SOUND2CNT_L, value, mode);
|
||||
break;
|
||||
case Register.SOUND2CNT_H:
|
||||
Memory.WriteRegisterHelper(ref _SOUND2CNT_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.SOUND3CNT_L:
|
||||
Memory.WriteRegisterHelper(ref _SOUND3CNT_L, value, mode);
|
||||
break;
|
||||
case Register.SOUND3CNT_H:
|
||||
Memory.WriteRegisterHelper(ref _SOUND3CNT_H, value, mode);
|
||||
break;
|
||||
case Register.SOUND3CNT_X:
|
||||
Memory.WriteRegisterHelper(ref _SOUND3CNT_X, value, mode);
|
||||
break;
|
||||
|
||||
case Register.SOUND4CNT_L:
|
||||
Memory.WriteRegisterHelper(ref _SOUND4CNT_L, value, mode);
|
||||
break;
|
||||
case Register.SOUND4CNT_H:
|
||||
Memory.WriteRegisterHelper(ref _SOUND4CNT_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.SOUNDCNT_L:
|
||||
Memory.WriteRegisterHelper(ref _SOUNDCNT_L, value, mode);
|
||||
break;
|
||||
case Register.SOUNDCNT_H:
|
||||
Memory.WriteRegisterHelper(ref _SOUNDCNT_H, value, mode);
|
||||
break;
|
||||
case Register.SOUNDCNT_X:
|
||||
Memory.WriteRegisterHelper(ref _SOUNDCNT_X, value, mode);
|
||||
break;
|
||||
|
||||
case Register.SOUNDBIAS:
|
||||
Memory.WriteRegisterHelper(ref _SOUNDBIAS, value, mode);
|
||||
break;
|
||||
|
||||
case Register.WAVE_RAM0_L:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM0_L, value, mode);
|
||||
break;
|
||||
case Register.WAVE_RAM0_H:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM0_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.WAVE_RAM1_L:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM1_L, value, mode);
|
||||
break;
|
||||
case Register.WAVE_RAM1_H:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM1_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.WAVE_RAM2_L:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM2_L, value, mode);
|
||||
break;
|
||||
case Register.WAVE_RAM2_H:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM2_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.WAVE_RAM3_L:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM3_L, value, mode);
|
||||
break;
|
||||
case Register.WAVE_RAM3_H:
|
||||
Memory.WriteRegisterHelper(ref _WAVE_RAM3_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.FIFO_A_L:
|
||||
Memory.WriteRegisterHelper(ref _FIFO_A_L, value, mode);
|
||||
break;
|
||||
case Register.FIFO_A_H:
|
||||
Memory.WriteRegisterHelper(ref _FIFO_A_H, value, mode);
|
||||
break;
|
||||
|
||||
case Register.FIFO_B_L:
|
||||
Memory.WriteRegisterHelper(ref _FIFO_B_L, value, mode);
|
||||
break;
|
||||
case Register.FIFO_B_H:
|
||||
Memory.WriteRegisterHelper(ref _FIFO_B_H, value, mode);
|
||||
break;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.Sound: Register write error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e76b919919204d4db34341937e30bc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class SystemControl
|
||||
{
|
||||
internal enum Register
|
||||
{
|
||||
WAITCNT,
|
||||
SYSCNT_UND0 // undocumented - Post Boot Flag (POSTFLG) & Power Down Control (HALTCNT)
|
||||
}
|
||||
|
||||
private UInt16 _WAITCNT;
|
||||
private UInt16 _SYSCNT_UND0;
|
||||
|
||||
internal void ResetState()
|
||||
{
|
||||
_WAITCNT = 0;
|
||||
_SYSCNT_UND0 = 0;
|
||||
}
|
||||
|
||||
internal void LoadState(BinaryReader reader)
|
||||
{
|
||||
_WAITCNT = reader.ReadUInt16();
|
||||
_SYSCNT_UND0 = reader.ReadUInt16();
|
||||
}
|
||||
|
||||
internal void SaveState(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(_WAITCNT);
|
||||
writer.Write(_SYSCNT_UND0);
|
||||
}
|
||||
|
||||
internal UInt16 ReadRegister(Register register)
|
||||
{
|
||||
return register switch
|
||||
{
|
||||
Register.WAITCNT => _WAITCNT,
|
||||
Register.SYSCNT_UND0 => _SYSCNT_UND0,
|
||||
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.SystemControl: Register read error"),
|
||||
};
|
||||
}
|
||||
|
||||
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
||||
{
|
||||
switch (register)
|
||||
{
|
||||
case Register.WAITCNT:
|
||||
Memory.WriteRegisterHelper(ref _WAITCNT, value, mode);
|
||||
break;
|
||||
case Register.SYSCNT_UND0:
|
||||
Memory.WriteRegisterHelper(ref _SYSCNT_UND0, value, mode);
|
||||
break;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.SystemControl: Register write error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24d9a1588f50c654691f136b394d3d31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,383 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static UnityEditor.Experimental.AssetDatabaseExperimental.AssetDatabaseCounters;
|
||||
|
||||
namespace Iris.GBA
|
||||
{
|
||||
internal sealed class Timer
|
||||
{
|
||||
internal enum Register
|
||||
{
|
||||
TM0CNT_L,
|
||||
TM0CNT_H,
|
||||
|
||||
TM1CNT_L,
|
||||
TM1CNT_H,
|
||||
|
||||
TM2CNT_L,
|
||||
TM2CNT_H,
|
||||
|
||||
TM3CNT_L,
|
||||
TM3CNT_H
|
||||
}
|
||||
|
||||
private readonly Common.Scheduler _scheduler;
|
||||
|
||||
private InterruptControl _interruptControl;
|
||||
|
||||
//private struct Channel(GBA_System.TaskId startTaskId, GBA_System.TaskId handleOverflowTaskId, InterruptControl.Interrupt interrupt)
|
||||
//{
|
||||
// internal UInt16 _counter;
|
||||
// internal UInt16 _reload;
|
||||
// internal UInt16 _control;
|
||||
// internal UInt64 _cycleCount; // only used in non-cascading mode
|
||||
// internal bool _running;
|
||||
|
||||
// internal readonly GBA_System.TaskId _startTaskId = startTaskId;
|
||||
// internal readonly GBA_System.TaskId _handleOverflowTaskId = handleOverflowTaskId;
|
||||
// internal readonly InterruptControl.Interrupt _interrupt = interrupt;
|
||||
//}
|
||||
|
||||
private struct Channel
|
||||
{
|
||||
internal Channel(GBA_System.TaskId startTaskId, GBA_System.TaskId handleOverflowTaskId, InterruptControl.Interrupt interrupt)
|
||||
{
|
||||
_startTaskId = startTaskId;
|
||||
_handleOverflowTaskId = handleOverflowTaskId;
|
||||
_interrupt = interrupt;
|
||||
|
||||
_counter = 0;
|
||||
_reload = 0;
|
||||
_control = 0;
|
||||
_cycleCount = 0;
|
||||
_running = false;
|
||||
|
||||
}
|
||||
internal UInt16 _counter;
|
||||
internal UInt16 _reload;
|
||||
internal UInt16 _control;
|
||||
internal UInt64 _cycleCount; // only used in non-cascading mode
|
||||
internal bool _running;
|
||||
|
||||
internal readonly GBA_System.TaskId _startTaskId;
|
||||
internal readonly GBA_System.TaskId _handleOverflowTaskId;
|
||||
internal readonly InterruptControl.Interrupt _interrupt;
|
||||
}
|
||||
|
||||
private readonly Channel[] _channels;
|
||||
|
||||
internal Timer(Common.Scheduler scheduler)
|
||||
{
|
||||
_scheduler = scheduler;
|
||||
|
||||
_channels = new Channel[]
|
||||
{
|
||||
new Channel(GBA_System.TaskId.StartTimer_Channel0, GBA_System.TaskId.HandleTimerOverflow_Channel0, InterruptControl.Interrupt.Timer0),
|
||||
new Channel(GBA_System.TaskId.StartTimer_Channel1, GBA_System.TaskId.HandleTimerOverflow_Channel1, InterruptControl.Interrupt.Timer1),
|
||||
new Channel(GBA_System.TaskId.StartTimer_Channel2, GBA_System.TaskId.HandleTimerOverflow_Channel2, InterruptControl.Interrupt.Timer2),
|
||||
new Channel(GBA_System.TaskId.StartTimer_Channel3, GBA_System.TaskId.HandleTimerOverflow_Channel3, InterruptControl.Interrupt.Timer3)
|
||||
};
|
||||
|
||||
for (int channelIndex = 0; channelIndex < 4; ++channelIndex)
|
||||
{
|
||||
int channelIndexCopy = channelIndex;
|
||||
_scheduler.RegisterTask((int)_channels[channelIndex]._startTaskId, cycleCountDelay => Start(channelIndexCopy, cycleCountDelay));
|
||||
_scheduler.RegisterTask((int)_channels[channelIndex]._handleOverflowTaskId, cycleCountDelay => HandleOverflow(channelIndexCopy, cycleCountDelay));
|
||||
}
|
||||
}
|
||||
|
||||
internal void Initialize(InterruptControl interruptControl)
|
||||
{
|
||||
_interruptControl = interruptControl;
|
||||
}
|
||||
|
||||
internal void ResetState()
|
||||
{
|
||||
foreach (ref Channel channel in _channels.AsSpan())
|
||||
{
|
||||
channel._counter = 0;
|
||||
channel._reload = 0;
|
||||
channel._control = 0;
|
||||
channel._cycleCount = 0;
|
||||
channel._running = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal void LoadState(BinaryReader reader)
|
||||
{
|
||||
foreach (ref Channel channel in _channels.AsSpan())
|
||||
{
|
||||
channel._counter = reader.ReadUInt16();
|
||||
channel._reload = reader.ReadUInt16();
|
||||
channel._control = reader.ReadUInt16();
|
||||
channel._cycleCount = reader.ReadUInt64();
|
||||
channel._running = reader.ReadBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
internal void SaveState(BinaryWriter writer)
|
||||
{
|
||||
foreach (Channel channel in _channels)
|
||||
{
|
||||
writer.Write(channel._counter);
|
||||
writer.Write(channel._reload);
|
||||
writer.Write(channel._control);
|
||||
writer.Write(channel._cycleCount);
|
||||
writer.Write(channel._running);
|
||||
}
|
||||
}
|
||||
|
||||
internal UInt16 ReadRegister(Register register)
|
||||
{
|
||||
UInt16 ReadCounter(int channelIndex)
|
||||
{
|
||||
ref Channel channel = ref _channels[channelIndex];
|
||||
|
||||
if (channel._running && (((channel._control & 0x0004) == 0) || (channelIndex == 0)))
|
||||
UpdateCounter(ref channel, channel._control);
|
||||
|
||||
return channel._counter;
|
||||
}
|
||||
|
||||
return register switch
|
||||
{
|
||||
Register.TM0CNT_L => ReadCounter(0),
|
||||
Register.TM0CNT_H => _channels[0]._control,
|
||||
|
||||
Register.TM1CNT_L => ReadCounter(1),
|
||||
Register.TM1CNT_H => _channels[1]._control,
|
||||
|
||||
Register.TM2CNT_L => ReadCounter(2),
|
||||
Register.TM2CNT_H => _channels[2]._control,
|
||||
|
||||
Register.TM3CNT_L => ReadCounter(3),
|
||||
Register.TM3CNT_H => _channels[3]._control,
|
||||
|
||||
// should never happen
|
||||
_ => throw new Exception("Iris.GBA.Timer: Register read error"),
|
||||
};
|
||||
}
|
||||
|
||||
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
||||
{
|
||||
void WriteReload(ref Channel channel)
|
||||
{
|
||||
UInt16 reload = channel._reload;
|
||||
Memory.WriteRegisterHelper(ref reload, value, mode);
|
||||
channel._reload = reload;
|
||||
}
|
||||
|
||||
void WriteControl(int channelIndex)
|
||||
{
|
||||
ref Channel channel = ref _channels[channelIndex];
|
||||
|
||||
UInt16 previousControl = channel._control;
|
||||
|
||||
UInt16 newControl = channel._control;
|
||||
Memory.WriteRegisterHelper(ref newControl, value, mode);
|
||||
channel._control = newControl;
|
||||
|
||||
CheckControl(ref channel, channelIndex, previousControl, newControl);
|
||||
}
|
||||
|
||||
switch (register)
|
||||
{
|
||||
case Register.TM0CNT_L:
|
||||
WriteReload(ref _channels[0]);
|
||||
break;
|
||||
case Register.TM0CNT_H:
|
||||
WriteControl(0);
|
||||
break;
|
||||
|
||||
case Register.TM1CNT_L:
|
||||
WriteReload(ref _channels[1]);
|
||||
break;
|
||||
case Register.TM1CNT_H:
|
||||
WriteControl(1);
|
||||
break;
|
||||
|
||||
case Register.TM2CNT_L:
|
||||
WriteReload(ref _channels[2]);
|
||||
break;
|
||||
case Register.TM2CNT_H:
|
||||
WriteControl(2);
|
||||
break;
|
||||
|
||||
case Register.TM3CNT_L:
|
||||
WriteReload(ref _channels[3]);
|
||||
break;
|
||||
case Register.TM3CNT_H:
|
||||
WriteControl(3);
|
||||
break;
|
||||
|
||||
// should never happen
|
||||
default:
|
||||
throw new Exception("Iris.GBA.Timer: Register write error");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CheckControl(ref Channel channel, int channelIndex, UInt16 previousControl, UInt16 newControl)
|
||||
{
|
||||
if ((previousControl & 0x0080) == 0)
|
||||
{
|
||||
if ((newControl & 0x0080) == 0x0080)
|
||||
_scheduler.ScheduleTask((int)channel._startTaskId, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((newControl & 0x0080) == 0)
|
||||
{
|
||||
if (channel._running)
|
||||
{
|
||||
if (((previousControl & 0x0004) == 0) || (channelIndex == 0))
|
||||
{
|
||||
UpdateCounter(ref channel, previousControl);
|
||||
|
||||
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
|
||||
}
|
||||
|
||||
channel._running = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_scheduler.CancelTask((int)channel._startTaskId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (channel._running)
|
||||
{
|
||||
if (channelIndex == 0)
|
||||
{
|
||||
if ((previousControl & 0b11) != (newControl & 0b11))
|
||||
{
|
||||
UpdateCounter(ref channel, previousControl);
|
||||
|
||||
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
|
||||
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((previousControl & 0x0004) == 0)
|
||||
{
|
||||
if ((newControl & 0x0004) == 0)
|
||||
{
|
||||
if ((previousControl & 0b11) != (newControl & 0b11))
|
||||
{
|
||||
UpdateCounter(ref channel, previousControl);
|
||||
|
||||
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
|
||||
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateCounter(ref channel, previousControl);
|
||||
|
||||
_scheduler.CancelTask((int)channel._handleOverflowTaskId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((newControl & 0x0004) == 0)
|
||||
{
|
||||
channel._cycleCount = _scheduler.GetCycleCounter();
|
||||
|
||||
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCounter(ref Channel channel, UInt16 control)
|
||||
{
|
||||
UInt64 currentCycleCount = _scheduler.GetCycleCounter();
|
||||
UInt64 cycleCountDelta = currentCycleCount - channel._cycleCount;
|
||||
UInt64 prescaler = GetPrescaler(control);
|
||||
|
||||
channel._counter += (UInt16)(cycleCountDelta / prescaler);
|
||||
channel._cycleCount = currentCycleCount - (UInt16)(cycleCountDelta % prescaler);
|
||||
}
|
||||
|
||||
private void Start(int channelIndex, UInt64 cycleCountDelay)
|
||||
{
|
||||
ref Channel channel = ref _channels[channelIndex];
|
||||
|
||||
channel._counter = channel._reload;
|
||||
channel._running = true;
|
||||
|
||||
if (((channel._control & 0x0004) == 0) || (channelIndex == 0))
|
||||
{
|
||||
channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay;
|
||||
|
||||
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOverflow(int channelIndex, UInt64 cycleCountDelay)
|
||||
{
|
||||
ref Channel channel = ref _channels[channelIndex];
|
||||
|
||||
channel._counter = channel._reload;
|
||||
channel._cycleCount = _scheduler.GetCycleCounter() - cycleCountDelay;
|
||||
|
||||
_scheduler.ScheduleTask((int)channel._handleOverflowTaskId, ComputeCycleCountUntilOverflow(ref channel) - cycleCountDelay);
|
||||
|
||||
if ((channel._control & 0x0040) == 0x0040)
|
||||
_interruptControl.RequestInterrupt(channel._interrupt);
|
||||
|
||||
CascadeOverflow(channelIndex);
|
||||
}
|
||||
|
||||
private void CascadeOverflow(int channelIndex)
|
||||
{
|
||||
if (channelIndex == 3)
|
||||
return;
|
||||
|
||||
++channelIndex;
|
||||
|
||||
ref Channel channel = ref _channels[channelIndex];
|
||||
|
||||
if (!channel._running || ((channel._control & 0x0004) != 0x0004))
|
||||
return;
|
||||
|
||||
if (channel._counter == 0xffff)
|
||||
{
|
||||
channel._counter = channel._reload;
|
||||
|
||||
if ((channel._control & 0x0040) == 0x0040)
|
||||
_interruptControl.RequestInterrupt(channel._interrupt);
|
||||
|
||||
CascadeOverflow(channelIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
++channel._counter;
|
||||
}
|
||||
}
|
||||
|
||||
//private static UInt64 ComputeCycleCountUntilOverflow(ref readonly Channel channel)
|
||||
private static UInt64 ComputeCycleCountUntilOverflow(ref Channel channel)
|
||||
{
|
||||
return (0x1_0000u - channel._counter) * GetPrescaler(channel._control);
|
||||
}
|
||||
|
||||
private static UInt64 GetPrescaler(UInt16 control)
|
||||
{
|
||||
return (control & 0b11) switch
|
||||
{
|
||||
0b00 => 1,
|
||||
0b01 => 64,
|
||||
0b10 => 256,
|
||||
0b11 => 1024,
|
||||
_ => 0, // cannot happen
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1c87077f94b1c547b5ef23240dca9cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c10b6bffb02e139458b969dbf72fc69b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89f540181c8a126469d24bee76e92113
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Iris.NDS
|
||||
{
|
||||
public sealed partial class NDS_System
|
||||
{
|
||||
private void BIOS_Reset()
|
||||
{
|
||||
const UInt32 ROMAddress = 0x0800_0000;
|
||||
|
||||
// TODO
|
||||
|
||||
_cpu.Reg[CPU.CPU_Core.PC] = ROMAddress;
|
||||
_cpu.NextInstructionAddress = ROMAddress;
|
||||
}
|
||||
|
||||
private UInt64 HandleSWI()
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.BIOS: HandleSWI unimplemented");
|
||||
}
|
||||
|
||||
private UInt64 HandleIRQ()
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.BIOS: HandleIRQ unimplemented");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65bfe93914138fb498b88080b1e38337
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,47 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Iris.NDS
|
||||
{
|
||||
public sealed partial class NDS_System
|
||||
{
|
||||
private const int KB = 1024;
|
||||
|
||||
private Byte[]? _ROM;
|
||||
|
||||
public override void LoadROM(string filename)
|
||||
{
|
||||
_ROM = File.ReadAllBytes(filename);
|
||||
}
|
||||
|
||||
private Byte ReadMemory8(UInt32 address)
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory8 unimplemented");
|
||||
}
|
||||
|
||||
private UInt16 ReadMemory16(UInt32 address)
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory16 unimplemented");
|
||||
}
|
||||
|
||||
private UInt32 ReadMemory32(UInt32 address)
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
|
||||
}
|
||||
|
||||
private void WriteMemory8(UInt32 address, Byte value)
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
|
||||
}
|
||||
|
||||
private void WriteMemory16(UInt32 address, UInt16 value)
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
|
||||
}
|
||||
|
||||
private void WriteMemory32(UInt32 address, UInt32 value)
|
||||
{
|
||||
throw new NotImplementedException("Iris.NDS.Core.Memory: ReadMemory32 unimplemented");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 901a653d16f65ca4b906d7d0fb660a03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,76 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Iris.NDS
|
||||
{
|
||||
public sealed partial class NDS_System : Common.System
|
||||
{
|
||||
private readonly CPU.CPU_Core _cpu;
|
||||
private readonly PPU _ppu;
|
||||
|
||||
private bool _running;
|
||||
private bool _disposed;
|
||||
|
||||
public NDS_System(PollInput_Delegate pollInputCallback, PresentFrame_Delegate presentFrameCallback)
|
||||
{
|
||||
CPU.CPU_Core.CallbackInterface cpuCallbackInterface = new(ReadMemory8, ReadMemory16, ReadMemory32, WriteMemory8, WriteMemory16, WriteMemory32, HandleSWI, HandleIRQ);
|
||||
_cpu = new(CPU.CPU_Core.Model.ARM946ES, cpuCallbackInterface);
|
||||
_ppu = new(presentFrameCallback);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
// TODO
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public override void ResetState(bool skipIntro)
|
||||
{
|
||||
BIOS_Reset();
|
||||
|
||||
_cpu.NIRQ = CPU.CPU_Core.Signal.High;
|
||||
}
|
||||
|
||||
public override void LoadState(BinaryReader reader)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public override void SaveState(BinaryWriter writer)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
public override bool IsRunning()
|
||||
{
|
||||
return _running;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
_running = true;
|
||||
|
||||
while (_running)
|
||||
{
|
||||
UInt64 cycles = _cpu.Step();
|
||||
|
||||
for (UInt64 i = 0; i < cycles; ++i)
|
||||
_ppu.Step();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Pause()
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
|
||||
public override void SetKeyStatus(Key key, KeyStatus status)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2233aca5b62bca346b553db0b0fcc482
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,19 +0,0 @@
|
||||
namespace Iris.NDS
|
||||
{
|
||||
public sealed class PPU
|
||||
{
|
||||
private const int KB = 1024;
|
||||
|
||||
private readonly Common.System.PresentFrame_Delegate _presentFrameCallback;
|
||||
|
||||
internal PPU(Common.System.PresentFrame_Delegate presentFrameCallback)
|
||||
{
|
||||
_presentFrameCallback = presentFrameCallback;
|
||||
}
|
||||
|
||||
internal void Step()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd011144397045948876bf23745e4a4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.ConstrainedExecution;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public static class MyUnSafeCommon
|
||||
{
|
||||
@ -88,4 +91,254 @@ public static class MyBitOperations
|
||||
// 组合结果
|
||||
return rightShifted | leftShifted;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static class MyStruct
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vector256<T> where T : struct, IComparable, IComparable<T>, IEquatable<T>, IFormattable
|
||||
{
|
||||
public T[] data;
|
||||
|
||||
// 假设Vector256总是包含8个元素(对于uint来说,总共32字节)
|
||||
public const int ElementCount = 8;
|
||||
|
||||
// 私有构造函数,用于内部创建实例
|
||||
public Vector256(T[] data)
|
||||
{
|
||||
if (data == null || data.Length != ElementCount)
|
||||
throw new ArgumentException("Data array must contain exactly 8 elements.");
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// 静态Create方法,用于从T类型的数组创建Vector256实例
|
||||
public static Vector256<T> Create(params T[] values)
|
||||
{
|
||||
if (values.Length > ElementCount)
|
||||
throw new ArgumentException("Too many values provided.");
|
||||
|
||||
T[] fullData = new T[ElementCount];
|
||||
Array.Copy(values, 0, fullData, 0, values.Length);
|
||||
|
||||
// 对于uint,默认值是0
|
||||
if (typeof(T) == typeof(uint))
|
||||
{
|
||||
for (int i = values.Length; i < ElementCount; i++)
|
||||
{
|
||||
fullData[i] = (T)(object)0u; // 使用显式类型转换来避免编译时错误
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector256<T>(fullData);
|
||||
}
|
||||
|
||||
// 静态Zero属性,返回一个所有元素都为0的Vector256<T>实例
|
||||
public static Vector256<T> Zero
|
||||
{
|
||||
get
|
||||
{
|
||||
if (typeof(T) == typeof(uint))
|
||||
{
|
||||
uint[] zeroData = new uint[ElementCount];
|
||||
//Array.Fill(zeroData, 0u); // 使用Array.Fill来填充所有元素为0
|
||||
return new Vector256<T>((T[])(object)zeroData); // 显式类型转换以绕过泛型约束
|
||||
}
|
||||
|
||||
// 如果T不是uint,这里可能需要抛出一个异常或者返回一个默认构造的Vector256<T>
|
||||
// 但由于泛型约束,实际上T必须是uint(或者同时满足其他接口的类型,但在这个上下文中我们只关心uint)
|
||||
// 因此,这里实际上不会执行到
|
||||
throw new InvalidOperationException("Zero property is only valid for Vector256<uint>.");
|
||||
}
|
||||
}
|
||||
|
||||
// ... 可以根据需要添加更多重载
|
||||
|
||||
// 为了方便调试,可以重写ToString方法
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join(", ", Array.ConvertAll(data, x => x.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 模拟 Avx2.ConvertToVector256Int32
|
||||
public static Vector256<uint> ConvertToVector256Int32(byte[] source)
|
||||
{
|
||||
if (source.Length < 32) // 32 bytes for 8 uints
|
||||
throw new ArgumentException("Source array must be at least 32 bytes long.");
|
||||
|
||||
uint[] uints = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
uints[i] = BitConverter.ToUInt32(source, i * 4);
|
||||
}
|
||||
|
||||
return new Vector256<uint>(uints);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.ShiftLeftLogical
|
||||
public static Vector256<int> ShiftLeftLogical(Vector256<int> metaVec, int shift)
|
||||
{
|
||||
int[] shifted = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
shifted[i] = (int)(metaVec.data[i] << shift);
|
||||
}
|
||||
return new Vector256<int>(shifted);
|
||||
}
|
||||
// 模拟 Avx2.And
|
||||
public static Vector256<int> And(Vector256<int> data1, Vector256<int> data2)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] & data2.data[i];
|
||||
}
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
// 模拟 Avx2.And
|
||||
public static Vector256<uint> And(Vector256<uint> data1, Vector256<uint> data2)
|
||||
{
|
||||
uint[] result = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] & data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<uint>(result);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.Or
|
||||
public static Vector256<int> Or(Vector256<int> data1, Vector256<int> data2)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] | data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
// 模拟 Avx2.Or
|
||||
public static Vector256<uint> Or(Vector256<uint> data1, Vector256<uint> data2)
|
||||
{
|
||||
uint[] result = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] | data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<uint>(result);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.Xor
|
||||
|
||||
public static Vector256<int> Xor(Vector256<int> data1, Vector256<int> data2)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] ^ data2.data[i];
|
||||
}
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
|
||||
public static Vector256<uint> Xor(Vector256<uint> data1, Vector256<uint> data2)
|
||||
{
|
||||
uint[] result = new uint[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = data1.data[i] ^ data2.data[i];
|
||||
}
|
||||
|
||||
return new Vector256<uint>(result);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.Permute2x128 (只模拟上下两半交换)
|
||||
public static Vector256<uint> Permute2x128(Vector256<uint> data)
|
||||
{
|
||||
uint[] swapped = new uint[8];
|
||||
Array.Copy(data.data, 4, swapped, 0, 4);
|
||||
Array.Copy(data.data, 0, swapped, 4, 4);
|
||||
return new Vector256<uint>(swapped);
|
||||
}
|
||||
// 模拟 Avx2.Permute2x128 (只模拟上下两半交换)
|
||||
public static Vector256<int> Permute2x128(Vector256<int> data)
|
||||
{
|
||||
int[] swapped = new int[8];
|
||||
Array.Copy(data.data, 4, swapped, 0, 4);
|
||||
Array.Copy(data.data, 0, swapped, 4, 4);
|
||||
return new Vector256<int>(swapped);
|
||||
}
|
||||
|
||||
// 模拟 Avx2.CompareEqual
|
||||
public static Vector256<int> CompareEqual(Vector256<int> left, Vector256<int> right)
|
||||
{
|
||||
int[] result = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
result[i] = (left.data[i] == right.data[i]) ? -1 : 0; // 通常使用-1或全1表示真,0表示假
|
||||
}
|
||||
return new Vector256<int>(result);
|
||||
}
|
||||
// 模拟 MaskedStore 操作
|
||||
public static void MaskedStore(Vector256<int> destination, Vector256<int> mask, Vector256<int> source)
|
||||
{
|
||||
//if (destination.Length < 8 || mask.Length != 8 || source.data.Length != 8)
|
||||
// throw new ArgumentException("Arrays must be of length 8 and match in size.");
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
// 如果掩码中的值为非零(表示真),则写入源向量的值
|
||||
if (mask.data[i] != 0)
|
||||
{
|
||||
destination.data[i] = source.data[i];
|
||||
}
|
||||
// 如果掩码中的值为零(表示假),则不修改destination[i]
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:这不是AVX2的gather指令的直接模拟,而是一个简化的示例
|
||||
public static Vector256<int> GatherVector256(T[] source, int[] indices)
|
||||
{
|
||||
if (source.Length < 8 || indices.Length < 8)
|
||||
throw new ArgumentException("Source array and indices array must be large enough.");
|
||||
|
||||
int[] gatheredData = new int[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int index = indices[i];
|
||||
if (index < 0 || index >= source.Length)
|
||||
gatheredData[i] = source[index];
|
||||
}
|
||||
|
||||
return new Vector256<int>(gatheredData);
|
||||
}
|
||||
|
||||
|
||||
// 模拟 ShiftRightLogicalVariable,接受两个 Vector256<uint> 参数
|
||||
// 第一个参数是要右移的向量,第二个参数是每个元素要右移的位数
|
||||
public static Vector256<uint> ShiftRightLogicalVariable(this Vector256<uint> vector, Vector256<uint> shiftCounts)
|
||||
{
|
||||
if (vector.data.Length != shiftCounts.data.Length)
|
||||
{
|
||||
throw new ArgumentException("Both vectors must have the same number of elements.");
|
||||
}
|
||||
|
||||
uint[] shiftedData = new uint[Vector256<uint>.ElementCount];
|
||||
for (int i = 0; i < vector.data.Length; i++)
|
||||
{
|
||||
// 注意:这里假设 shiftCounts.data[i] 中的值是有效的右移位数(非负且小于 32)
|
||||
// 如果需要,可以添加额外的检查来处理无效值
|
||||
int shiftCount = (int)shiftCounts.data[i] & 0x1F; // 限制右移位数在 0 到 31 之间
|
||||
shiftedData[i] = vector.data[i] >> shiftCount;
|
||||
}
|
||||
|
||||
return new Vector256<uint>(shiftedData);
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a9692e254d34f34aa83151367615810
|
||||
guid: 06ea5793f83f5ab4e83233d686814f83
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cb7f59fb73e5f74cb7d6d787eadcbd1
|
||||
guid: a3e5397cebfd55d4d950fc3d7fcafd8f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b70ee69b176c61f4eb6db50741d13b0b
|
||||
guid: 7079d9113f8bfda4db8a78fffdd414a9
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d58c54bd61da7884f97da0f6bea3e4f5
|
||||
guid: 677f64bf577a1724699f45841c5bcb04
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b26dd54a0d93ba0469d1f9307e0e4b43
|
||||
guid: c92fb77cb5b063541bd04cb8a496013f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ea315d0fd7389c41b19996891e99ae3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -1,267 +0,0 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 9
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 705507994}
|
||||
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 12
|
||||
m_GIWorkflowMode: 1
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_FinalGather: 0
|
||||
m_FinalGatherFiltering: 1
|
||||
m_FinalGatherRayCount: 256
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 500
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 500
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 2
|
||||
m_PVRDenoiserTypeDirect: 0
|
||||
m_PVRDenoiserTypeIndirect: 0
|
||||
m_PVRDenoiserTypeAO: 0
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 0
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
m_PVRFilteringGaussRadiusAO: 2
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 2
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
accuratePlacement: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &705507993
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 705507995}
|
||||
- component: {fileID: 705507994}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &705507994
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 705507993}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 8
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_Lightmapping: 1
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
--- !u!4 &705507995
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 705507993}
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 1
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1 &963194225
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 963194228}
|
||||
- component: {fileID: 963194227}
|
||||
- component: {fileID: 963194226}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &963194226
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &963194227
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_GateFitMode: 2
|
||||
m_FocalLength: 50
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &963194228
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_GameObject: {fileID: 963194225}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fc0d4010bbf28b4594072e72b8655ab
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
51
Assets/Util.cs
Normal file
51
Assets/Util.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
static class Util
|
||||
{
|
||||
public static void Each<T>(this System.Collections.Generic.IEnumerable<T> ie, Action<T, int> action)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var e in ie) action(e, i++);
|
||||
}
|
||||
|
||||
public static void WriteDebug(string text)
|
||||
{
|
||||
// Console.WriteLine(text);
|
||||
}
|
||||
|
||||
public static string Pad(string n, int width, char padChar)
|
||||
{
|
||||
return n.Length >= width ? n : string.Join(padChar, new int[width - (n.Length + 1)]) + n;
|
||||
}
|
||||
|
||||
public static string RightPad(string n, int width, char z)
|
||||
{
|
||||
return n.Length >= width ? n : n + string.Join(z, new int[width - (n.Length + 1)]);
|
||||
}
|
||||
|
||||
public static string Hex(long i, int digits)
|
||||
{
|
||||
return $"0x{i.ToString("X").ToUpper().PadLeft(digits, '0')}";
|
||||
}
|
||||
|
||||
public static string Hex(ulong i, int digits)
|
||||
{
|
||||
return $"0x{i.ToString("X").ToUpper().PadLeft(digits, '0')}";
|
||||
}
|
||||
|
||||
public static string HexN(long i, int digits)
|
||||
{
|
||||
return i.ToString("X").ToUpper().PadLeft(digits, '0');
|
||||
}
|
||||
|
||||
public static string HexN_LC(long i, int digits)
|
||||
{
|
||||
return i.ToString("X").ToLower().PadLeft(digits, '0');
|
||||
}
|
||||
|
||||
public static string Binary(long i, int digits)
|
||||
{
|
||||
return Convert.ToString(i, 2).ToLower().PadLeft(digits, '0');
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 300995068a952f743aaddf6f4736c84c
|
||||
guid: 1a5554b2d514cfb40a6352bbea330fe6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c85535aab73500e4e93bc6a3430ff694
|
||||
guid: 3efde08a75dea6141840226c711cab4b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
1
Assets/emulator/ApplySkipLocalsInit.cs
Normal file
1
Assets/emulator/ApplySkipLocalsInit.cs
Normal file
@ -0,0 +1 @@
|
||||
//[module: System.Runtime.CompilerServices.SkipLocalsInit]
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d321636c09403e249a26ee8f85908765
|
||||
guid: 758bfc6cd0551814abf32fc61d504c02
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
169
Assets/emulator/BlipBuf.cs
Normal file
169
Assets/emulator/BlipBuf.cs
Normal file
@ -0,0 +1,169 @@
|
||||
// Inspired by Blargg's blip-buf
|
||||
|
||||
using System;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public class BlipBuf
|
||||
{
|
||||
const int KERNEL_RESOLUTION = 1024;
|
||||
|
||||
float[] Kernel;
|
||||
int KernelSize = 0;
|
||||
|
||||
float[] ChannelValsL;
|
||||
float[] ChannelValsR;
|
||||
double[] ChannelSample;
|
||||
double[] ChannelRealSample;
|
||||
|
||||
float[] BufferL;
|
||||
float[] BufferR;
|
||||
|
||||
int BufferPos = 0;
|
||||
int BufferSize = 0;
|
||||
|
||||
public float CurrentValL = 0;
|
||||
public float CurrentValR = 0;
|
||||
|
||||
double CurrentSampleInPos = 0;
|
||||
double CurrentSampleOutPos = 0;
|
||||
|
||||
public BlipBuf(int kernelSize, bool normalize, int channels)
|
||||
{
|
||||
ChannelValsL = new float[channels];
|
||||
ChannelValsR = new float[channels];
|
||||
ChannelSample = new double[channels];
|
||||
ChannelRealSample = new double[channels];
|
||||
|
||||
BufferSize = 32768;
|
||||
BufferL = new float[BufferSize];
|
||||
BufferR = new float[BufferSize];
|
||||
|
||||
SetKernelSize(kernelSize, normalize, true);
|
||||
}
|
||||
|
||||
public void SetKernelSize(int kernelSize, bool normalize, bool enabled)
|
||||
{
|
||||
Kernel = new float[kernelSize * KERNEL_RESOLUTION];
|
||||
KernelSize = kernelSize;
|
||||
|
||||
if ((kernelSize & (kernelSize - 1)) != 0)
|
||||
{
|
||||
throw new ArgumentException("Kernel size not power of 2:" + kernelSize);
|
||||
}
|
||||
|
||||
for (int i = 0; i < KERNEL_RESOLUTION; i++)
|
||||
{
|
||||
float sum = 0;
|
||||
for (int j = 0; j < kernelSize; j++)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
float x = j - kernelSize / 2F;
|
||||
x += (KERNEL_RESOLUTION - i - 1) / (float)KERNEL_RESOLUTION;
|
||||
x *= (float)Math.PI;
|
||||
|
||||
float sinc = (float)Math.Sin(x) / x;
|
||||
float lanzcosWindow = (float)Math.Sin((float)x / kernelSize) / ((float)x / kernelSize);
|
||||
|
||||
if (x == 0)
|
||||
{
|
||||
Kernel[i * kernelSize + j] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Kernel[i * kernelSize + j] = sinc * lanzcosWindow;
|
||||
}
|
||||
|
||||
sum += Kernel[i * kernelSize + j];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j == kernelSize / 2)
|
||||
{
|
||||
Kernel[i * kernelSize + j] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Kernel[i * kernelSize + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (normalize && enabled)
|
||||
{
|
||||
for (int j = 0; j < kernelSize; j++)
|
||||
{
|
||||
Kernel[i * kernelSize + j] /= sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
BufferPos = 0;
|
||||
CurrentValL = 0;
|
||||
CurrentValR = 0;
|
||||
for (int i = 0; i < BufferSize; i++)
|
||||
{
|
||||
BufferL[i] = 0;
|
||||
BufferR[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValue(int channel, double sample, float valL, float valR)
|
||||
{
|
||||
// Tracking to allow submitting value for different channels out of order
|
||||
double realSample = sample;
|
||||
double dist = sample - ChannelRealSample[channel];
|
||||
sample = ChannelSample[channel] + dist;
|
||||
|
||||
if (sample >= CurrentSampleInPos)
|
||||
{
|
||||
CurrentSampleInPos = sample;
|
||||
}
|
||||
|
||||
if (sample < CurrentSampleOutPos)
|
||||
{
|
||||
Console.Error.WriteLine("Tried to set amplitude backward in time!");
|
||||
Console.WriteLine(System.Environment.StackTrace);
|
||||
}
|
||||
|
||||
ChannelSample[channel] = sample;
|
||||
ChannelRealSample[channel] = realSample;
|
||||
|
||||
if (valL != ChannelValsL[channel] || valR != ChannelValsR[channel])
|
||||
{
|
||||
float diffL = valL - ChannelValsL[channel];
|
||||
float diffR = valR - ChannelValsR[channel];
|
||||
|
||||
int subsamplePos = (int)Math.Floor((sample % 1) * KERNEL_RESOLUTION);
|
||||
|
||||
|
||||
// Add our bandlimited impulse to the difference buffer
|
||||
int kBufPos = (BufferPos + (int)(Math.Floor(sample) - CurrentSampleOutPos)) % BufferSize;
|
||||
for (int i = 0; i < KernelSize; i++)
|
||||
{
|
||||
float kernelVal = Kernel[KernelSize * subsamplePos + i];
|
||||
BufferL[kBufPos] += kernelVal * diffL;
|
||||
BufferR[kBufPos] += kernelVal * diffR;
|
||||
kBufPos = (kBufPos + 1) % BufferSize;
|
||||
}
|
||||
}
|
||||
|
||||
ChannelValsL[channel] = valL;
|
||||
ChannelValsR[channel] = valR;
|
||||
}
|
||||
|
||||
public void ReadOutSample()
|
||||
{
|
||||
CurrentValL += BufferL[BufferPos];
|
||||
CurrentValR += BufferR[BufferPos];
|
||||
BufferL[BufferPos] = 0;
|
||||
BufferR[BufferPos] = 0;
|
||||
BufferPos = (BufferPos + 1) % BufferSize;
|
||||
CurrentSampleOutPos++;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ad832993e3b0bc4695925ea366d4d9e
|
||||
guid: d2f0e6f7505a3d04bb84e704d1ed8d33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
770
Assets/emulator/CartridgeNds.cs
Normal file
770
Assets/emulator/CartridgeNds.cs
Normal file
@ -0,0 +1,770 @@
|
||||
using static Util;
|
||||
using static OptimeGBA.Bits;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using System;
|
||||
using System.Text;
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum SpiEepromState
|
||||
{
|
||||
Ready,
|
||||
ReadStatus,
|
||||
WriteStatus,
|
||||
SetReadAddress,
|
||||
SetWriteAddress,
|
||||
ReadData,
|
||||
WriteData,
|
||||
Done,
|
||||
}
|
||||
|
||||
public enum ExternalMemoryType
|
||||
{
|
||||
None,
|
||||
Eeprom,
|
||||
Flash,
|
||||
FlashWithInfrared
|
||||
}
|
||||
|
||||
public enum CartridgeState
|
||||
{
|
||||
Dummy,
|
||||
ReadCartridgeHeader,
|
||||
ReadRomChipId1,
|
||||
Dummy2,
|
||||
ReadRomChipId2,
|
||||
Key2DataRead,
|
||||
SecureAreaRead,
|
||||
ReadRomChipId3,
|
||||
}
|
||||
|
||||
public class CartridgeNds
|
||||
{
|
||||
Nds Nds;
|
||||
byte[] Rom;
|
||||
byte[] SecureArea = new byte[0x4000];
|
||||
|
||||
public uint[] EncLutKeycodeLevel1 = new uint[0x412];
|
||||
public uint[] EncLutKeycodeLevel2 = new uint[0x412];
|
||||
public uint[] EncLutKeycodeLevel3 = new uint[0x412];
|
||||
|
||||
public uint IdCode;
|
||||
public string IdString;
|
||||
|
||||
public CartridgeNds(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
Rom = Nds.Provider.Rom;
|
||||
|
||||
for (uint i = 0; i < 0x412; i++)
|
||||
{
|
||||
uint val = GetUint(Nds.Provider.Bios7, 0x30 + i * 4);
|
||||
EncLutKeycodeLevel1[i] = val;
|
||||
EncLutKeycodeLevel2[i] = val;
|
||||
EncLutKeycodeLevel3[i] = val;
|
||||
}
|
||||
|
||||
if (Rom.Length >= 0x10)
|
||||
{
|
||||
IdCode = GetUint(Rom, 0x0C);
|
||||
|
||||
Span<byte> gameIdSpan = stackalloc byte[4];
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
gameIdSpan[i] = GetByte(Rom, 0x0C + (uint)i);
|
||||
}
|
||||
|
||||
IdString = Encoding.ASCII.GetString(gameIdSpan);
|
||||
Console.WriteLine("Game ID: " + IdString);
|
||||
}
|
||||
|
||||
InitKeycode(EncLutKeycodeLevel1, 1);
|
||||
InitKeycode(EncLutKeycodeLevel2, 2);
|
||||
InitKeycode(EncLutKeycodeLevel3, 3);
|
||||
|
||||
if (!Nds.Provider.DirectBoot && Rom.Length >= 0x8000 && GetUint(Rom, 0x4000) == 0xE7FFDEFF)
|
||||
{
|
||||
for (uint i = 0; i < 0x4000; i++)
|
||||
{
|
||||
SecureArea[i] = Rom[0x4000 + i];
|
||||
}
|
||||
Console.WriteLine("Encrypting first 2KB of secure area");
|
||||
SetUlong(SecureArea, 0x0000, 0x6A624F7972636E65); // Write in "encryObj"
|
||||
|
||||
// Encrypt first 2K of the secure area with KEY1
|
||||
for (uint i = 0x0000; i < 0x0800; i += 8)
|
||||
{
|
||||
// Console.WriteLine("Encrypted ulong at " + Hex(i, 16));
|
||||
ulong raw = GetUlong(SecureArea, i);
|
||||
ulong encrypted = Encrypt64(EncLutKeycodeLevel3, raw);
|
||||
SetUlong(SecureArea, i, encrypted);
|
||||
// Console.WriteLine("Before:" + Hex(raw, 16));
|
||||
// Console.WriteLine("After :" + Hex(encrypted, 16));
|
||||
}
|
||||
|
||||
Console.WriteLine(Hex(GetUint(SecureArea, 0x0010), 8));
|
||||
|
||||
// Double-encrypt KEY1
|
||||
SetUlong(SecureArea, 0x0000, Encrypt64(EncLutKeycodeLevel2, GetUlong(SecureArea, 0x0000)));
|
||||
}
|
||||
|
||||
for (uint i = 0; i < ExternalMemory.Length; i++)
|
||||
{
|
||||
ExternalMemory[i] = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
ulong PendingCommand;
|
||||
|
||||
// some GBATek example
|
||||
// TODO: Replace this with something more realistic, maybe from a game DB
|
||||
public uint RomChipId = 0x00001FC2;
|
||||
|
||||
// State
|
||||
public CartridgeState State;
|
||||
public uint DataPos;
|
||||
public uint BytesTransferred;
|
||||
public bool Key1Encryption;
|
||||
public bool Key2Encryption;
|
||||
|
||||
public uint TransferLength;
|
||||
public uint PendingDummyWrites;
|
||||
|
||||
public bool ReadyBit23;
|
||||
public byte BlockSize;
|
||||
public bool SlowTransferClock;
|
||||
public bool BusyBit31;
|
||||
|
||||
// AUXSPICNT
|
||||
public byte SpiBaudRate;
|
||||
public bool SpiChipSelHold = false;
|
||||
public bool SpiBusy = false;
|
||||
public bool Slot1SpiMode = false;
|
||||
public bool TransferReadyIrq = false;
|
||||
public bool Slot1Enable = false;
|
||||
|
||||
// Shared External Memory State
|
||||
public SpiEepromState SpiEepromState;
|
||||
public byte SpiOutData;
|
||||
public uint SpiAddress;
|
||||
public uint SpiBytesWritten;
|
||||
public bool ExternalMemoryWriteEnable;
|
||||
// only one game called art academy uses more than 1MB
|
||||
public byte[] ExternalMemory = new byte[1048576];
|
||||
|
||||
// EEPROM state
|
||||
public byte EepromWriteProtect;
|
||||
|
||||
// Flash state
|
||||
// From Nocash's original DS
|
||||
// TODO: use more realistic flash ID
|
||||
byte[] FlashId = new byte[] { 0x20, 0x40, 0x12 };
|
||||
public SpiFlashState SpiFlashState;
|
||||
public byte FlashIdIndex;
|
||||
|
||||
// ROMCTRL
|
||||
byte ROMCTRLB0;
|
||||
byte ROMCTRLB1;
|
||||
bool ReleaseReset;
|
||||
|
||||
// cart input
|
||||
uint InData;
|
||||
|
||||
public byte ReadHwio8(bool fromArm7, uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
if (fromArm7 == Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001A0: // AUXSPICNT B0
|
||||
val |= SpiBaudRate;
|
||||
if (SpiChipSelHold) val = BitSet(val, 6);
|
||||
if (SpiBusy) val = BitSet(val, 7);
|
||||
// Console.WriteLine("AUXSPICNT B0 read");
|
||||
break;
|
||||
case 0x40001A1: // AUXSPICNT B1
|
||||
if (Slot1SpiMode) val = BitSet(val, 5);
|
||||
if (TransferReadyIrq) val = BitSet(val, 6);
|
||||
if (Slot1Enable) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x40001A2: // AUXSPIDATA
|
||||
return SpiOutData;
|
||||
|
||||
case 0x40001A4: // ROMCTRL B0
|
||||
return ROMCTRLB0;
|
||||
case 0x40001A5: // ROMCTRL B1
|
||||
return ROMCTRLB1;
|
||||
case 0x40001A6: // ROMCTRL B2
|
||||
if (ReleaseReset) val = BitSet(val, 5);
|
||||
if (ReadyBit23) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x40001A7: // ROMCTRL B3
|
||||
val |= BlockSize;
|
||||
if (SlowTransferClock) val = BitSet(val, 3);
|
||||
if (BusyBit31) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4100010: // From cartridge
|
||||
if (Slot1Enable)
|
||||
{
|
||||
ReadData(fromArm7);
|
||||
}
|
||||
return (byte)(InData >> 0);
|
||||
case 0x4100011:
|
||||
return (byte)(InData >> 8);
|
||||
case 0x4100012:
|
||||
return (byte)(InData >> 16);
|
||||
case 0x4100013:
|
||||
return (byte)(InData >> 24);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine((fromArm7 ? "ARM7" : "ARM9") + " tried to read from Slot 1 @ " + Hex(addr, 8));
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(bool fromArm7, uint addr, byte val)
|
||||
{
|
||||
if (fromArm7 == Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001A0: // AUXSPICNT B0
|
||||
SpiBaudRate = (byte)(val & 0b11);
|
||||
SpiChipSelHold = BitTest(val, 6);
|
||||
SpiBusy = BitTest(val, 7);
|
||||
return;
|
||||
case 0x40001A1: // AUXSPICNT B1
|
||||
Slot1SpiMode = BitTest(val, 5);
|
||||
TransferReadyIrq = BitTest(val, 6);
|
||||
Slot1Enable = BitTest(val, 7);
|
||||
return;
|
||||
|
||||
case 0x40001A2: // AUXSPIDATA
|
||||
SpiTransferTo(val);
|
||||
break;
|
||||
|
||||
case 0x40001A4: // ROMCTRL B0
|
||||
ROMCTRLB0 = val;
|
||||
break;
|
||||
case 0x40001A5: // ROMCTRL B1
|
||||
ROMCTRLB1 = val;
|
||||
break;
|
||||
case 0x40001A6: // ROMCTRL B2
|
||||
if (BitTest(val, 5)) ReleaseReset = true;
|
||||
break;
|
||||
case 0x40001A7: // ROMCTRL B3
|
||||
BlockSize = (byte)(val & 0b111);
|
||||
SlowTransferClock = BitTest(val, 3);
|
||||
|
||||
if (BitTest(val, 7) && !BusyBit31 && Slot1Enable)
|
||||
{
|
||||
ProcessCommand(fromArm7);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (Slot1Enable)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x40001A8: // Slot 1 Command out
|
||||
case 0x40001A9:
|
||||
case 0x40001AA:
|
||||
case 0x40001AB:
|
||||
case 0x40001AC:
|
||||
case 0x40001AD:
|
||||
case 0x40001AE:
|
||||
case 0x40001AF:
|
||||
if (Slot1Enable)
|
||||
{
|
||||
int shiftBy = (int)((7 - (addr & 7)) * 8);
|
||||
PendingCommand &= (ulong)(~(0xFFUL << shiftBy));
|
||||
PendingCommand |= (ulong)val << shiftBy;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine((fromArm7 ? "ARM7" : "ARM9") + " tried to read from Slot 1 @ " + Hex(addr, 8));
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessCommand(bool fromArm7)
|
||||
{
|
||||
ulong cmd = PendingCommand;
|
||||
if (Key1Encryption)
|
||||
{
|
||||
cmd = Decrypt64(EncLutKeycodeLevel2, cmd);
|
||||
}
|
||||
|
||||
// Console.WriteLine("Slot 1 CMD: " + Hex(cmd, 16));
|
||||
|
||||
if (BlockSize == 0)
|
||||
{
|
||||
TransferLength = 0;
|
||||
}
|
||||
else if (BlockSize == 7)
|
||||
{
|
||||
TransferLength = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
TransferLength = 0x100U << BlockSize;
|
||||
}
|
||||
|
||||
if (TransferLength != 0)
|
||||
{
|
||||
DataPos = 0;
|
||||
BytesTransferred = 0;
|
||||
}
|
||||
|
||||
BusyBit31 = true;
|
||||
|
||||
if (cmd == 0x9F00000000000000)
|
||||
{
|
||||
State = CartridgeState.Dummy;
|
||||
}
|
||||
else if (cmd == 0x0000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up cartridge header");
|
||||
State = CartridgeState.ReadCartridgeHeader;
|
||||
}
|
||||
else if (cmd == 0x9000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up ROM chip ID 1");
|
||||
State = CartridgeState.ReadRomChipId1;
|
||||
}
|
||||
else if ((cmd & 0xFF00000000000000) == 0x3C00000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Enabled KEY1 encryption");
|
||||
State = CartridgeState.Dummy2;
|
||||
Key1Encryption = true;
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0x2000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Get Secure Area Block");
|
||||
State = CartridgeState.SecureAreaRead;
|
||||
DataPos = (uint)(((cmd >> 44) & 0xFFFF) * 0x1000);
|
||||
// Console.WriteLine("Secure area read pos: " + Hex(DataPos, 8));
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0x4000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Enable KEY2");
|
||||
State = CartridgeState.Dummy2;
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0x1000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up ROM chip ID 2");
|
||||
State = CartridgeState.ReadRomChipId2;
|
||||
}
|
||||
else if ((cmd & 0xF000000000000000) == 0xA000000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Enter main data mode");
|
||||
State = CartridgeState.Dummy2;
|
||||
Key1Encryption = false;
|
||||
}
|
||||
else if ((cmd & 0xFF00000000FFFFFF) == 0xB700000000000000)
|
||||
{
|
||||
// On a real DS, KEY2 encryption is transparent to software,
|
||||
// as it is all handled in the hardware cartridge interface.
|
||||
// Plus, DS ROM dumps are usually KEY2 decrypted, so in most cases
|
||||
// there's actually no need to actually handle KEY2 encryption in
|
||||
// an emulator.
|
||||
// Console.WriteLine("KEY2 data read");
|
||||
State = CartridgeState.Key2DataRead;
|
||||
|
||||
DataPos = (uint)((cmd >> 24) & 0xFFFFFFFF);
|
||||
// Console.WriteLine("Addr: " + Hex(DataPos, 8));
|
||||
}
|
||||
else if (cmd == 0xB800000000000000)
|
||||
{
|
||||
// Console.WriteLine("Slot 1: Putting up ROM chip ID 3");
|
||||
State = CartridgeState.ReadRomChipId3;
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new NotImplementedException("Slot 1: unimplemented command " + Hex(cmd, 16));
|
||||
}
|
||||
// If block size is zero, no transfer will take place, signal end.
|
||||
if (TransferLength == 0)
|
||||
{
|
||||
FinishTransfer();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadyBit23 = true;
|
||||
|
||||
// Trigger Slot 1 DMA
|
||||
Nds.Scheduler.AddEventRelative(SchedulerId.None, 0, RepeatCartridgeTransfer);
|
||||
// Console.WriteLine("Trigger slot 1 DMA, Dest: " + Hex(Nds.Dma7.Ch[3].DmaDest, 8));
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadData(bool fromArm7)
|
||||
{
|
||||
if (!ReadyBit23)
|
||||
{
|
||||
InData = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
uint val = 0xFFFFFFFF;
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case CartridgeState.Dummy: // returns all 1s
|
||||
break;
|
||||
case CartridgeState.ReadCartridgeHeader:
|
||||
val = GetUint(Rom, DataPos & 0xFFF);
|
||||
break;
|
||||
case CartridgeState.ReadRomChipId1:
|
||||
case CartridgeState.ReadRomChipId2:
|
||||
case CartridgeState.ReadRomChipId3:
|
||||
val = RomChipId;
|
||||
break;
|
||||
case CartridgeState.Key2DataRead:
|
||||
// Console.WriteLine("Key2 data read");
|
||||
if (DataPos < Rom.Length)
|
||||
{
|
||||
if (DataPos < 0x8000)
|
||||
{
|
||||
DataPos = 0x8000 + (DataPos & 0x1FF);
|
||||
}
|
||||
val = GetUint(Rom, DataPos);
|
||||
}
|
||||
break;
|
||||
case CartridgeState.SecureAreaRead:
|
||||
val = GetUint(SecureArea, DataPos - 0x4000);
|
||||
// Console.WriteLine("Secure area read: Pos: " + Hex(DataPos, 8) + " Val: " + Hex(val, 4));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Slot 1: bad state");
|
||||
}
|
||||
|
||||
|
||||
DataPos += 4;
|
||||
BytesTransferred += 4;
|
||||
if (BytesTransferred >= TransferLength)
|
||||
{
|
||||
FinishTransfer();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Slot 1 DMA transfers
|
||||
Nds.Scheduler.AddEventRelative(SchedulerId.None, 0, RepeatCartridgeTransfer);
|
||||
}
|
||||
|
||||
InData = val;
|
||||
}
|
||||
|
||||
public void RepeatCartridgeTransfer(long cyclesLate)
|
||||
{
|
||||
// Console.WriteLine(Hex(Nds.Dma7.Ch[3].DmaDest, 8));
|
||||
if (Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
Nds.Dma7.Repeat((byte)DmaStartTimingNds7.Slot1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Nds.Dma9.Repeat((byte)DmaStartTimingNds9.Slot1);
|
||||
}
|
||||
}
|
||||
|
||||
public void FinishTransfer()
|
||||
{
|
||||
ReadyBit23 = false;
|
||||
BusyBit31 = false;
|
||||
|
||||
if (TransferReadyIrq)
|
||||
{
|
||||
if (Nds.MemoryControl.Nds7Slot1AccessRights)
|
||||
{
|
||||
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.Slot1DataTransferComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.Slot1DataTransferComplete);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From the Key1 Encryption section of GBATek.
|
||||
// Thanks Martin Korth.
|
||||
public static ulong Encrypt64(uint[] encLut, ulong val)
|
||||
{
|
||||
uint y = (uint)val;
|
||||
uint x = (uint)(val >> 32);
|
||||
for (uint i = 0; i < 0x10; i++)
|
||||
{
|
||||
uint z = encLut[i] ^ x;
|
||||
x = encLut[0x012 + (byte)(z >> 24)];
|
||||
x = encLut[0x112 + (byte)(z >> 16)] + x;
|
||||
x = encLut[0x212 + (byte)(z >> 8)] ^ x;
|
||||
x = encLut[0x312 + (byte)(z >> 0)] + x;
|
||||
x ^= y;
|
||||
y = z;
|
||||
}
|
||||
uint outLower = x ^ encLut[0x10];
|
||||
uint outUpper = y ^ encLut[0x11];
|
||||
|
||||
return ((ulong)outUpper << 32) | outLower;
|
||||
}
|
||||
|
||||
public static ulong Decrypt64(uint[] encLut, ulong val)
|
||||
{
|
||||
uint y = (uint)val;
|
||||
uint x = (uint)(val >> 32);
|
||||
for (uint i = 0x11; i >= 0x02; i--)
|
||||
{
|
||||
uint z = encLut[i] ^ x;
|
||||
x = encLut[0x012 + (byte)(z >> 24)];
|
||||
x = encLut[0x112 + (byte)(z >> 16)] + x;
|
||||
x = encLut[0x212 + (byte)(z >> 8)] ^ x;
|
||||
x = encLut[0x312 + (byte)(z >> 0)] + x;
|
||||
x ^= y;
|
||||
y = z;
|
||||
}
|
||||
uint outLower = x ^ encLut[0x1];
|
||||
uint outUpper = y ^ encLut[0x0];
|
||||
|
||||
return ((ulong)outUpper << 32) | outLower;
|
||||
}
|
||||
|
||||
// modulo is always 0x08
|
||||
public void ApplyKeycode(uint[] encLut, Span<uint> keyCode, uint modulo)
|
||||
{
|
||||
ulong encrypted1 = Encrypt64(encLut, ((ulong)keyCode[2] << 32) | keyCode[1]);
|
||||
keyCode[1] = (uint)encrypted1;
|
||||
keyCode[2] = (uint)(encrypted1 >> 32);
|
||||
ulong encrypted0 = Encrypt64(encLut, ((ulong)keyCode[1] << 32) | keyCode[0]);
|
||||
keyCode[0] = (uint)encrypted0;
|
||||
keyCode[1] = (uint)(encrypted0 >> 32);
|
||||
|
||||
ulong scratch = 0;
|
||||
|
||||
for (uint i = 0; i < 0x12; i++)
|
||||
{
|
||||
encLut[i] ^= BSwap32(keyCode[(int)(i % modulo)]);
|
||||
}
|
||||
|
||||
// EncLut is stored in uint for convenience so iterate in uints as well
|
||||
for (uint i = 0; i < 0x412; i += 2)
|
||||
{
|
||||
scratch = Encrypt64(encLut, scratch);
|
||||
encLut[i + 0] = (uint)(scratch >> 32);
|
||||
encLut[i + 1] = (uint)scratch;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitKeycode(uint[] encLut, uint level)
|
||||
{
|
||||
Span<uint> keyCode = stackalloc uint[3];
|
||||
keyCode[0] = IdCode;
|
||||
keyCode[1] = IdCode / 2;
|
||||
keyCode[2] = IdCode * 2;
|
||||
|
||||
// For game cartridge KEY1 decryption, modulo is always 2 (says 8 in GBATek)
|
||||
// but is 2 when divided by four to convert from byte to uint
|
||||
if (level >= 1) ApplyKeycode(encLut, keyCode, 2);
|
||||
if (level >= 2) ApplyKeycode(encLut, keyCode, 2);
|
||||
|
||||
keyCode[1] *= 2;
|
||||
keyCode[2] /= 2;
|
||||
|
||||
if (level >= 3) ApplyKeycode(encLut, keyCode, 2); //
|
||||
}
|
||||
|
||||
public static uint BSwap32(uint val)
|
||||
{
|
||||
return
|
||||
((val >> 24) & 0x000000FF) |
|
||||
((val >> 8) & 0x0000FF00) |
|
||||
((val << 8) & 0x00FF0000) |
|
||||
((val << 24) & 0xFF000000);
|
||||
}
|
||||
|
||||
public void SpiTransferTo(byte val)
|
||||
{
|
||||
// currently only EEPROM support
|
||||
if (Slot1Enable)
|
||||
{
|
||||
var saveType = ExternalMemoryType.Eeprom;
|
||||
// TODO: use a game DB to get memory type
|
||||
switch (saveType)
|
||||
{
|
||||
case ExternalMemoryType.None:
|
||||
break;
|
||||
case ExternalMemoryType.Eeprom:
|
||||
switch (SpiEepromState)
|
||||
{
|
||||
case SpiEepromState.Ready:
|
||||
switch (val)
|
||||
{
|
||||
case 0x06: // Write Enable
|
||||
ExternalMemoryWriteEnable = true;
|
||||
SpiEepromState = SpiEepromState.Ready;
|
||||
break;
|
||||
case 0x04: // Write Disable
|
||||
ExternalMemoryWriteEnable = false;
|
||||
SpiEepromState = SpiEepromState.Ready;
|
||||
break;
|
||||
case 0x5: // Read Status Register
|
||||
SpiEepromState = SpiEepromState.ReadStatus;
|
||||
break;
|
||||
case 0x1: // Write Status Register
|
||||
SpiEepromState = SpiEepromState.WriteStatus;
|
||||
break;
|
||||
case 0x9F: // Read JEDEC ID (returns 0xFF on EEPROM/FLASH)
|
||||
SpiOutData = 0xFF;
|
||||
break;
|
||||
case 0x3: // Read
|
||||
SpiEepromState = SpiEepromState.SetReadAddress;
|
||||
SpiAddress = 0;
|
||||
SpiBytesWritten = 0;
|
||||
break;
|
||||
case 0x2: // Write
|
||||
SpiEepromState = SpiEepromState.SetWriteAddress;
|
||||
SpiAddress = 0;
|
||||
SpiBytesWritten = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SpiEepromState.ReadStatus:
|
||||
byte status = 0;
|
||||
if (ExternalMemoryWriteEnable) status = BitSet(status, 1);
|
||||
status |= (byte)(EepromWriteProtect << 2);
|
||||
SpiOutData = status;
|
||||
break;
|
||||
case SpiEepromState.WriteStatus:
|
||||
ExternalMemoryWriteEnable = BitTest(val, 1);
|
||||
EepromWriteProtect = (byte)((val >> 2) & 0b11);
|
||||
break;
|
||||
case SpiEepromState.SetReadAddress:
|
||||
SpiAddress <<= 8;
|
||||
SpiAddress |= val;
|
||||
if (++SpiBytesWritten == 2)
|
||||
{
|
||||
SpiEepromState = SpiEepromState.ReadData;
|
||||
}
|
||||
break;
|
||||
case SpiEepromState.ReadData:
|
||||
SpiOutData = ExternalMemory[SpiAddress];
|
||||
SpiAddress++;
|
||||
break;
|
||||
case SpiEepromState.SetWriteAddress:
|
||||
SpiAddress <<= 8;
|
||||
SpiAddress |= val;
|
||||
if (++SpiBytesWritten == 2)
|
||||
{
|
||||
SpiEepromState = SpiEepromState.WriteData;
|
||||
}
|
||||
break;
|
||||
case SpiEepromState.WriteData:
|
||||
ExternalMemory[SpiAddress] = val;
|
||||
SpiOutData = 0;
|
||||
SpiAddress++;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ExternalMemoryType.FlashWithInfrared:
|
||||
switch (SpiFlashState)
|
||||
{
|
||||
case SpiFlashState.TakePrefix:
|
||||
if (val == 0)
|
||||
{
|
||||
SpiFlashState = SpiFlashState.Ready;
|
||||
Console.WriteLine("Flash with IR command");
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.Ready:
|
||||
// Console.WriteLine("SPI: Receive command! " + Hex(val, 2));
|
||||
SpiOutData = 0x00;
|
||||
switch (val)
|
||||
{
|
||||
case 0x06:
|
||||
ExternalMemoryWriteEnable = true;
|
||||
break;
|
||||
case 0x04:
|
||||
ExternalMemoryWriteEnable = false;
|
||||
break;
|
||||
case 0x9F:
|
||||
SpiFlashState = SpiFlashState.Identification;
|
||||
SpiAddress = 0;
|
||||
break;
|
||||
case 0x03:
|
||||
SpiFlashState = SpiFlashState.ReceiveAddress;
|
||||
SpiAddress = 0;
|
||||
SpiBytesWritten = 0;
|
||||
break;
|
||||
case 0x0B:
|
||||
throw new NotImplementedException("slot1 flash fast read");
|
||||
case 0x0A:
|
||||
throw new NotImplementedException("slot1 flash write");
|
||||
case 0x02:
|
||||
throw new NotImplementedException("slot1 flash program");
|
||||
case 0x05: // Identification
|
||||
// Console.WriteLine("SPI ID");
|
||||
SpiAddress = 0;
|
||||
SpiOutData = 0x00;
|
||||
break;
|
||||
case 0x00:
|
||||
break;
|
||||
// default:
|
||||
// throw new NotImplementedException("SPI: Unimplemented command: " + Hex(val, 2));
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.ReceiveAddress:
|
||||
// Console.WriteLine("SPI: Address byte write: " + Hex(val, 2));
|
||||
SpiAddress <<= 8;
|
||||
SpiAddress |= val;
|
||||
if (++SpiBytesWritten == 3)
|
||||
{
|
||||
SpiBytesWritten = 0;
|
||||
SpiFlashState = SpiFlashState.Reading;
|
||||
// Console.WriteLine("SPI: Address written: " + Hex(Address, 6));
|
||||
}
|
||||
break;
|
||||
case SpiFlashState.Reading:
|
||||
// Console.WriteLine("SPI: Read from address: " + Hex(Address, 6));
|
||||
// Nds7.Cpu.Error("SPI");
|
||||
SpiOutData = ExternalMemory[SpiAddress];
|
||||
SpiAddress++;
|
||||
SpiAddress &= 0xFFFFFF;
|
||||
break;
|
||||
case SpiFlashState.Identification:
|
||||
SpiOutData = FlashId[SpiAddress];
|
||||
SpiAddress++;
|
||||
SpiAddress %= 3;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!SpiChipSelHold)
|
||||
{
|
||||
SpiEepromState = SpiEepromState.Ready;
|
||||
SpiFlashState = SpiFlashState.TakePrefix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] GetSave()
|
||||
{
|
||||
return ExternalMemory;
|
||||
}
|
||||
|
||||
public void LoadSave(byte[] sav)
|
||||
{
|
||||
sav.CopyTo(ExternalMemory, 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c4deba1667702f49b08a4ccab53688e
|
||||
guid: 0aa73f4dabd6dcd44b7d5eaa5cbf4c68
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
207
Assets/emulator/CoreUtil.cs
Normal file
207
Assets/emulator/CoreUtil.cs
Normal file
@ -0,0 +1,207 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
sealed class Bits
|
||||
{
|
||||
public const uint BIT_0 = (1 << 0);
|
||||
public const uint BIT_1 = (1 << 1);
|
||||
public const uint BIT_2 = (1 << 2);
|
||||
public const uint BIT_3 = (1 << 3);
|
||||
public const uint BIT_4 = (1 << 4);
|
||||
public const uint BIT_5 = (1 << 5);
|
||||
public const uint BIT_6 = (1 << 6);
|
||||
public const uint BIT_7 = (1 << 7);
|
||||
public const uint BIT_8 = (1 << 8);
|
||||
public const uint BIT_9 = (1 << 9);
|
||||
public const uint BIT_10 = (1 << 10);
|
||||
public const uint BIT_11 = (1 << 11);
|
||||
public const uint BIT_12 = (1 << 12);
|
||||
public const uint BIT_13 = (1 << 13);
|
||||
public const uint BIT_14 = (1 << 14);
|
||||
public const uint BIT_15 = (1 << 15);
|
||||
public const uint BIT_16 = (1 << 16);
|
||||
public const uint BIT_17 = (1 << 17);
|
||||
public const uint BIT_18 = (1 << 18);
|
||||
public const uint BIT_19 = (1 << 19);
|
||||
public const uint BIT_20 = (1 << 20);
|
||||
public const uint BIT_21 = (1 << 21);
|
||||
public const uint BIT_22 = (1 << 22);
|
||||
public const uint BIT_23 = (1 << 23);
|
||||
public const uint BIT_24 = (1 << 24);
|
||||
public const uint BIT_25 = (1 << 25);
|
||||
public const uint BIT_26 = (1 << 26);
|
||||
public const uint BIT_27 = (1 << 27);
|
||||
public const uint BIT_28 = (1 << 28);
|
||||
public const uint BIT_29 = (1 << 29);
|
||||
public const uint BIT_30 = (1 << 30);
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool BitTest(uint i, byte bit)
|
||||
{
|
||||
return (i & (1 << bit)) != 0;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool BitTest(ulong i, byte bit)
|
||||
{
|
||||
return (i & (1u << bit)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool BitTest(long i, byte bit)
|
||||
{
|
||||
return (i & (1u << bit)) != 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint BitSet(uint i, byte bit)
|
||||
{
|
||||
return (uint)(i | (uint)(1 << bit));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte BitSet(byte i, byte bit)
|
||||
{
|
||||
return (byte)(i | (byte)(1 << bit));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte BitClear(byte i, byte bit)
|
||||
{
|
||||
return (byte)(i & ~(byte)(1 << bit));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint BitRange(uint i, byte start, byte end)
|
||||
{
|
||||
return (i >> start) & (0xFFFFFFFF >> (31 - (end - start)));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte BitReverse8(byte i)
|
||||
{
|
||||
return (byte)((i * 0x0202020202U & 0x010884422010U) % 1023);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint LogicalShiftLeft32(uint n, byte bits)
|
||||
{
|
||||
return n << bits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint LogicalShiftRight32(uint n, byte bits)
|
||||
{
|
||||
return n >> bits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint ArithmeticShiftRight32(uint n, byte bits)
|
||||
{
|
||||
return (uint)((int)n >> bits);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint RotateRight32(uint n, byte bits)
|
||||
{
|
||||
return (n >> bits) | (n << -bits);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong RotateRight64(ulong n, byte bits)
|
||||
{
|
||||
return (n >> bits) | (n << -bits);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte GetByteIn(long n, int pos)
|
||||
{
|
||||
return (byte)(n >> (pos * 8));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte GetByteIn(ulong n, int pos)
|
||||
{
|
||||
return (byte)(n >> (pos * 8));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte GetByteIn(long n, uint pos)
|
||||
{
|
||||
return (byte)(n >> (int)(pos * 8));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte GetByteIn(ulong n, uint pos)
|
||||
{
|
||||
return (byte)(n >> (int)(pos * 8));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint SetByteIn(uint n, byte val, uint pos)
|
||||
{
|
||||
uint mask = ~(0xFFU << (int)(pos * 8));
|
||||
uint or = (uint)(val << (int)(pos * 8));
|
||||
return (n & mask) | or;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong SetByteIn(ulong n, byte val, uint pos)
|
||||
{
|
||||
ulong mask = ~(0xFFUL << (int)(pos * 8));
|
||||
ulong or = (ulong)((ulong)val << (int)(pos * 8));
|
||||
return (n & mask) | or;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static long SetByteIn(long n, byte val, uint pos)
|
||||
{
|
||||
long mask = ~(0xFFL << (int)(pos * 8));
|
||||
long or = (long)((long)val << (int)(pos * 8));
|
||||
return (n & mask) | or;
|
||||
}
|
||||
|
||||
// public bool bitReset(uint i, uint bit)
|
||||
// {
|
||||
// return i & (~(1 << bit));
|
||||
// }
|
||||
|
||||
// public bool bitSetValue(uint i, uint bit, bool value)
|
||||
// {
|
||||
// if (value)
|
||||
// {
|
||||
// return i | (1 << bit);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return i & (~(1 << bit));
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
public class CoreUtil
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Swap<T>(ref T one, ref T two)
|
||||
{
|
||||
T temp = one;
|
||||
one = two;
|
||||
two = temp;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static sbyte SignExtend8(byte val, int pos)
|
||||
{
|
||||
return (sbyte)(((sbyte)val << (7 - pos)) >> (7 - pos));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static short SignExtend16(ushort val, int pos)
|
||||
{
|
||||
return (short)(((short)val << (15 - pos)) >> (15 - pos));
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/CoreUtil.cs.meta
Normal file
11
Assets/emulator/CoreUtil.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc5bdea9f2094294da4dcefeebda8f63
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
86
Assets/emulator/Cp15.cs
Normal file
86
Assets/emulator/Cp15.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public class Cp15
|
||||
{
|
||||
Nds Nds;
|
||||
|
||||
public Cp15(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
}
|
||||
|
||||
public uint ControlRegister;
|
||||
|
||||
public uint DataTcmSettings;
|
||||
public uint InstTcmSettings;
|
||||
|
||||
public void TransferTo(uint opcode1, uint rdVal, uint cRn, uint cRm, uint opcode2)
|
||||
{
|
||||
uint reg = ((cRn & 0xF) << 8) | ((cRm & 0xF) << 4) | (opcode2 & 0x7);
|
||||
|
||||
switch (reg)
|
||||
{
|
||||
case 0x100:
|
||||
ControlRegister = rdVal;
|
||||
ControlRegister |= 0b00000000000000000000000001111000;
|
||||
ControlRegister &= 0b00000000000011111111000010000101;
|
||||
Nds.Mem9.UpdateTcmSettings();
|
||||
break;
|
||||
|
||||
case 0x704:
|
||||
case 0x782:
|
||||
Nds.Cpu9.Halted = true;
|
||||
break;
|
||||
|
||||
case 0x910:
|
||||
DataTcmSettings = rdVal;
|
||||
Nds.Mem9.UpdateTcmSettings();
|
||||
break;
|
||||
case 0x911:
|
||||
InstTcmSettings = rdVal;
|
||||
Nds.Mem9.UpdateTcmSettings();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Console.WriteLine($"UNIMPLEMENTED TO CP15 {opcode1},C{cRn},C{cRm},{opcode2}: {HexN(rdVal, 8)}");
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public uint TransferFrom(uint opcode1, uint cRn, uint cRm, uint opcode2)
|
||||
{
|
||||
uint val = 0;
|
||||
|
||||
uint reg = ((cRn & 0xF) << 8) | ((cRm & 0xF) << 4) | (opcode2 & 0x7);
|
||||
switch (reg)
|
||||
{
|
||||
case 0x000: // ID register
|
||||
val = 0x41059461;
|
||||
break;
|
||||
case 0x001:
|
||||
val = 0x0F0D2112;
|
||||
break;
|
||||
case 0x100:
|
||||
val = ControlRegister;
|
||||
break;
|
||||
case 0x910:
|
||||
val = DataTcmSettings;
|
||||
break;
|
||||
case 0x911:
|
||||
val = InstTcmSettings;
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"UNIMPLEMENTED FROM CP15 {opcode1},C{cRn},C{cRm},{opcode2}");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/Cp15.cs.meta
Normal file
11
Assets/emulator/Cp15.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 930c82bbb70e8d54d8bed296233fb78a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
29
Assets/emulator/Dma.cs
Normal file
29
Assets/emulator/Dma.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using System;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum DmaStartTiming
|
||||
{
|
||||
Immediately = 0,
|
||||
VBlank = 1,
|
||||
HBlank = 2,
|
||||
Special = 3,
|
||||
}
|
||||
|
||||
public enum DmaDestAddrCtrl
|
||||
{
|
||||
Increment = 0,
|
||||
Decrement = 1,
|
||||
Fixed = 2,
|
||||
IncrementReload = 3,
|
||||
}
|
||||
|
||||
public enum DmaSrcAddrCtrl
|
||||
{
|
||||
Increment = 0,
|
||||
Decrement = 1,
|
||||
Fixed = 2,
|
||||
PROHIBITED = 3,
|
||||
}
|
||||
}
|
11
Assets/emulator/Dma.cs.meta
Normal file
11
Assets/emulator/Dma.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d57248ed47e4cf4a922bbb7f923b6e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
444
Assets/emulator/DmaGba.cs
Normal file
444
Assets/emulator/DmaGba.cs
Normal file
@ -0,0 +1,444 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using System;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum DmaStartTimingGba
|
||||
{
|
||||
Immediately = 0,
|
||||
VBlank = 1,
|
||||
HBlank = 2,
|
||||
Special = 3,
|
||||
}
|
||||
|
||||
public sealed class DmaChannelGba
|
||||
{
|
||||
public uint DMASAD;
|
||||
public uint DMADAD;
|
||||
public uint DMACNT_L;
|
||||
|
||||
public uint DmaSource;
|
||||
public uint DmaDest;
|
||||
public uint DmaLength;
|
||||
|
||||
// DMACNT_H
|
||||
public DmaDestAddrCtrl DestAddrCtrl;
|
||||
public DmaSrcAddrCtrl SrcAddrCtrl;
|
||||
public bool Repeat;
|
||||
public bool TransferType;
|
||||
public bool GamePakDRQ;
|
||||
public DmaStartTimingGba StartTiming;
|
||||
public bool FinishedIRQ;
|
||||
public bool Enabled; // Don't directly set to false, use Disable()
|
||||
|
||||
public uint DMACNT_H;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
// DMASAD, DMADAD, and DMACNT_L are write-only
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x0A: // DMACNT_H B0
|
||||
case 0x0B: // DMACNT_H B1
|
||||
val = GetByteIn(GetControl(), addr & 1);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x00: // DMASAD B0
|
||||
case 0x01: // DMASAD B1
|
||||
case 0x02: // DMASAD B2
|
||||
case 0x03: // DMASAD B3
|
||||
DMASAD = SetByteIn(DMASAD, val, addr & 3);
|
||||
break;
|
||||
|
||||
case 0x04: // DMADAD B0
|
||||
case 0x05: // DMADAD B1
|
||||
case 0x06: // DMADAD B2
|
||||
case 0x07: // DMADAD B3
|
||||
DMADAD = SetByteIn(DMADAD, val, addr & 3);
|
||||
break;
|
||||
|
||||
case 0x08: // DMACNT_L B0
|
||||
case 0x09: // DMACNT_L B1
|
||||
DMACNT_L = SetByteIn(DMACNT_L, val, addr & 1);
|
||||
break;
|
||||
case 0x0A: // DMACNT_H B0
|
||||
case 0x0B: // DMACNT_H B1
|
||||
DMACNT_H = SetByteIn(DMACNT_H, val, addr & 1);
|
||||
UpdateControl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateControl()
|
||||
{
|
||||
DestAddrCtrl = (DmaDestAddrCtrl)BitRange(DMACNT_H, 5, 6);
|
||||
SrcAddrCtrl = (DmaSrcAddrCtrl)BitRange(DMACNT_H, 7, 8);
|
||||
Repeat = BitTest(DMACNT_H, 9);
|
||||
TransferType = BitTest(DMACNT_H, 10);
|
||||
GamePakDRQ = BitTest(DMACNT_H, 11);
|
||||
StartTiming = (DmaStartTimingGba)BitRange(DMACNT_H, 12, 13);
|
||||
FinishedIRQ = BitTest(DMACNT_H, 14);
|
||||
if (BitTest(DMACNT_H, 15))
|
||||
{
|
||||
Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Disable();
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetControl()
|
||||
{
|
||||
uint val = 0;
|
||||
val |= ((uint)DestAddrCtrl & 0b11) << 5;
|
||||
val |= ((uint)SrcAddrCtrl & 0b11) << 7;
|
||||
if (Repeat) val = BitSet(val, 9);
|
||||
if (TransferType) val = BitSet(val, 10);
|
||||
if (GamePakDRQ) val = BitSet(val, 11);
|
||||
val |= ((uint)StartTiming & 0b11) << 12;
|
||||
if (FinishedIRQ) val = BitSet(val, 14);
|
||||
if (Enabled) val = BitSet(val, 15);
|
||||
|
||||
DMACNT_H = val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
DmaSource = DMASAD;
|
||||
DmaDest = DMADAD;
|
||||
DmaLength = DMACNT_L;
|
||||
}
|
||||
|
||||
Enabled = true;
|
||||
GetControl();
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
Enabled = false;
|
||||
GetControl();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe sealed class DmaGba
|
||||
{
|
||||
Gba Gba;
|
||||
|
||||
public DmaChannelGba[] Ch = new DmaChannelGba[4] {
|
||||
new DmaChannelGba(),
|
||||
new DmaChannelGba(),
|
||||
new DmaChannelGba(),
|
||||
new DmaChannelGba(),
|
||||
};
|
||||
|
||||
static readonly uint[] DmaSourceMask = { 0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF };
|
||||
static readonly uint[] DmaDestMask = { 0x07FFFFFF, 0x07FFFFFF, 0x07FFFFFF, 0x0FFFFFFFF };
|
||||
|
||||
public bool DmaLock;
|
||||
|
||||
public DmaGba(Gba gba)
|
||||
{
|
||||
Gba = gba;
|
||||
}
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
if (addr >= 0x40000B0 && addr <= 0x40000BB)
|
||||
{
|
||||
return Ch[0].ReadHwio8(addr - 0x40000B0);
|
||||
}
|
||||
else if (addr >= 0x40000BC && addr <= 0x40000C7)
|
||||
{
|
||||
return Ch[1].ReadHwio8(addr - 0x40000BC);
|
||||
}
|
||||
else if (addr >= 0x40000C8 && addr <= 0x40000D3)
|
||||
{
|
||||
return Ch[2].ReadHwio8(addr - 0x40000C8);
|
||||
}
|
||||
else if (addr >= 0x40000D4 && addr <= 0x40000DF)
|
||||
{
|
||||
return Ch[3].ReadHwio8(addr - 0x40000D4);
|
||||
}
|
||||
throw new Exception("This shouldn't happen.");
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
if (addr >= 0x40000B0 && addr <= 0x40000BB)
|
||||
{
|
||||
bool oldEnabled = Ch[0].Enabled;
|
||||
Ch[0].WriteHwio8(addr - 0x40000B0, val);
|
||||
if (!oldEnabled && Ch[0].Enabled) ExecuteImmediate(0);
|
||||
return;
|
||||
}
|
||||
else if (addr >= 0x40000BC && addr <= 0x40000C7)
|
||||
{
|
||||
bool oldEnabled = Ch[1].Enabled;
|
||||
Ch[1].WriteHwio8(addr - 0x40000BC, val);
|
||||
if (!oldEnabled && Ch[1].Enabled) ExecuteImmediate(1);
|
||||
return;
|
||||
}
|
||||
else if (addr >= 0x40000C8 && addr <= 0x40000D3)
|
||||
{
|
||||
bool oldEnabled = Ch[2].Enabled;
|
||||
Ch[2].WriteHwio8(addr - 0x40000C8, val);
|
||||
if (!oldEnabled && Ch[2].Enabled) ExecuteImmediate(2);
|
||||
return;
|
||||
}
|
||||
else if (addr >= 0x40000D4 && addr <= 0x40000DF)
|
||||
{
|
||||
bool oldEnabled = Ch[3].Enabled;
|
||||
Ch[3].WriteHwio8(addr - 0x40000D4, val);
|
||||
if (!oldEnabled && Ch[3].Enabled) ExecuteImmediate(3);
|
||||
return;
|
||||
}
|
||||
throw new Exception("This shouldn't happen.");
|
||||
}
|
||||
|
||||
public void ExecuteDma(DmaChannelGba c, uint ci)
|
||||
{
|
||||
DmaLock = true;
|
||||
|
||||
// Least significant 28 (or 27????) bits
|
||||
c.DmaSource &= DmaSourceMask[ci];
|
||||
c.DmaDest &= DmaDestMask[ci];
|
||||
|
||||
if (ci == 3)
|
||||
{
|
||||
// DMA 3 is 16-bit length
|
||||
c.DmaLength &= 0b1111111111111111;
|
||||
// Value of zero is treated as maximum length
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x10000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// DMA 0-2 are 14-bit length
|
||||
c.DmaLength &= 0b11111111111111;
|
||||
// Value of zero is treated as maximum length
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x4000;
|
||||
}
|
||||
|
||||
// Console.WriteLine($"Starting DMA {ci}");
|
||||
// Console.WriteLine($"SRC: {Util.HexN(srcAddr, 7)}");
|
||||
// Console.WriteLine($"DEST: {Util.HexN(destAddr, 7)}");
|
||||
// Console.WriteLine($"LENGTH: {Util.HexN(c.DmaLength, 4)}");
|
||||
|
||||
int destOffsPerUnit;
|
||||
int sourceOffsPerUnit;
|
||||
if (c.TransferType)
|
||||
{
|
||||
switch (c.DestAddrCtrl)
|
||||
{
|
||||
case DmaDestAddrCtrl.Increment: destOffsPerUnit = +4; break;
|
||||
case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -4; break;
|
||||
case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +4; break;
|
||||
default: destOffsPerUnit = 0; break;
|
||||
}
|
||||
switch (c.SrcAddrCtrl)
|
||||
{
|
||||
case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +4; break;
|
||||
case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -4; break;
|
||||
default: sourceOffsPerUnit = 0; break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (c.DestAddrCtrl)
|
||||
{
|
||||
case DmaDestAddrCtrl.Increment: destOffsPerUnit = +2; break;
|
||||
case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -2; break;
|
||||
case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +2; break;
|
||||
default: destOffsPerUnit = 0; break;
|
||||
}
|
||||
switch (c.SrcAddrCtrl)
|
||||
{
|
||||
case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +2; break;
|
||||
case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -2; break;
|
||||
default: sourceOffsPerUnit = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
uint origLength = c.DmaLength;
|
||||
|
||||
|
||||
if (c.TransferType)
|
||||
{
|
||||
for (; c.DmaLength > 0; c.DmaLength--)
|
||||
{
|
||||
Gba.Mem.Write32(c.DmaDest & ~3u, Gba.Mem.Read32(c.DmaSource & ~3u));
|
||||
Gba.Tick(Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]);
|
||||
Gba.Tick(Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]);
|
||||
|
||||
c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest);
|
||||
c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; c.DmaLength > 0; c.DmaLength--)
|
||||
{
|
||||
Gba.Mem.Write16(c.DmaDest & ~1u, Gba.Mem.Read16(c.DmaSource & ~1u));
|
||||
Gba.Tick(Gba.Cpu.Timing8And16[(c.DmaSource >> 24) & 0xF]);
|
||||
Gba.Tick(Gba.Cpu.Timing8And16[(c.DmaDest >> 24) & 0xF]);
|
||||
|
||||
c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest);
|
||||
c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (c.DestAddrCtrl == DmaDestAddrCtrl.IncrementReload)
|
||||
{
|
||||
c.DmaLength = origLength;
|
||||
|
||||
if (c.Repeat)
|
||||
{
|
||||
c.DmaDest = c.DMADAD;
|
||||
}
|
||||
}
|
||||
|
||||
if (c.FinishedIRQ)
|
||||
{
|
||||
Gba.HwControl.FlagInterrupt((uint)InterruptGba.Dma0 + ci);
|
||||
}
|
||||
|
||||
DmaLock = false;
|
||||
}
|
||||
|
||||
public void ExecuteSoundDma(DmaChannelGba c, uint ci)
|
||||
{
|
||||
DmaLock = true;
|
||||
|
||||
// Least significant 28 (or 27????) bits
|
||||
uint srcAddr = c.DmaSource & 0b1111111111111111111111111111;
|
||||
uint destAddr = c.DmaDest & 0b111111111111111111111111111;
|
||||
|
||||
// 4 units of 32bits (16 bytes) are transferred to FIFO_A or FIFO_B
|
||||
for (uint i = 0; i < 4; i++)
|
||||
{
|
||||
uint val = Gba.Mem.Read32(srcAddr + 0);
|
||||
if (destAddr == 0x40000A0)
|
||||
{
|
||||
Gba.GbaAudio.A.Insert((byte)val);
|
||||
Gba.GbaAudio.A.Insert((byte)(val >>= 8));
|
||||
Gba.GbaAudio.A.Insert((byte)(val >>= 8));
|
||||
Gba.GbaAudio.A.Insert((byte)(val >>= 8));
|
||||
}
|
||||
else if (destAddr == 0x40000A4)
|
||||
{
|
||||
Gba.GbaAudio.B.Insert((byte)val);
|
||||
Gba.GbaAudio.B.Insert((byte)(val >>= 8));
|
||||
Gba.GbaAudio.B.Insert((byte)(val >>= 8));
|
||||
Gba.GbaAudio.B.Insert((byte)(val >>= 8));
|
||||
}
|
||||
else
|
||||
{
|
||||
Gba.Mem.Write8(destAddr + 0, (byte)val);
|
||||
Gba.Mem.Write8(destAddr + 1, (byte)(val >>= 8));
|
||||
Gba.Mem.Write8(destAddr + 2, (byte)(val >>= 8));
|
||||
Gba.Mem.Write8(destAddr + 3, (byte)(val >>= 8));
|
||||
}
|
||||
|
||||
switch (c.SrcAddrCtrl)
|
||||
{
|
||||
case DmaSrcAddrCtrl.Increment: srcAddr += 4; break;
|
||||
case DmaSrcAddrCtrl.Decrement: srcAddr -= 4; break;
|
||||
case DmaSrcAddrCtrl.Fixed: break;
|
||||
}
|
||||
|
||||
// Applying proper timing to sound DMAs causes crackling in certain games including PMD.
|
||||
// This only happens with scheduled timers, which leads me to believe the real problem is in there.
|
||||
// PROBLEM SOLVED.... my timers were 1 cycle too slow to reload
|
||||
Gba.Cpu.InstructionCycles += (Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]);
|
||||
Gba.Cpu.InstructionCycles += (Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]);
|
||||
}
|
||||
|
||||
c.DmaSource = srcAddr;
|
||||
|
||||
if (c.FinishedIRQ)
|
||||
{
|
||||
Gba.HwControl.FlagInterrupt((uint)InterruptGba.Dma0 + ci);
|
||||
}
|
||||
|
||||
DmaLock = false;
|
||||
}
|
||||
|
||||
|
||||
public void ExecuteImmediate(uint ci)
|
||||
{
|
||||
DmaChannelGba c = Ch[ci];
|
||||
|
||||
if (c.Enabled && c.StartTiming == DmaStartTimingGba.Immediately)
|
||||
{
|
||||
c.Disable();
|
||||
|
||||
ExecuteDma(c, ci);
|
||||
}
|
||||
}
|
||||
|
||||
public void RepeatFifoA()
|
||||
{
|
||||
if (!DmaLock)
|
||||
{
|
||||
if (Ch[1].StartTiming == DmaStartTimingGba.Special)
|
||||
{
|
||||
ExecuteSoundDma(Ch[1], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void RepeatFifoB()
|
||||
{
|
||||
if (!DmaLock)
|
||||
{
|
||||
if (Ch[2].StartTiming == DmaStartTimingGba.Special)
|
||||
{
|
||||
ExecuteSoundDma(Ch[2], 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RepeatHblank()
|
||||
{
|
||||
if (!DmaLock)
|
||||
{
|
||||
for (uint ci = 0; ci < 4; ci++)
|
||||
{
|
||||
DmaChannelGba c = Ch[ci];
|
||||
if (c.StartTiming == DmaStartTimingGba.HBlank)
|
||||
{
|
||||
c.DmaLength = c.DMACNT_L;
|
||||
ExecuteDma(c, ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RepeatVblank()
|
||||
{
|
||||
if (!DmaLock)
|
||||
{
|
||||
for (uint ci = 0; ci < 4; ci++)
|
||||
{
|
||||
DmaChannelGba c = Ch[ci];
|
||||
if (c.StartTiming == DmaStartTimingGba.VBlank)
|
||||
{
|
||||
c.DmaLength = c.DMACNT_L;
|
||||
ExecuteDma(c, ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/DmaGba.cs.meta
Normal file
11
Assets/emulator/DmaGba.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c4ffd474f58e654b873b37f5ca89d16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
417
Assets/emulator/DmaNds.cs
Normal file
417
Assets/emulator/DmaNds.cs
Normal file
@ -0,0 +1,417 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using static Util;
|
||||
using System;
|
||||
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum DmaStartTimingNds9 : byte
|
||||
{
|
||||
Immediately = 0,
|
||||
VBlank = 1,
|
||||
HBlank = 2,
|
||||
UponRenderBegin = 3,
|
||||
MainMemoryDisplay = 4,
|
||||
Slot1 = 5,
|
||||
Slot2 = 6,
|
||||
GeometryCommandFifo = 7,
|
||||
}
|
||||
|
||||
public enum DmaStartTimingNds7 : byte
|
||||
{
|
||||
Immediately = 0,
|
||||
VBlank = 1,
|
||||
Slot1 = 2,
|
||||
Misc = 3,
|
||||
}
|
||||
|
||||
public sealed class DmaChannelNds
|
||||
{
|
||||
public bool Nds7;
|
||||
|
||||
public DmaChannelNds(bool nds7)
|
||||
{
|
||||
Nds7 = nds7;
|
||||
}
|
||||
|
||||
public uint DMASAD;
|
||||
public uint DMADAD;
|
||||
public uint DMACNT_L;
|
||||
|
||||
public uint DmaSource;
|
||||
public uint DmaDest;
|
||||
public uint DmaLength;
|
||||
|
||||
// DMACNT_H
|
||||
public DmaDestAddrCtrl DestAddrCtrl;
|
||||
public DmaSrcAddrCtrl SrcAddrCtrl;
|
||||
public bool Repeat;
|
||||
public bool TransferType;
|
||||
public byte StartTiming;
|
||||
public bool FinishedIRQ;
|
||||
public bool Enabled; // Don't directly set to false, use Disable()
|
||||
|
||||
public uint DMACNT_H;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
// DMASAD, DMADAD, and DMACNT_L are write-only
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x0A: // DMACNT_H B0
|
||||
case 0x0B: // DMACNT_H B1
|
||||
val = GetByteIn(GetControl(), addr & 1);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x00: // DMASAD B0
|
||||
case 0x01: // DMASAD B1
|
||||
case 0x02: // DMASAD B2
|
||||
case 0x03: // DMASAD B3
|
||||
DMASAD = SetByteIn(DMASAD, val, addr & 3);
|
||||
break;
|
||||
|
||||
case 0x04: // DMADAD B0
|
||||
case 0x05: // DMADAD B1
|
||||
case 0x06: // DMADAD B2
|
||||
case 0x07: // DMADAD B3
|
||||
DMADAD = SetByteIn(DMADAD, val, addr & 3);
|
||||
break;
|
||||
|
||||
case 0x08: // DMACNT_L B0
|
||||
case 0x09: // DMACNT_L B1
|
||||
DMACNT_L = SetByteIn(DMACNT_L, val, addr & 1);
|
||||
break;
|
||||
case 0x0A: // DMACNT_H B0
|
||||
case 0x0B: // DMACNT_H B1
|
||||
DMACNT_H = SetByteIn(DMACNT_H, val, addr & 1);
|
||||
UpdateControl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateControl()
|
||||
{
|
||||
DestAddrCtrl = (DmaDestAddrCtrl)BitRange(DMACNT_H, 5, 6);
|
||||
SrcAddrCtrl = (DmaSrcAddrCtrl)BitRange(DMACNT_H, 7, 8);
|
||||
Repeat = BitTest(DMACNT_H, 9);
|
||||
TransferType = BitTest(DMACNT_H, 10);
|
||||
if (!Nds7)
|
||||
{
|
||||
StartTiming = (byte)BitRange(DMACNT_H, 11, 13);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartTiming = (byte)BitRange(DMACNT_H, 12, 13);
|
||||
}
|
||||
FinishedIRQ = BitTest(DMACNT_H, 14);
|
||||
if (BitTest(DMACNT_H, 15))
|
||||
{
|
||||
Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Disable();
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetControl()
|
||||
{
|
||||
uint val = 0;
|
||||
val |= ((uint)DestAddrCtrl & 0b11) << 5;
|
||||
val |= ((uint)SrcAddrCtrl & 0b11) << 7;
|
||||
if (Repeat) val = BitSet(val, 9);
|
||||
if (TransferType) val = BitSet(val, 10);
|
||||
val |= ((uint)StartTiming & 0b111) << 11;
|
||||
if (FinishedIRQ) val = BitSet(val, 14);
|
||||
if (Enabled) val = BitSet(val, 15);
|
||||
|
||||
DMACNT_H = val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
DmaSource = DMASAD;
|
||||
DmaDest = DMADAD;
|
||||
DmaLength = DMACNT_L;
|
||||
}
|
||||
|
||||
Enabled = true;
|
||||
GetControl();
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
Enabled = false;
|
||||
GetControl();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe sealed class DmaNds
|
||||
{
|
||||
bool Nds7;
|
||||
Memory Mem;
|
||||
HwControl HwControl;
|
||||
|
||||
public DmaChannelNds[] Ch;
|
||||
static readonly uint[] DmaSourceMask = { 0x07FFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF };
|
||||
static readonly uint[] DmaDestMask = { 0x07FFFFFF, 0x07FFFFFF, 0x07FFFFFF, 0x0FFFFFFFF };
|
||||
|
||||
public byte[] DmaFill = new byte[16];
|
||||
|
||||
public bool DmaLock;
|
||||
|
||||
public DmaNds(bool nds7, Memory mem, HwControlNds hwControl)
|
||||
{
|
||||
Nds7 = nds7;
|
||||
Mem = mem;
|
||||
HwControl = hwControl;
|
||||
|
||||
Ch = new DmaChannelNds[4] {
|
||||
new DmaChannelNds(Nds7),
|
||||
new DmaChannelNds(Nds7),
|
||||
new DmaChannelNds(Nds7),
|
||||
new DmaChannelNds(Nds7),
|
||||
};
|
||||
}
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
if (addr >= 0x40000B0 && addr <= 0x40000BB)
|
||||
{
|
||||
return Ch[0].ReadHwio8(addr - 0x40000B0);
|
||||
}
|
||||
else if (addr >= 0x40000BC && addr <= 0x40000C7)
|
||||
{
|
||||
return Ch[1].ReadHwio8(addr - 0x40000BC);
|
||||
}
|
||||
else if (addr >= 0x40000C8 && addr <= 0x40000D3)
|
||||
{
|
||||
return Ch[2].ReadHwio8(addr - 0x40000C8);
|
||||
}
|
||||
else if (addr >= 0x40000D4 && addr <= 0x40000DF)
|
||||
{
|
||||
return Ch[3].ReadHwio8(addr - 0x40000D4);
|
||||
}
|
||||
else if (addr >= 0x40000E0 && addr <= 0x40000EF)
|
||||
{
|
||||
return DmaFill[addr & 0xF];
|
||||
}
|
||||
throw new Exception("This shouldn't happen.");
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
if (addr >= 0x40000B0 && addr <= 0x40000BB)
|
||||
{
|
||||
bool oldEnabled = Ch[0].Enabled;
|
||||
Ch[0].WriteHwio8(addr - 0x40000B0, val);
|
||||
if (!oldEnabled && Ch[0].Enabled) ExecuteImmediate(0);
|
||||
return;
|
||||
}
|
||||
else if (addr >= 0x40000BC && addr <= 0x40000C7)
|
||||
{
|
||||
bool oldEnabled = Ch[1].Enabled;
|
||||
Ch[1].WriteHwio8(addr - 0x40000BC, val);
|
||||
if (!oldEnabled && Ch[1].Enabled) ExecuteImmediate(1);
|
||||
return;
|
||||
}
|
||||
else if (addr >= 0x40000C8 && addr <= 0x40000D3)
|
||||
{
|
||||
bool oldEnabled = Ch[2].Enabled;
|
||||
Ch[2].WriteHwio8(addr - 0x40000C8, val);
|
||||
if (!oldEnabled && Ch[2].Enabled) ExecuteImmediate(2);
|
||||
return;
|
||||
}
|
||||
else if (addr >= 0x40000D4 && addr <= 0x40000DF)
|
||||
{
|
||||
bool oldEnabled = Ch[3].Enabled;
|
||||
Ch[3].WriteHwio8(addr - 0x40000D4, val);
|
||||
if (!oldEnabled && Ch[3].Enabled) ExecuteImmediate(3);
|
||||
return;
|
||||
}
|
||||
else if (addr >= 0x40000E0 && addr <= 0x40000EF)
|
||||
{
|
||||
DmaFill[addr & 0xF] = val;
|
||||
return;
|
||||
}
|
||||
throw new Exception("This shouldn't happen.");
|
||||
}
|
||||
|
||||
public void ExecuteDma(DmaChannelNds c, uint ci)
|
||||
{
|
||||
|
||||
DmaLock = true;
|
||||
|
||||
// Console.WriteLine("NDS: Executing DMA");
|
||||
// Console.WriteLine("Source: " + Util.Hex(c.DmaSource, 8));
|
||||
// Console.WriteLine("Dest: " + Util.Hex(c.DmaDest, 8));
|
||||
// Console.WriteLine("Length: " + c.DmaLength);
|
||||
|
||||
if (!Nds7)
|
||||
{
|
||||
c.DmaSource &= 0x0FFFFFFF;
|
||||
c.DmaDest &= 0x0FFFFFFF;
|
||||
|
||||
// All NDS9 DMAs use 21-bit length
|
||||
c.DmaLength &= 0x1FFFFF;
|
||||
// Value of zero is treated as maximum length
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x200000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Least significant 28 (or 27????) bits
|
||||
c.DmaSource &= DmaSourceMask[ci];
|
||||
c.DmaDest &= DmaDestMask[ci];
|
||||
|
||||
if (ci == 3)
|
||||
{
|
||||
// DMA 3 is 16-bit length
|
||||
c.DmaLength &= 0xFFFF;
|
||||
// Value of zero is treated as maximum length
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x10000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// DMA 0-2 are 14-bit length
|
||||
c.DmaLength &= 0x3FFF;
|
||||
// Value of zero is treated as maximum length
|
||||
if (c.DmaLength == 0) c.DmaLength = 0x4000;
|
||||
}
|
||||
}
|
||||
|
||||
// if (c.DmaLength != 1 && ci == 3)
|
||||
// {
|
||||
// Console.WriteLine(((DmaStartTimingNds7)c.StartTiming).ToString());
|
||||
// Console.WriteLine("DMA length " + c.DmaLength);
|
||||
// }
|
||||
|
||||
// Console.WriteLine($"Starting DMA {ci}");
|
||||
// Console.WriteLine($"SRC: {Util.HexN(srcAddr, 7)}");
|
||||
// Console.WriteLine($"DEST: {Util.HexN(destAddr, 7)}");
|
||||
// Console.WriteLine($"LENGTH: {Util.HexN(c.DmaLength, 4)}");
|
||||
|
||||
int destOffsPerUnit;
|
||||
int sourceOffsPerUnit;
|
||||
if (c.TransferType)
|
||||
{
|
||||
switch (c.DestAddrCtrl)
|
||||
{
|
||||
case DmaDestAddrCtrl.Increment: destOffsPerUnit = +4; break;
|
||||
case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -4; break;
|
||||
case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +4; break;
|
||||
default: destOffsPerUnit = 0; break;
|
||||
}
|
||||
switch (c.SrcAddrCtrl)
|
||||
{
|
||||
case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +4; break;
|
||||
case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -4; break;
|
||||
default: sourceOffsPerUnit = 0; break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (c.DestAddrCtrl)
|
||||
{
|
||||
case DmaDestAddrCtrl.Increment: destOffsPerUnit = +2; break;
|
||||
case DmaDestAddrCtrl.Decrement: destOffsPerUnit = -2; break;
|
||||
case DmaDestAddrCtrl.IncrementReload: destOffsPerUnit = +2; break;
|
||||
default: destOffsPerUnit = 0; break;
|
||||
}
|
||||
switch (c.SrcAddrCtrl)
|
||||
{
|
||||
case DmaSrcAddrCtrl.Increment: sourceOffsPerUnit = +2; break;
|
||||
case DmaSrcAddrCtrl.Decrement: sourceOffsPerUnit = -2; break;
|
||||
default: sourceOffsPerUnit = 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
uint origLength = c.DmaLength;
|
||||
|
||||
// TODO: NDS DMA timings
|
||||
if (c.TransferType)
|
||||
{
|
||||
for (; c.DmaLength > 0; c.DmaLength--)
|
||||
{
|
||||
Mem.Write32(c.DmaDest & ~3u, Mem.Read32(c.DmaSource & ~3u));
|
||||
// Gba.Tick(Gba.Cpu.Timing32[(c.DmaSource >> 24) & 0xF]);
|
||||
// Gba.Tick(Gba.Cpu.Timing32[(c.DmaDest >> 24) & 0xF]);
|
||||
|
||||
c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest);
|
||||
c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (; c.DmaLength > 0; c.DmaLength--)
|
||||
{
|
||||
Mem.Write16(c.DmaDest & ~1u, Mem.Read16(c.DmaSource & ~1u));
|
||||
// Gba.Tick(Nds.Timing8And16[(c.DmaSource >> 24) & 0xF]);
|
||||
// Gba.Tick(Nds.Timing8And16[(c.DmaDest >> 24) & 0xF]);
|
||||
|
||||
c.DmaDest = (uint)(long)(destOffsPerUnit + c.DmaDest);
|
||||
c.DmaSource = (uint)(long)(sourceOffsPerUnit + c.DmaSource);
|
||||
}
|
||||
}
|
||||
|
||||
if (c.DestAddrCtrl == DmaDestAddrCtrl.IncrementReload)
|
||||
{
|
||||
if (c.Repeat)
|
||||
{
|
||||
c.DmaDest = c.DMADAD;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (c.FinishedIRQ)
|
||||
{
|
||||
HwControl.FlagInterrupt((uint)InterruptNds.Dma0 + ci);
|
||||
}
|
||||
|
||||
DmaLock = false;
|
||||
}
|
||||
|
||||
public void ExecuteImmediate(uint ci)
|
||||
{
|
||||
DmaChannelNds c = Ch[ci];
|
||||
// Console.WriteLine($"NDS{(Nds9 ? "9" : "7")}: Ch{ci} immediate DMA from:{Hex(c.DMASAD, 8)} to:{Hex(c.DMADAD, 8)}");
|
||||
|
||||
if (c.Enabled && c.StartTiming == (byte)DmaStartTimingNds9.Immediately)
|
||||
{
|
||||
c.Disable();
|
||||
|
||||
ExecuteDma(c, ci);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Repeat(byte val)
|
||||
{
|
||||
bool executed = false;
|
||||
if (!DmaLock)
|
||||
{
|
||||
for (uint ci = 0; ci < 4; ci++)
|
||||
{
|
||||
DmaChannelNds c = Ch[ci];
|
||||
if (c.StartTiming == val)
|
||||
{
|
||||
executed = true;
|
||||
c.DmaLength = c.DMACNT_L;
|
||||
ExecuteDma(c, ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
return executed;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/DmaNds.cs.meta
Normal file
11
Assets/emulator/DmaNds.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de4ad34ffe4c2a5458453cc7c40a4065
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1186
Assets/emulator/GbAudio.cs
Normal file
1186
Assets/emulator/GbAudio.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/emulator/GbAudio.cs.meta
Normal file
11
Assets/emulator/GbAudio.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05f5a54c4470e584daa86a0c04ddd943
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
218
Assets/emulator/Gba.cs
Normal file
218
Assets/emulator/Gba.cs
Normal file
@ -0,0 +1,218 @@
|
||||
using System;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
|
||||
public unsafe sealed class Gba
|
||||
{
|
||||
public ProviderGba Provider;
|
||||
|
||||
public Scheduler Scheduler;
|
||||
|
||||
public AudioCallback AudioCallback;
|
||||
|
||||
public MemoryGba Mem;
|
||||
public Arm7 Cpu;
|
||||
public GbaAudio GbaAudio;
|
||||
public Keypad Keypad;
|
||||
public PpuGba Ppu;
|
||||
public HwControlGba HwControl;
|
||||
public DmaGba Dma;
|
||||
public Timers Timers;
|
||||
|
||||
public Gba(ProviderGba provider)
|
||||
{
|
||||
Provider = provider;
|
||||
|
||||
Scheduler = new Scheduler();
|
||||
|
||||
Mem = new MemoryGba(this, provider);
|
||||
GbaAudio = new GbaAudio(this, Scheduler);
|
||||
Ppu = new PpuGba(this, Scheduler);
|
||||
Keypad = new Keypad();
|
||||
Dma = new DmaGba(this);
|
||||
Timers = new Timers(GbaAudio, HwControl, Scheduler, false, true);
|
||||
HwControl = new HwControlGba(this);
|
||||
Cpu = new Arm7(StateChange, Mem, false, false, null);
|
||||
|
||||
Cpu.SetTimingsTable(
|
||||
Cpu.Timing8And16,
|
||||
1, // BIOS
|
||||
1, // Unused
|
||||
3, // EWRAM
|
||||
1, // IWRAM
|
||||
1, // I/O Registers
|
||||
1, // PPU Palettes
|
||||
1, // PPU VRAM
|
||||
1, // PPU OAM
|
||||
5, // Game Pak ROM/FlashROM
|
||||
5, // Game Pak ROM/FlashROM
|
||||
5, // Game Pak ROM/FlashROM
|
||||
5, // Game Pak ROM/FlashROM
|
||||
5, // Game Pak ROM/FlashROM
|
||||
5, // Game Pak ROM/FlashROM
|
||||
5, // Game Pak SRAM/Flash
|
||||
5 // Game Pak SRAM/Flash
|
||||
);
|
||||
Cpu.SetTimingsTable(
|
||||
Cpu.Timing32,
|
||||
1, // BIOS
|
||||
1, // Unused
|
||||
6, // EWRAM
|
||||
1, // IWRAM
|
||||
1, // I/O Registers
|
||||
2, // PPU Palettes
|
||||
2, // PPU VRAM
|
||||
1, // PPU OAM
|
||||
8, // Game Pak ROM/FlashROM
|
||||
8, // Game Pak ROM/FlashROM
|
||||
8, // Game Pak ROM/FlashROM
|
||||
8, // Game Pak ROM/FlashROM
|
||||
8, // Game Pak ROM/FlashROM
|
||||
8, // Game Pak ROM/FlashROM
|
||||
8, // Game Pak SRAM/Flash
|
||||
8 // Game Pak SRAM/Flash
|
||||
);
|
||||
|
||||
Cpu.SetTimingsTable(
|
||||
Cpu.Timing8And16InstrFetch,
|
||||
1, // BIOS
|
||||
1, // Unused
|
||||
3, // EWRAM
|
||||
1, // IWRAM
|
||||
1, // I/O Registers
|
||||
1, // PPU Palettes
|
||||
1, // PPU VRAM
|
||||
1, // PPU OAM
|
||||
// Compensate for no prefetch buffer 5 -> 2
|
||||
2, // Game Pak ROM/FlashROM
|
||||
2, // Game Pak ROM/FlashROM
|
||||
2, // Game Pak ROM/FlashROM
|
||||
2, // Game Pak ROM/FlashROM
|
||||
2, // Game Pak ROM/FlashROM
|
||||
2, // Game Pak ROM/FlashROM
|
||||
5, // Game Pak SRAM/Flash
|
||||
5 // Game Pak SRAM/Flash
|
||||
);
|
||||
|
||||
Cpu.SetTimingsTable(
|
||||
Cpu.Timing32InstrFetch,
|
||||
1, // BIOS
|
||||
1, // Unused
|
||||
6, // EWRAM
|
||||
1, // IWRAM
|
||||
1, // I/O Registers
|
||||
2, // PPU Palettes
|
||||
2, // PPU VRAM
|
||||
1, // PPU OAM
|
||||
// Compensate for no prefetch buffer 8 -> 4
|
||||
4, // Game Pak ROM/FlashROM
|
||||
4, // Game Pak ROM/FlashROM
|
||||
4, // Game Pak ROM/FlashROM
|
||||
4, // Game Pak ROM/FlashROM
|
||||
4, // Game Pak ROM/FlashROM
|
||||
4, // Game Pak ROM/FlashROM
|
||||
8, // Game Pak SRAM/Flash
|
||||
8 // Game Pak SRAM/Flash
|
||||
);
|
||||
|
||||
if (!provider.BootBios)
|
||||
{
|
||||
Cpu.SetModeReg(13, Arm7Mode.SVC, 0x03007FE0);
|
||||
Cpu.SetModeReg(13, Arm7Mode.IRQ, 0x03007FA0);
|
||||
Cpu.SetModeReg(13, Arm7Mode.USR, 0x03007F00);
|
||||
|
||||
// Default Stack Pointer
|
||||
Cpu.R[13] = Cpu.GetModeReg(13, Arm7Mode.USR);
|
||||
Cpu.R[15] = 0x08000000;
|
||||
}
|
||||
|
||||
AudioCallback = provider.AudioCallback;
|
||||
|
||||
Mem.InitPageTables();
|
||||
Cpu.InitFlushPipeline();
|
||||
|
||||
#if UNSAFE
|
||||
Console.WriteLine("Starting in memory UNSAFE mode");
|
||||
#else
|
||||
Console.WriteLine("Starting in memory SAFE mode");
|
||||
#endif
|
||||
}
|
||||
|
||||
public uint Step()
|
||||
{
|
||||
Cpu.CheckInterrupts();
|
||||
long beforeTicks = Scheduler.CurrentTicks;
|
||||
if (!Cpu.ThumbState)
|
||||
{
|
||||
Scheduler.CurrentTicks += Cpu.ExecuteArm();
|
||||
}
|
||||
else
|
||||
{
|
||||
Scheduler.CurrentTicks += Cpu.ExecuteThumb();
|
||||
}
|
||||
while (Scheduler.CurrentTicks >= Scheduler.NextEventTicks)
|
||||
{
|
||||
long current = Scheduler.CurrentTicks;
|
||||
long next = Scheduler.NextEventTicks;
|
||||
Scheduler.PopFirstEvent().Callback(current - next);
|
||||
}
|
||||
|
||||
return (uint)(Scheduler.CurrentTicks - beforeTicks);
|
||||
}
|
||||
|
||||
public void DoNothing(long cyclesLate) { }
|
||||
|
||||
public void StateChange()
|
||||
{
|
||||
Scheduler.AddEventRelative(SchedulerId.None, 0, DoNothing);
|
||||
}
|
||||
|
||||
public uint StateStep()
|
||||
{
|
||||
Cpu.CheckInterrupts();
|
||||
|
||||
long beforeTicks = Scheduler.CurrentTicks;
|
||||
if (!Cpu.ThumbState)
|
||||
{
|
||||
while (Scheduler.CurrentTicks < Scheduler.NextEventTicks)
|
||||
{
|
||||
Scheduler.CurrentTicks += Cpu.ExecuteArm();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (Scheduler.CurrentTicks < Scheduler.NextEventTicks)
|
||||
{
|
||||
Scheduler.CurrentTicks += Cpu.ExecuteThumb();
|
||||
}
|
||||
}
|
||||
|
||||
while (Scheduler.CurrentTicks >= Scheduler.NextEventTicks)
|
||||
{
|
||||
long current = Scheduler.CurrentTicks;
|
||||
long next = Scheduler.NextEventTicks;
|
||||
Scheduler.PopFirstEvent().Callback(current - next);
|
||||
}
|
||||
|
||||
// Return cycles executed
|
||||
return (uint)(Scheduler.CurrentTicks - beforeTicks);
|
||||
}
|
||||
|
||||
public void Tick(uint cycles)
|
||||
{
|
||||
Scheduler.CurrentTicks += cycles;
|
||||
}
|
||||
|
||||
public void HaltSkip(long cyclesLate)
|
||||
{
|
||||
long before = Scheduler.CurrentTicks;
|
||||
while (!HwControl.Available)
|
||||
{
|
||||
long ticksPassed = Scheduler.NextEventTicks - Scheduler.CurrentTicks;
|
||||
Scheduler.CurrentTicks = Scheduler.NextEventTicks;
|
||||
Scheduler.PopFirstEvent().Callback(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/Gba.cs.meta
Normal file
11
Assets/emulator/Gba.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c7b25fc293f60540a475ccb2f6357b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
483
Assets/emulator/GbaAudio.cs
Normal file
483
Assets/emulator/GbaAudio.cs
Normal file
@ -0,0 +1,483 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum ResamplingMode
|
||||
{
|
||||
None,
|
||||
Linear,
|
||||
Sinc,
|
||||
SincLowPass,
|
||||
}
|
||||
|
||||
public sealed class CircularBufferOverwriting<T>
|
||||
{
|
||||
public uint Size;
|
||||
public T[] Buffer;
|
||||
public uint ReadPos = 0;
|
||||
public uint WritePos = 0;
|
||||
|
||||
public CircularBufferOverwriting(uint size)
|
||||
{
|
||||
Size = size;
|
||||
Buffer = new T[Size];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Insert(T data)
|
||||
{
|
||||
Buffer[WritePos++] = data;
|
||||
|
||||
if (WritePos >= Size)
|
||||
{
|
||||
WritePos = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T Pop()
|
||||
{
|
||||
T data = Buffer[ReadPos++];
|
||||
|
||||
if (ReadPos >= Size)
|
||||
{
|
||||
ReadPos = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T Peek(int offset)
|
||||
{
|
||||
return Buffer[(ReadPos + offset) % Size];
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ReadPos = 0;
|
||||
WritePos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CircularBuffer<T>
|
||||
{
|
||||
public uint Size;
|
||||
public T[] Buffer;
|
||||
public T EmptyValue;
|
||||
public uint ReadPos = 0;
|
||||
public uint WritePos = 0;
|
||||
public uint Entries = 0;
|
||||
public uint TotalPops = 0;
|
||||
public uint EmptyPops = 0;
|
||||
public uint FullInserts = 0;
|
||||
public uint Collisions = 0;
|
||||
|
||||
public CircularBuffer(uint size, T emptyValue)
|
||||
{
|
||||
Size = size;
|
||||
Buffer = new T[Size];
|
||||
EmptyValue = emptyValue;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Insert(T data)
|
||||
{
|
||||
if (Entries < Size)
|
||||
{
|
||||
if (ReadPos == WritePos) Collisions++;
|
||||
Entries++;
|
||||
Buffer[WritePos++] = data;
|
||||
|
||||
if (WritePos >= Size)
|
||||
{
|
||||
WritePos = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FullInserts++;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T Pop()
|
||||
{
|
||||
T data;
|
||||
TotalPops++;
|
||||
if (Entries > 0)
|
||||
{
|
||||
Entries--;
|
||||
data = Buffer[ReadPos++];
|
||||
|
||||
if (ReadPos >= Size)
|
||||
{
|
||||
ReadPos = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmptyPops++;
|
||||
data = EmptyValue;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T Peek(int offset)
|
||||
{
|
||||
return Buffer[(ReadPos + offset) % Size];
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Entries = 0;
|
||||
ReadPos = 0;
|
||||
WritePos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GbaAudio
|
||||
{
|
||||
Gba Gba;
|
||||
Scheduler Scheduler;
|
||||
public GbaAudio(Gba gba, Scheduler scheduler)
|
||||
{
|
||||
Gba = gba;
|
||||
Scheduler = scheduler;
|
||||
|
||||
Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample, Sample);
|
||||
}
|
||||
|
||||
public GbAudio GbAudio = new GbAudio();
|
||||
|
||||
public bool DebugEnableA = true;
|
||||
public bool DebugEnableB = true;
|
||||
|
||||
public CircularBuffer<byte> A = new CircularBuffer<byte>(32, 0);
|
||||
public CircularBuffer<byte> B = new CircularBuffer<byte>(32, 0);
|
||||
|
||||
public CircularBufferOverwriting<short> VisBufA = new CircularBufferOverwriting<short>(1024);
|
||||
public CircularBufferOverwriting<short> VisBufB = new CircularBufferOverwriting<short>(1024);
|
||||
|
||||
public short CurrentValueA;
|
||||
public short CurrentValueB;
|
||||
|
||||
public short PreviousValueA;
|
||||
public short PreviousValueB;
|
||||
|
||||
public long LastSampleTimeA;
|
||||
public long LastSampleTimeB;
|
||||
|
||||
public long IntervalA = 1;
|
||||
public long IntervalB = 1;
|
||||
|
||||
uint BiasLevel = 0x100;
|
||||
uint AmplitudeRes;
|
||||
|
||||
// SOUNDCNT_H
|
||||
uint SoundVolume = 0; // 0-1
|
||||
byte DmaSoundAVolume = 0; // 2
|
||||
byte DmaSoundBVolume = 0; // 3
|
||||
|
||||
bool DmaSoundAEnableRight = false; // 8
|
||||
bool DmaSoundAEnableLeft = false; // 9
|
||||
bool DmaSoundATimerSelect = false; // 10
|
||||
|
||||
bool DmaSoundBEnableRight = false; // 11
|
||||
bool DmaSoundBEnableLeft = false; // 12
|
||||
bool DmaSoundBTimerSelect = false; // 13
|
||||
|
||||
bool MasterEnable = false;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000082: // SOUNDCNT_H B0
|
||||
val |= (byte)((SoundVolume >> 0) & 0b11); // 0-1
|
||||
val |= (byte)(DmaSoundAVolume << 2); // 2
|
||||
val |= (byte)(DmaSoundBVolume << 3); // 3
|
||||
break;
|
||||
case 0x4000083: // SOUNDCNT_H B1
|
||||
if (DmaSoundAEnableRight) val = BitSet(val, 8 - 8); // 8
|
||||
if (DmaSoundAEnableLeft) val = BitSet(val, 9 - 8); // 9
|
||||
if (DmaSoundATimerSelect) val = BitSet(val, 10 - 8); // 10
|
||||
if (DmaSoundBEnableRight) val = BitSet(val, 12 - 8); // 12
|
||||
if (DmaSoundBEnableLeft) val = BitSet(val, 13 - 8); // 13
|
||||
if (DmaSoundBTimerSelect) val = BitSet(val, 14 - 8); // 14
|
||||
break;
|
||||
|
||||
// Special case, because SOUNDCNT_X contains both GB Audio status and GBA audio status
|
||||
case 0x4000084:
|
||||
// NR52
|
||||
byte i = 0;
|
||||
i |= 0b01110000;
|
||||
if (GbAudio.noise_enabled && GbAudio.noise_dacEnabled) i |= (byte)BIT_3;
|
||||
if (GbAudio.wave_enabled && GbAudio.wave_dacEnabled) i |= (byte)BIT_2;
|
||||
if (GbAudio.pulse2_enabled && GbAudio.pulse2_dacEnabled) i |= (byte)BIT_1;
|
||||
if (GbAudio.pulse1_enabled && GbAudio.pulse1_dacEnabled) i |= (byte)BIT_0;
|
||||
|
||||
if (MasterEnable) i |= (byte)BIT_7;
|
||||
return i;
|
||||
|
||||
case 0x4000088: // SOUNDBIAS B0
|
||||
val |= (byte)(BiasLevel << 1);
|
||||
break;
|
||||
case 0x4000089: // SOUNDBIAS B1
|
||||
val |= (byte)(BiasLevel >> 7);
|
||||
val |= (byte)(AmplitudeRes << 6);
|
||||
break;
|
||||
}
|
||||
|
||||
if (addr >= 0x4000060 && addr <= 0x4000084)
|
||||
{
|
||||
// GB Registers
|
||||
val = GbAudio.ReadHwio8(addr & 0xFF);
|
||||
}
|
||||
else if (addr >= 0x4000090 && addr <= 0x400009F)
|
||||
{
|
||||
// Wave RAM
|
||||
val = GbAudio.ReadHwio8(addr & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
if (addr >= 0x4000060 && addr <= 0x400009F)
|
||||
{
|
||||
GbAudio.WriteHwio8(addr & 0xFF, val);
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000082: // SOUNDCNT_H B0
|
||||
SoundVolume = (uint)(val & 0b11); // 0-1
|
||||
DmaSoundAVolume = (byte)((val >> 2) & 1); // 2
|
||||
DmaSoundBVolume = (byte)((val >> 3) & 1); // 3
|
||||
break;
|
||||
case 0x4000083: // SOUNDCNT_H B1
|
||||
DmaSoundAEnableRight = BitTest(val, 8 - 8); // 8
|
||||
DmaSoundAEnableLeft = BitTest(val, 9 - 8); // 9
|
||||
DmaSoundATimerSelect = BitTest(val, 10 - 8); // 10
|
||||
if (BitTest(val, 11 - 8)) A.Reset();
|
||||
DmaSoundBEnableRight = BitTest(val, 12 - 8); // 12
|
||||
DmaSoundBEnableLeft = BitTest(val, 13 - 8); // 13
|
||||
DmaSoundBTimerSelect = BitTest(val, 14 - 8); // 14
|
||||
if (BitTest(val, 15 - 8)) B.Reset();
|
||||
break;
|
||||
case 0x4000084: // SOUNDCNT_X
|
||||
MasterEnable = BitTest(val, 7);
|
||||
break;
|
||||
case 0x4000088: // SOUNDBIAS B0
|
||||
BiasLevel &= 0b110000000;
|
||||
BiasLevel |= (uint)((val >> 1) & 0b1111111);
|
||||
break;
|
||||
case 0x4000089: // SOUNDBIAS B1
|
||||
BiasLevel &= 0b001111111;
|
||||
BiasLevel |= (uint)((val & 0b11) << 7);
|
||||
|
||||
AmplitudeRes &= 0;
|
||||
AmplitudeRes |= (uint)((val >> 6) & 0b11);
|
||||
break;
|
||||
|
||||
case 0x40000A0:
|
||||
case 0x40000A1:
|
||||
case 0x40000A2:
|
||||
case 0x40000A3:
|
||||
// Gba.Arm7.Error("FIFO Insert");
|
||||
A.Insert(val);
|
||||
break;
|
||||
|
||||
case 0x40000A4:
|
||||
case 0x40000A5:
|
||||
case 0x40000A6:
|
||||
case 0x40000A7:
|
||||
// Gba.Arm7.Error("FIFO Insert");
|
||||
B.Insert(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CollectSamples = true;
|
||||
|
||||
public bool EnablePsg = true;
|
||||
public bool EnableFifo = true;
|
||||
|
||||
public BlipBuf BlipBuf = new BlipBuf(32, true, 2);
|
||||
public ResamplingMode ResamplingMode = ResamplingMode.Sinc;
|
||||
public const int SampleRate = 32768;
|
||||
const int CyclesPerSample = 16777216 / SampleRate;
|
||||
// public CircularBuffer<short> SampleBuffer = new CircularBuffer<short>(32768, 0);
|
||||
public const uint SampleBufferMax = 256;
|
||||
public short[] SampleBuffer = new short[SampleBufferMax];
|
||||
public uint SampleBufferPos = 0;
|
||||
public bool AudioReady;
|
||||
|
||||
public uint VisSamplingTimer = 0;
|
||||
|
||||
public void Sample(long cyclesLate)
|
||||
{
|
||||
GbAudio.Tick(CyclesPerSample / 4); // convert to GB sample rate
|
||||
|
||||
BlipBuf.ReadOutSample();
|
||||
|
||||
double fifoASinc = BlipBuf.CurrentValL;
|
||||
double fifoBSinc = BlipBuf.CurrentValR;
|
||||
|
||||
short fifoA = 0;
|
||||
short fifoB = 0;
|
||||
short psgA = 0;
|
||||
short psgB = 0;
|
||||
|
||||
if (MasterEnable)
|
||||
{
|
||||
if (EnablePsg)
|
||||
{
|
||||
psgA += GbAudio.Out1;
|
||||
psgB += GbAudio.Out2;
|
||||
}
|
||||
if (EnableFifo)
|
||||
{
|
||||
switch (ResamplingMode)
|
||||
{
|
||||
case ResamplingMode.None:
|
||||
if (DebugEnableA)
|
||||
{
|
||||
if (DmaSoundAEnableLeft) fifoA += CurrentValueA;
|
||||
if (DmaSoundAEnableRight) fifoB += CurrentValueA;
|
||||
}
|
||||
if (DebugEnableB)
|
||||
{
|
||||
if (DmaSoundBEnableLeft) fifoA += CurrentValueB;
|
||||
if (DmaSoundBEnableRight) fifoB += CurrentValueB;
|
||||
}
|
||||
break;
|
||||
case ResamplingMode.Linear:
|
||||
long current = Scheduler.CurrentTicks - cyclesLate;
|
||||
|
||||
if (DebugEnableA)
|
||||
{
|
||||
double ratio = (current - LastSampleTimeA) / (double)IntervalA;
|
||||
double valDouble = (PreviousValueA + ratio * (double)(CurrentValueA - PreviousValueA));
|
||||
short val = (short)valDouble;
|
||||
|
||||
if (DmaSoundAEnableLeft) fifoA += val;
|
||||
if (DmaSoundAEnableRight) fifoB += val;
|
||||
}
|
||||
if (DebugEnableB)
|
||||
{
|
||||
double ratio = (current - LastSampleTimeB) / (double)IntervalB;
|
||||
double valDouble = (PreviousValueB + ratio * (double)(CurrentValueB - PreviousValueB));
|
||||
short val = (short)valDouble;
|
||||
|
||||
if (DmaSoundBEnableLeft) fifoA += val;
|
||||
if (DmaSoundBEnableRight) fifoB += val;
|
||||
}
|
||||
break;
|
||||
case ResamplingMode.Sinc:
|
||||
case ResamplingMode.SincLowPass:
|
||||
if (DebugEnableA)
|
||||
{
|
||||
if (DmaSoundAEnableLeft) fifoA += (short)(fifoASinc);
|
||||
if (DmaSoundAEnableRight) fifoB += (short)(fifoASinc);
|
||||
}
|
||||
if (DebugEnableB)
|
||||
{
|
||||
if (DmaSoundBEnableLeft) fifoA += (short)(fifoBSinc);
|
||||
if (DmaSoundBEnableRight) fifoB += (short)(fifoBSinc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (++VisSamplingTimer >= 4)
|
||||
{
|
||||
VisSamplingTimer = 0;
|
||||
|
||||
VisBufA.Insert(CurrentValueA);
|
||||
VisBufB.Insert(CurrentValueB);
|
||||
}
|
||||
|
||||
SampleBuffer[SampleBufferPos++] = (short)((fifoA + psgA) * 64);
|
||||
SampleBuffer[SampleBufferPos++] = (short)((fifoB + psgB) * 64);
|
||||
|
||||
if (SampleBufferPos >= SampleBufferMax)
|
||||
{
|
||||
SampleBufferPos = 0;
|
||||
|
||||
Gba.AudioCallback(SampleBuffer);
|
||||
}
|
||||
|
||||
Scheduler.AddEventRelative(SchedulerId.ApuSample, CyclesPerSample - cyclesLate, Sample);
|
||||
}
|
||||
|
||||
public void TimerOverflowFifoA(long cyclesLate, uint timerId)
|
||||
{
|
||||
LastSampleTimeA = Scheduler.CurrentTicks - cyclesLate;
|
||||
PreviousValueA = CurrentValueA;
|
||||
IntervalA = Gba.Timers.T[timerId].Interval;
|
||||
|
||||
CurrentValueA = (short)((sbyte)A.Pop() << DmaSoundAVolume);
|
||||
|
||||
BlipBuf.SetValue(0, (float)LastSampleTimeA / CyclesPerSample, (float)CurrentValueA, 0);
|
||||
|
||||
if (A.Entries <= 16)
|
||||
{
|
||||
Gba.Dma.RepeatFifoA();
|
||||
}
|
||||
}
|
||||
public void TimerOverflowFifoB(long cyclesLate, uint timerId)
|
||||
{
|
||||
LastSampleTimeB = Scheduler.CurrentTicks - cyclesLate;
|
||||
PreviousValueB = CurrentValueB;
|
||||
IntervalB = Gba.Timers.T[timerId].Interval;
|
||||
|
||||
CurrentValueB = (short)((sbyte)B.Pop() << DmaSoundBVolume);
|
||||
|
||||
BlipBuf.SetValue(1, (float)LastSampleTimeB / CyclesPerSample, 0, (float)CurrentValueB);
|
||||
|
||||
if (B.Entries <= 16)
|
||||
{
|
||||
Gba.Dma.RepeatFifoB();
|
||||
}
|
||||
}
|
||||
|
||||
// Called when Timer 0 or 1 overflows.
|
||||
public void TimerOverflow(long cyclesLate, uint timerId)
|
||||
{
|
||||
if (timerId == 0)
|
||||
{
|
||||
if (!DmaSoundATimerSelect)
|
||||
{
|
||||
TimerOverflowFifoA(cyclesLate, timerId);
|
||||
}
|
||||
if (!DmaSoundBTimerSelect)
|
||||
{
|
||||
TimerOverflowFifoB(cyclesLate, timerId);
|
||||
}
|
||||
}
|
||||
else if (timerId == 1)
|
||||
{
|
||||
if (DmaSoundATimerSelect)
|
||||
{
|
||||
TimerOverflowFifoA(cyclesLate, timerId);
|
||||
}
|
||||
if (DmaSoundBTimerSelect)
|
||||
{
|
||||
TimerOverflowFifoB(cyclesLate, timerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/GbaAudio.cs.meta
Normal file
11
Assets/emulator/GbaAudio.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eda7cec1bdd2dee48802bc4b1456d549
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
14
Assets/emulator/HwControl.cs
Normal file
14
Assets/emulator/HwControl.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public abstract class HwControl
|
||||
{
|
||||
public bool IME;
|
||||
|
||||
public uint IE;
|
||||
public uint IF;
|
||||
|
||||
public bool Available;
|
||||
|
||||
public abstract void FlagInterrupt(uint interruptFlag);
|
||||
}
|
||||
}
|
11
Assets/emulator/HwControl.cs.meta
Normal file
11
Assets/emulator/HwControl.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c64a99535514fa649aa2a79581ea1781
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
109
Assets/emulator/HwControlGba.cs
Normal file
109
Assets/emulator/HwControlGba.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum InterruptGba
|
||||
{
|
||||
VBlank = 0,
|
||||
HBlank = 1,
|
||||
VCounterMatch = 2,
|
||||
Timer0Overflow = 3,
|
||||
Timer1Overflow = 4,
|
||||
Timer2Overflow = 5,
|
||||
Timer3Overflow = 6,
|
||||
Serial = 7,
|
||||
Dma0 = 8,
|
||||
Dma1 = 9,
|
||||
Dma2 = 10,
|
||||
Dma3 = 11,
|
||||
Keypad = 12,
|
||||
GamePak = 13,
|
||||
}
|
||||
|
||||
public sealed class HwControlGba : HwControl
|
||||
{
|
||||
Gba Gba;
|
||||
|
||||
public HwControlGba(Gba gba)
|
||||
{
|
||||
Gba = gba;
|
||||
}
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000200: // IE B0
|
||||
return (byte)(IE >> 0);
|
||||
case 0x4000201: // IE B1
|
||||
return (byte)(IE >> 8);
|
||||
|
||||
case 0x4000202: // IF B0
|
||||
return (byte)(IF >> 0);
|
||||
case 0x4000203: // IF B1
|
||||
return (byte)(IF >> 8);
|
||||
|
||||
case 0x4000208: // IME
|
||||
if (IME) val = BitSet(val, 0);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000200: // IE B0
|
||||
IE &= 0x3F00;
|
||||
IE |= (ushort)((ushort)val << 0);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000201: // IE B1
|
||||
IE &= 0x00FF;
|
||||
IE |= (ushort)((val & 0x3F) << 8);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
|
||||
case 0x4000202: // IF B0
|
||||
IF &= (ushort)(~((ushort)val << 0));
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000203: // IF B1
|
||||
IF &= (ushort)(~((val & 0x3F) << 8));
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
|
||||
case 0x4000208: // IME
|
||||
IME = BitTest(val, 0);
|
||||
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
|
||||
case 0x4000301: // HALTCNT
|
||||
if (BitTest(val, 7))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
Gba.Scheduler.AddEventRelative(SchedulerId.HaltSkip, 0, Gba.HaltSkip);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlagInterrupt(uint i)
|
||||
{
|
||||
IF |= (ushort)(1 << (int)i);
|
||||
CheckAndFireInterrupts();
|
||||
}
|
||||
|
||||
public void CheckAndFireInterrupts()
|
||||
{
|
||||
Available = (IE & IF & 0x3FFF) != 0;
|
||||
Gba.Cpu.FlagInterrupt = Available && IME;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/HwControlGba.cs.meta
Normal file
11
Assets/emulator/HwControlGba.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 654dd2b57b6345649802e6a16a013537
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
150
Assets/emulator/HwControlNds.cs
Normal file
150
Assets/emulator/HwControlNds.cs
Normal file
@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum InterruptNds
|
||||
{
|
||||
VBlank = 0,
|
||||
HBlank = 1,
|
||||
VCounterMatch = 2,
|
||||
Timer0Overflow = 3,
|
||||
Timer1Overflow = 4,
|
||||
Timer2Overflow = 5,
|
||||
Timer3Overflow = 6,
|
||||
Rtc = 7,
|
||||
Dma0 = 8,
|
||||
Dma1 = 9,
|
||||
Dma2 = 10,
|
||||
Dma3 = 11,
|
||||
Keypad = 12,
|
||||
GamePak = 13,
|
||||
// 14, 15, unused
|
||||
IpcSync = 16,
|
||||
IpcSendFifoEmpty = 17,
|
||||
IpcRecvFifoPending = 18,
|
||||
Slot1DataTransferComplete = 19,
|
||||
Slot1rq = 20,
|
||||
GeometryFifo = 21, // ARM9 only
|
||||
ScreenUnfold = 22, // ARM7 only
|
||||
SpiBus = 23, // ARM7 only
|
||||
Wifi = 24, // ARM7 only
|
||||
}
|
||||
|
||||
public sealed class HwControlNds : HwControl
|
||||
{
|
||||
Arm7 Cpu;
|
||||
|
||||
public HwControlNds(Arm7 cpu)
|
||||
{
|
||||
Cpu = cpu;
|
||||
}
|
||||
|
||||
public byte Postflg; // POSTFLG
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000208: // IME
|
||||
if (IME) val = BitSet(val, 0);
|
||||
break;
|
||||
|
||||
case 0x4000210: // IE B0
|
||||
return (byte)(IE >> 0);
|
||||
case 0x4000211: // IE B1
|
||||
return (byte)(IE >> 8);
|
||||
case 0x4000212: // IE B2
|
||||
return (byte)(IE >> 16);
|
||||
case 0x4000213: // IE B3
|
||||
return (byte)(IE >> 24);
|
||||
|
||||
case 0x4000214: // IF B0
|
||||
return (byte)(IF >> 0);
|
||||
case 0x4000215: // IF B1
|
||||
return (byte)(IF >> 8);
|
||||
case 0x4000216: // IF B2
|
||||
return (byte)(IF >> 16);
|
||||
case 0x4000217: // IF B3
|
||||
return (byte)(IF >> 24);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000208: // IME
|
||||
IME = BitTest(val, 0);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
|
||||
case 0x4000210: // IE B0
|
||||
IE &= 0xFFFFFF00;
|
||||
IE |= (uint)((uint)val << 0);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000211: // IE B1
|
||||
IE &= 0xFFFF00FF;
|
||||
IE |= (uint)((uint)val << 8);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000212: // IE B2
|
||||
IE &= 0xFF00FFFF;
|
||||
IE |= (uint)((uint)val << 16);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000213: // IE B3
|
||||
IE &= 0x00FFFFFF;
|
||||
IE |= (uint)((uint)val << 24);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
|
||||
case 0x4000214: // IF B0
|
||||
IF &= ~(uint)((uint)val << 0);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000215: // IF B1
|
||||
IF &= ~(uint)((uint)val << 8);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000216: // IF B2
|
||||
IF &= ~(uint)((uint)val << 16);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
case 0x4000217: // IF B3
|
||||
IF &= ~(uint)((uint)val << 24);
|
||||
CheckAndFireInterrupts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlagInterrupt(uint i)
|
||||
{
|
||||
IF |= (uint)(1 << (int)i);
|
||||
CheckAndFireInterrupts();
|
||||
}
|
||||
|
||||
public void CheckAndFireInterrupts()
|
||||
{
|
||||
Available = (IE & IF & 0xFFFFFFFF) != 0;
|
||||
Cpu.FlagInterrupt = Available && IME;
|
||||
if (Cpu.Armv5)
|
||||
{
|
||||
if (Available && IME)
|
||||
{
|
||||
Cpu.Halted = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Available)
|
||||
{
|
||||
Cpu.Halted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/HwControlNds.cs.meta
Normal file
11
Assets/emulator/HwControlNds.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 423f62bf81dca7648b5974281bffb372
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
207
Assets/emulator/Ipc.cs
Normal file
207
Assets/emulator/Ipc.cs
Normal file
@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed class Ipc
|
||||
{
|
||||
Nds Nds;
|
||||
byte Id;
|
||||
|
||||
public Ipc(Nds nds, byte id)
|
||||
{
|
||||
Nds = nds;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public CircularBuffer<uint> RecvFifo = new CircularBuffer<uint>(16, 0);
|
||||
public uint LastSendValue;
|
||||
public uint LastRecvValue;
|
||||
|
||||
public bool SendFifoEmptyIrqLevel;
|
||||
public bool RecvFifoPendingIrqLevel;
|
||||
|
||||
public byte IpcSyncDataOut;
|
||||
|
||||
// IPCSYNC
|
||||
public bool EnableRemoteIrq;
|
||||
|
||||
// IPCFIFOCNT
|
||||
public bool EnableSendFifoEmptyIrq;
|
||||
public bool EnableRecvFifoPendingIrq;
|
||||
|
||||
public bool FifoError;
|
||||
public bool EnableFifos;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000180: // IPCSYNC B0
|
||||
val |= GetRemote().IpcSyncDataOut;
|
||||
break;
|
||||
case 0x4000181: // IPCSYNC B1
|
||||
val |= IpcSyncDataOut;
|
||||
|
||||
if (EnableRemoteIrq) val = BitSet(val, 14 - 8);
|
||||
break;
|
||||
|
||||
case 0x4000184: // IPCFIFOCNT B0
|
||||
if (GetRemote().RecvFifo.Entries == 0) val = BitSet(val, 0); // Send FIFO empty
|
||||
if (GetRemote().RecvFifo.Entries == 16) val = BitSet(val, 1); // Send FIFO full
|
||||
if (EnableSendFifoEmptyIrq) val = BitSet(val, 2);
|
||||
CheckSendFifoEmptyIrq("IPCFIFOCNT bit enable");
|
||||
break;
|
||||
case 0x4000185: // IPCFIFOCNT B1
|
||||
if (RecvFifo.Entries == 0) val = BitSet(val, 0); // Receive FIFO empty
|
||||
if (RecvFifo.Entries == 16) val = BitSet(val, 1); // Receive FIFO full
|
||||
if (EnableRecvFifoPendingIrq) val = BitSet(val, 2);
|
||||
CheckRecvFifoPendingIrq("IPCFIFOCNT bit enable");
|
||||
|
||||
if (FifoError) val = BitSet(val, 6);
|
||||
if (EnableFifos) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4100000: // IPCFIFORECV B0
|
||||
if (RecvFifo.Entries > 0)
|
||||
{
|
||||
if (EnableFifos)
|
||||
{
|
||||
LastRecvValue = RecvFifo.Pop();
|
||||
GetRemote().CheckSendFifoEmptyIrq("remote pop");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FifoError = true;
|
||||
}
|
||||
val = GetByteIn(LastRecvValue, addr & 3);
|
||||
break;
|
||||
case 0x4100001: // IPCFIFORECV B1
|
||||
case 0x4100002: // IPCFIFORECV B2
|
||||
case 0x4100003: // IPCFIFORECV B3
|
||||
val = GetByteIn(LastRecvValue, addr & 3);
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000180: // IPCSYNC B0
|
||||
break;
|
||||
case 0x4000181: // IPCSYNC B1
|
||||
IpcSyncDataOut = (byte)(val & 0xF);
|
||||
|
||||
// send IRQ to remote
|
||||
if (BitTest(val, 13 - 8) && GetRemote().EnableRemoteIrq)
|
||||
{
|
||||
// Console.WriteLine($"[{Id}] Sending IRQ");
|
||||
switch (Id)
|
||||
{
|
||||
case 0:
|
||||
Nds.HwControl7.FlagInterrupt((uint)InterruptNds.IpcSync);
|
||||
break;
|
||||
case 1:
|
||||
Nds.HwControl9.FlagInterrupt((uint)InterruptNds.IpcSync);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EnableRemoteIrq = BitTest(val, 14 - 8);
|
||||
break;
|
||||
|
||||
case 0x4000184: // IPCFIFOCNT B0
|
||||
EnableSendFifoEmptyIrq = BitTest(val, 2);
|
||||
if (BitTest(val, 3))
|
||||
{
|
||||
GetRemote().RecvFifo.Reset();
|
||||
}
|
||||
break;
|
||||
case 0x4000185: // IPCFIFOCNT B1
|
||||
EnableRecvFifoPendingIrq = BitTest(val, 2);
|
||||
|
||||
if (BitTest(val, 6))
|
||||
{
|
||||
FifoError = false;
|
||||
}
|
||||
EnableFifos = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000188: // IPCFIFOSEND B0
|
||||
case 0x4000189: // IPCFIFOSEND B1
|
||||
case 0x400018A: // IPCFIFOSEND B2
|
||||
LastSendValue = SetByteIn(LastSendValue, val, addr & 3);
|
||||
break;
|
||||
case 0x400018B: // IPCFIFOSEND B3
|
||||
LastSendValue = SetByteIn(LastSendValue, val, addr & 3);
|
||||
if (EnableFifos)
|
||||
{
|
||||
GetRemote().RecvFifo.Insert(LastSendValue);
|
||||
|
||||
bool eligible = true;
|
||||
|
||||
// if ((LastSendValue >> 28) == 0x8) eligible = false;
|
||||
// if ((LastSendValue >> 28) == 0x4) eligible = false;
|
||||
// if ((LastSendValue >> 28) == 0xC) eligible = false;
|
||||
// // if ((LastSendValue >> 28) == 0x0) eligible = false;
|
||||
|
||||
// if (eligible)
|
||||
// {
|
||||
// if (Id == 0) Console.WriteLine("ARM9 to ARM7 " + Util.Hex(LastSendValue, 8));
|
||||
// // else Console.WriteLine("ARM7 to ARM9 " + Util.Hex(LastSendValue, 8));
|
||||
// }
|
||||
|
||||
unsafe
|
||||
{
|
||||
GetRemote().CheckRecvFifoPendingIrq("remote insert R15: " + Util.Hex(Nds.Cpu7.R[15], 8));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Ipc GetRemote()
|
||||
{
|
||||
return Nds.Ipcs[Id ^ 1];
|
||||
}
|
||||
|
||||
public void CheckSendFifoEmptyIrq(string from)
|
||||
{
|
||||
var prev = SendFifoEmptyIrqLevel;
|
||||
SendFifoEmptyIrqLevel = GetRemote().RecvFifo.Entries == 0 && EnableSendFifoEmptyIrq;
|
||||
if (!prev && SendFifoEmptyIrqLevel)
|
||||
{
|
||||
// Console.WriteLine($"Flagging ARM{(Id == 0 ? 7 : 9)} IPC Send FIFO Empty IRQ from " + from);
|
||||
FlagSourceInterrupt(InterruptNds.IpcSendFifoEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckRecvFifoPendingIrq(string from)
|
||||
{
|
||||
var prev = RecvFifoPendingIrqLevel;
|
||||
RecvFifoPendingIrqLevel = RecvFifo.Entries > 0 && EnableRecvFifoPendingIrq;
|
||||
if (!prev && RecvFifoPendingIrqLevel)
|
||||
{
|
||||
// Console.WriteLine($"Flagging ARM{(Id == 0 ? 7 : 9)} IPC Recv FIFO Pending Irq from " + from);
|
||||
FlagSourceInterrupt(InterruptNds.IpcRecvFifoPending);
|
||||
}
|
||||
}
|
||||
|
||||
public void FlagSourceInterrupt(InterruptNds interrupt)
|
||||
{
|
||||
switch (Id)
|
||||
{
|
||||
case 0:
|
||||
Nds.HwControl9.FlagInterrupt((uint)interrupt);
|
||||
break;
|
||||
case 1:
|
||||
Nds.HwControl7.FlagInterrupt((uint)interrupt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/Ipc.cs.meta
Normal file
11
Assets/emulator/Ipc.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84b3353082f9bee4f829c5b77d38c1fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
61
Assets/emulator/Keypad.cs
Normal file
61
Assets/emulator/Keypad.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using static OptimeGBA.Bits;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed class Keypad
|
||||
{
|
||||
public bool A;
|
||||
public bool B;
|
||||
public bool Start;
|
||||
public bool Select;
|
||||
public bool Right;
|
||||
public bool Left;
|
||||
public bool Up;
|
||||
public bool Down;
|
||||
public bool R;
|
||||
public bool L;
|
||||
|
||||
// DS Exclusive
|
||||
public bool X;
|
||||
public bool Y;
|
||||
public bool DebugButton;
|
||||
public bool Touch;
|
||||
public bool ScreensOpen = true; // DS folded
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000130: // KEYINPUT B0
|
||||
if (!A) val = BitSet(val, 0);
|
||||
if (!B) val = BitSet(val, 1);
|
||||
if (!Select) val = BitSet(val, 2);
|
||||
if (!Start) val = BitSet(val, 3);
|
||||
if (!Right) val = BitSet(val, 4);
|
||||
if (!Left) val = BitSet(val, 5);
|
||||
if (!Up) val = BitSet(val, 6);
|
||||
if (!Down) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x4000131: // KEYINPUT B1
|
||||
if (!R) val = BitSet(val, 8 - 8);
|
||||
if (!L) val = BitSet(val, 9 - 8);
|
||||
break;
|
||||
|
||||
case 0x4000136: // EXTKEYIN - ARM7 only
|
||||
if (!X) val = BitSet(val, 0);
|
||||
if (!Y) val = BitSet(val, 1);
|
||||
if (!DebugButton) val = BitSet(val, 3);
|
||||
if (!Touch) val = BitSet(val, 6);
|
||||
if (!ScreensOpen) val = BitSet(val, 7);
|
||||
// System.Console.WriteLine(Util.Hex(val, 2));
|
||||
break;
|
||||
case 0x4000137: // EXTKEYIN B1
|
||||
val = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/Keypad.cs.meta
Normal file
11
Assets/emulator/Keypad.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46a2eabfa7974ee478fa25cfdc9fa1f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
207
Assets/emulator/Memory.cs
Normal file
207
Assets/emulator/Memory.cs
Normal file
@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Concurrent;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.InteropServices;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public unsafe abstract class Memory
|
||||
{
|
||||
public SaveProvider SaveProvider;
|
||||
public SortedDictionary<uint, uint> HwioWriteLog = new SortedDictionary<uint, uint>();
|
||||
public SortedDictionary<uint, uint> HwioReadLog = new SortedDictionary<uint, uint>();
|
||||
public bool LogHwioAccesses = false;
|
||||
|
||||
public abstract void InitPageTable(byte*[] pageTable, uint[] maskTable, bool write);
|
||||
public const int PageSize = 1024;
|
||||
|
||||
public uint[] MemoryRegionMasks = new uint[1048576];
|
||||
|
||||
public byte[] EmptyPage = new byte[PageSize];
|
||||
public byte*[] PageTableRead = new byte*[1048576];
|
||||
public byte*[] PageTableWrite = new byte*[1048576];
|
||||
|
||||
public abstract byte Read8Unregistered(bool debug, uint addr);
|
||||
public abstract void Write8Unregistered(bool debug, uint addr, byte val);
|
||||
public abstract ushort Read16Unregistered(bool debug, uint addr);
|
||||
public abstract void Write16Unregistered(bool debug, uint addr, ushort val);
|
||||
public abstract uint Read32Unregistered(bool debug, uint addr);
|
||||
public abstract void Write32Unregistered(bool debug, uint addr, uint val);
|
||||
|
||||
public Dictionary<byte[], GCHandle> Handles = new Dictionary<byte[], GCHandle>();
|
||||
|
||||
public byte* TryPinByteArray(byte[] arr)
|
||||
{
|
||||
GCHandle handle;
|
||||
if (!Handles.TryGetValue(arr, out handle))
|
||||
{
|
||||
handle = GCHandle.Alloc(arr, GCHandleType.Pinned);
|
||||
Handles[arr] = handle;
|
||||
}
|
||||
|
||||
return (byte*)handle.AddrOfPinnedObject();
|
||||
}
|
||||
|
||||
public void UnpinByteArray(byte[] arr)
|
||||
{
|
||||
Handles[arr].Free();
|
||||
if (!Handles.Remove(arr))
|
||||
{
|
||||
throw new ArgumentException("Tried to unpin already unpinned array.");
|
||||
}
|
||||
}
|
||||
|
||||
public void InitPageTables()
|
||||
{
|
||||
InitPageTable(PageTableRead, MemoryRegionMasks, false);
|
||||
InitPageTable(PageTableWrite, MemoryRegionMasks, true);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint MaskAddress(uint addr)
|
||||
{
|
||||
return addr & MemoryRegionMasks[addr >> 12];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* ResolvePageRead(uint addr)
|
||||
{
|
||||
return PageTableRead[addr >> 12];
|
||||
}
|
||||
|
||||
public byte* ResolvePageWrite(uint addr)
|
||||
{
|
||||
return PageTableWrite[addr >> 12];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte Read8(uint addr)
|
||||
{
|
||||
var page = ResolvePageRead(addr);
|
||||
if (page != null)
|
||||
{
|
||||
return GetByte(page, MaskAddress(addr));
|
||||
}
|
||||
|
||||
return Read8Unregistered(false, addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ushort Read16(uint addr)
|
||||
{
|
||||
#if DEBUG
|
||||
if ((addr & 1) != 0)
|
||||
{
|
||||
Console.Error.WriteLine("Misaligned Read16! " + Util.HexN(addr, 8));
|
||||
}
|
||||
#endif
|
||||
|
||||
var page = ResolvePageRead(addr);
|
||||
if (page != null)
|
||||
{
|
||||
return GetUshort(page, MaskAddress(addr));
|
||||
}
|
||||
|
||||
return Read16Unregistered(false, addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ushort ReadDebug16(uint addr)
|
||||
{
|
||||
var page = ResolvePageRead(addr);
|
||||
if (page != null)
|
||||
{
|
||||
return GetUshort(page, MaskAddress(addr));
|
||||
}
|
||||
|
||||
return Read16Unregistered(true, addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint Read32(uint addr)
|
||||
{
|
||||
#if DEBUG
|
||||
if ((addr & 3) != 0)
|
||||
{
|
||||
Console.Error.WriteLine("Misaligned Read32! " + Util.HexN(addr, 8));
|
||||
}
|
||||
#endif
|
||||
|
||||
var page = ResolvePageRead(addr);
|
||||
if (page != null)
|
||||
{
|
||||
return GetUint(page, MaskAddress(addr));
|
||||
}
|
||||
|
||||
return Read32Unregistered(false, addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint ReadDebug32(uint addr)
|
||||
{
|
||||
var page = ResolvePageRead(addr);
|
||||
if (page != null)
|
||||
{
|
||||
return GetUint(page, MaskAddress(addr));
|
||||
}
|
||||
|
||||
return Read32Unregistered(true, addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write8(uint addr, byte val)
|
||||
{
|
||||
var page = ResolvePageWrite(addr);
|
||||
if (page != null)
|
||||
{
|
||||
SetByte(page, MaskAddress(addr), val);
|
||||
return;
|
||||
}
|
||||
|
||||
Write8Unregistered(false, addr, val);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write16(uint addr, ushort val)
|
||||
{
|
||||
#if DEBUG
|
||||
if ((addr & 1) != 0)
|
||||
{
|
||||
Console.Error.WriteLine("Misaligned Write16! " + Util.HexN(addr, 8));
|
||||
}
|
||||
#endif
|
||||
|
||||
var page = ResolvePageWrite(addr);
|
||||
if (page != null)
|
||||
{
|
||||
SetUshort(page, MaskAddress(addr), val);
|
||||
return;
|
||||
}
|
||||
|
||||
Write16Unregistered(false, addr, val);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Write32(uint addr, uint val)
|
||||
{
|
||||
#if DEBUG
|
||||
if ((addr & 3) != 0)
|
||||
{
|
||||
Console.Error.WriteLine("Misaligned Write32! " + Util.HexN(addr, 8));
|
||||
}
|
||||
#endif
|
||||
|
||||
var page = ResolvePageWrite(addr);
|
||||
if (page != null)
|
||||
{
|
||||
SetUint(page, MaskAddress(addr), val);
|
||||
return;
|
||||
}
|
||||
|
||||
Write32Unregistered(false, addr, val);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c63b68705a3d713459aaacd1a2506ed8
|
||||
guid: d3f8f528aeb42c246bc8da7f5e034c36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
157
Assets/emulator/MemoryControlNds.cs
Normal file
157
Assets/emulator/MemoryControlNds.cs
Normal file
@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public class MemoryControlNds
|
||||
{
|
||||
public byte SharedRamControl;
|
||||
|
||||
public byte[] VRAMCNT = new byte[9];
|
||||
public bool VramConfigDirty;
|
||||
|
||||
// EXMEMCNT
|
||||
public byte Slot2SramWaitArm9;
|
||||
public byte Slot2Rom0WaitArm9;
|
||||
public byte Slot2Rom1WaitArm9;
|
||||
public byte Slot2RomPhiPinOutArm9;
|
||||
public byte Slot2SramWaitArm7;
|
||||
public byte Slot2Rom0WaitArm7;
|
||||
public byte Slot2Rom1WaitArm7;
|
||||
public byte Slot2RomPhiPinOutArm7;
|
||||
|
||||
// Shared between 7/9 EXMEMCNT/EXMEMSTAT
|
||||
// true = ARM7
|
||||
public bool Nds7Slot2AccessRights;
|
||||
public bool Nds7Slot1AccessRights;
|
||||
public bool MainMemoryAccessPriority;
|
||||
|
||||
public byte ReadHwio8Nds9(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("read from exmemcnt b0");
|
||||
val |= (byte)((Slot2SramWaitArm9 & 0b11) << 0);
|
||||
val |= (byte)((Slot2Rom0WaitArm9 & 0b11) << 2);
|
||||
val |= (byte)((Slot2Rom1WaitArm9 & 0b1) << 4);
|
||||
val |= (byte)((Slot2RomPhiPinOutArm9 & 0b11) << 5);
|
||||
if (Nds7Slot2AccessRights) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x4000205:
|
||||
// Console.WriteLine("read from exmemcnt b1");
|
||||
if (Nds7Slot1AccessRights) val = BitSet(val, 3);
|
||||
if (MainMemoryAccessPriority) val = BitSet(val, 7);
|
||||
val = BitSet(val, 6);
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Nds9(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("write to exmemcnt b0");
|
||||
Slot2SramWaitArm9 = (byte)BitRange(val, 0, 1);
|
||||
Slot2Rom0WaitArm9 = (byte)BitRange(val, 2, 3);
|
||||
Slot2Rom1WaitArm9 = (byte)BitRange(val, 4, 4);
|
||||
Slot2RomPhiPinOutArm9 = (byte)BitRange(val, 5, 6);
|
||||
Nds7Slot2AccessRights = BitTest(val, 7);
|
||||
break;
|
||||
case 0x4000205:
|
||||
// Console.WriteLine("write to exmemcnt b1");
|
||||
Nds7Slot1AccessRights = BitTest(val, 3);
|
||||
MainMemoryAccessPriority = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000240: if (VRAMCNT[0] != val) VramConfigDirty = true; VRAMCNT[0] = val; break;
|
||||
case 0x4000241: if (VRAMCNT[1] != val) VramConfigDirty = true; VRAMCNT[1] = val; break;
|
||||
case 0x4000242: if (VRAMCNT[2] != val) VramConfigDirty = true; VRAMCNT[2] = val; break;
|
||||
case 0x4000243: if (VRAMCNT[3] != val) VramConfigDirty = true; VRAMCNT[3] = val; break;
|
||||
case 0x4000244: if (VRAMCNT[4] != val) VramConfigDirty = true; VRAMCNT[4] = val; break;
|
||||
case 0x4000245: if (VRAMCNT[5] != val) VramConfigDirty = true; VRAMCNT[5] = val; break;
|
||||
case 0x4000246: if (VRAMCNT[6] != val) VramConfigDirty = true; VRAMCNT[6] = val; break;
|
||||
case 0x4000248: if (VRAMCNT[7] != val) VramConfigDirty = true; VRAMCNT[7] = val; break;
|
||||
case 0x4000249: if (VRAMCNT[8] != val) VramConfigDirty = true; VRAMCNT[8] = val; break;
|
||||
|
||||
case 0x4000247:
|
||||
SharedRamControl = (byte)(val & 0b11);
|
||||
break;
|
||||
}
|
||||
|
||||
// if (VramEnabledAndSet(2, 2) || VramEnabledAndSet(3, 2))
|
||||
// {
|
||||
// throw new NotImplementedException("Implement mapping VRAM banks C and D to ARM7");
|
||||
// }
|
||||
}
|
||||
|
||||
public byte ReadHwio8Nds7(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("read from exmemstat b0");
|
||||
val |= (byte)((Slot2SramWaitArm7 & 0b11) << 0);
|
||||
val |= (byte)((Slot2Rom0WaitArm7 & 0b11) << 2);
|
||||
val |= (byte)((Slot2Rom1WaitArm7 & 0b1) << 4);
|
||||
val |= (byte)((Slot2RomPhiPinOutArm7 & 0b11) << 5);
|
||||
if (Nds7Slot2AccessRights) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x4000205:
|
||||
// Console.WriteLine("read from exmemstat b1");
|
||||
if (Nds7Slot1AccessRights) val = BitSet(val, 3);
|
||||
if (MainMemoryAccessPriority) val = BitSet(val, 7);
|
||||
val = BitSet(val, 6);
|
||||
break;
|
||||
|
||||
case 0x4000240:
|
||||
if (VramEnabledAndSet(2, 2)) val = BitSet(val, 0);
|
||||
if (VramEnabledAndSet(3, 2)) val = BitSet(val, 1);
|
||||
break;
|
||||
case 0x4000241:
|
||||
return SharedRamControl;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Nds7(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000204:
|
||||
// Console.WriteLine("write to exmemstat b0");
|
||||
Slot2SramWaitArm7 = (byte)BitRange(val, 0, 1);
|
||||
Slot2Rom0WaitArm7 = (byte)BitRange(val, 2, 3);
|
||||
Slot2Rom1WaitArm7 = (byte)BitRange(val, 4, 4);
|
||||
Slot2RomPhiPinOutArm7 = (byte)BitRange(val, 5, 6);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool VramEnabledAndSet(uint bank, uint mst)
|
||||
{
|
||||
uint vramcntMst = VRAMCNT[bank] & 0b111U;
|
||||
bool vramcntEnable = BitTest(VRAMCNT[bank], 7);
|
||||
|
||||
return vramcntEnable && vramcntMst == mst;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint GetOffset(uint bank)
|
||||
{
|
||||
return (uint)(VRAMCNT[bank] >> 3) & 0b11U;
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/MemoryControlNds.cs.meta
Normal file
11
Assets/emulator/MemoryControlNds.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5cce821ff321fe34ea2e0c5cbabdd74e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
438
Assets/emulator/MemoryGba.cs
Normal file
438
Assets/emulator/MemoryGba.cs
Normal file
@ -0,0 +1,438 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Concurrent;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.InteropServices;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed unsafe class MemoryGba : Memory
|
||||
{
|
||||
Gba Gba;
|
||||
|
||||
public MemoryGba(Gba gba, ProviderGba provider)
|
||||
{
|
||||
Gba = gba;
|
||||
|
||||
for (uint i = 0; i < MaxRomSize && i < provider.Rom.Length; i++)
|
||||
{
|
||||
Rom[i] = provider.Rom[i];
|
||||
}
|
||||
|
||||
for (uint i = 0; i < BiosSize && i < provider.Bios.Length; i++)
|
||||
{
|
||||
Bios[i] = provider.Bios[i];
|
||||
}
|
||||
|
||||
RomSize = (uint)provider.Rom.Length;
|
||||
|
||||
// Detect save type
|
||||
|
||||
string[] strings = {
|
||||
"NONE_LOLOLLEXTRATONOTMATCHRANDOMSTRINGS",
|
||||
"EEPROM_",
|
||||
"SRAM_",
|
||||
"FLASH_",
|
||||
"FLASH512_",
|
||||
"FLASH1M_",
|
||||
};
|
||||
uint matchedIndex = 0;
|
||||
|
||||
for (uint i = 0; i < strings.Length; i++)
|
||||
{
|
||||
char[] chars = strings[i].ToCharArray();
|
||||
|
||||
int stringLength = chars.Length;
|
||||
int matchLength = 0;
|
||||
for (uint j = 0; j < provider.Rom.Length; j++)
|
||||
{
|
||||
if (provider.Rom[j] == chars[matchLength])
|
||||
{
|
||||
matchLength++;
|
||||
if (matchLength >= chars.Length)
|
||||
{
|
||||
matchedIndex = i;
|
||||
goto breakOuterLoop;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
matchLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
breakOuterLoop:
|
||||
|
||||
Console.WriteLine($"Save Type: {strings[matchedIndex]}");
|
||||
|
||||
switch (matchedIndex)
|
||||
{
|
||||
case 0: SaveProvider = new NullSaveProvider(); break;
|
||||
case 1:
|
||||
SaveProvider = new Eeprom(Gba, EepromSize.Eeprom64k);
|
||||
if (RomSize < 16777216)
|
||||
{
|
||||
EepromThreshold = 0x1000000;
|
||||
}
|
||||
else
|
||||
{
|
||||
EepromThreshold = 0x1FFFF00;
|
||||
}
|
||||
Console.WriteLine("EEPROM Threshold: " + Util.Hex(EepromThreshold, 8));
|
||||
break;
|
||||
case 2: SaveProvider = new Sram(); break;
|
||||
case 3: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break;
|
||||
case 4: SaveProvider = new Flash(Gba, FlashSize.Flash512k); break;
|
||||
case 5: SaveProvider = new Flash(Gba, FlashSize.Flash1m); break;
|
||||
}
|
||||
}
|
||||
|
||||
public uint EepromThreshold = 0x2000000;
|
||||
|
||||
public const int BiosSize = 16384;
|
||||
public const int MaxRomSize = 67108864;
|
||||
public const int EwramSize = 262144;
|
||||
public const int IwramSize = 32768;
|
||||
public uint RomSize;
|
||||
|
||||
public byte[] Bios = new byte[BiosSize];
|
||||
public byte[] Rom = new byte[MaxRomSize];
|
||||
public byte[] Ewram = new byte[EwramSize];
|
||||
public byte[] Iwram = new byte[IwramSize];
|
||||
|
||||
public override void InitPageTable(byte*[] table, uint[] maskTable, bool write)
|
||||
{
|
||||
byte* bios = TryPinByteArray(Bios);
|
||||
byte* ewram = TryPinByteArray(Ewram);
|
||||
byte* iwram = TryPinByteArray(Iwram);
|
||||
byte* palettes = TryPinByteArray(Gba.Ppu.Renderer.Palettes);
|
||||
byte* vram = TryPinByteArray(Gba.Ppu.Vram);
|
||||
byte* emptyPage = TryPinByteArray(EmptyPage);
|
||||
byte* oam = TryPinByteArray(Gba.Ppu.Renderer.Oam);
|
||||
byte* rom = TryPinByteArray(Rom);
|
||||
|
||||
// 12 bits shaved off already, shave off another 12 to get 24
|
||||
for (uint i = 0; i < 1048576; i++)
|
||||
{
|
||||
uint addr = (uint)(i << 12);
|
||||
switch (i >> 12)
|
||||
{
|
||||
case 0x0: // BIOS
|
||||
if (!write)
|
||||
{
|
||||
table[i] = bios;
|
||||
}
|
||||
maskTable[i] = 0x00003FFF;
|
||||
break;
|
||||
case 0x2: // EWRAM
|
||||
table[i] = ewram;
|
||||
maskTable[i] = 0x0003FFFF;
|
||||
break;
|
||||
case 0x3: // IWRAM
|
||||
table[i] = iwram;
|
||||
maskTable[i] = 0x00007FFF;
|
||||
break;
|
||||
case 0x5: // Palettes
|
||||
if (!write)
|
||||
{
|
||||
table[i] = palettes;
|
||||
}
|
||||
maskTable[i] = 0x3FF;
|
||||
break;
|
||||
case 0x6: // PPU VRAM
|
||||
addr &= 0x1FFFF;
|
||||
if (addr < 0x18000)
|
||||
{
|
||||
table[i] = vram;
|
||||
}
|
||||
else
|
||||
{
|
||||
table[i] = emptyPage;
|
||||
}
|
||||
maskTable[i] = 0x0001FFFF; // VRAM
|
||||
break;
|
||||
case 0x7: // PPU OAM
|
||||
table[i] = oam;
|
||||
maskTable[i] = 0x000003FF;
|
||||
break;
|
||||
case 0x8: // Game Pak ROM/FlashROM
|
||||
case 0x9: // Game Pak ROM/FlashROM
|
||||
case 0xA: // Game Pak ROM/FlashROM
|
||||
case 0xB: // Game Pak ROM/FlashROM
|
||||
case 0xC: // Game Pak ROM/FlashROM
|
||||
if (!write)
|
||||
{
|
||||
table[i] = rom;
|
||||
}
|
||||
maskTable[i] = 0x01FFFFFF;
|
||||
break;
|
||||
case 0xD: // Game Pak ROM/FlashROM/EEPROM
|
||||
maskTable[i] = 0x01FFFFFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~MemoryGba()
|
||||
{
|
||||
Console.WriteLine("Cleaning up GBA memory...");
|
||||
UnpinByteArray(Bios);
|
||||
UnpinByteArray(Ewram);
|
||||
UnpinByteArray(Iwram);
|
||||
UnpinByteArray(Gba.Ppu.Renderer.Palettes);
|
||||
UnpinByteArray(Gba.Ppu.Vram);
|
||||
UnpinByteArray(EmptyPage);
|
||||
UnpinByteArray(Gba.Ppu.Renderer.Oam);
|
||||
UnpinByteArray(Rom);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override byte Read8Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x4: // I/O Registers
|
||||
// addr &= 0x400FFFF;
|
||||
return ReadHwio8(debug, addr);
|
||||
case 0xA: // ROM / EEPROM
|
||||
case 0xB: // ROM / EEPROM
|
||||
case 0xC: // ROM / EEPROM
|
||||
case 0xD: // ROM / EEPROM
|
||||
uint adjAddr = addr & 0x1FFFFFF;
|
||||
if (adjAddr >= EepromThreshold)
|
||||
{
|
||||
return SaveProvider.Read8(adjAddr);
|
||||
}
|
||||
|
||||
return GetByte(Rom, adjAddr);
|
||||
case 0xE: // Game Pak SRAM/Flash
|
||||
case 0xF: // Game Pak SRAM/Flash
|
||||
return SaveProvider.Read8(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override ushort Read16Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x4: // I/O Registers
|
||||
byte f0 = Read8Unregistered(debug, addr++);
|
||||
byte f1 = Read8Unregistered(debug, addr++);
|
||||
|
||||
ushort u16 = (ushort)((f1 << 8) | (f0 << 0));
|
||||
|
||||
return u16;
|
||||
case 0xA: // ROM / EEPROM
|
||||
case 0xB: // ROM / EEPROM
|
||||
case 0xC: // ROM / EEPROM
|
||||
case 0xD: // ROM / EEPROM
|
||||
uint adjAddr = addr & 0x1FFFFFF;
|
||||
if (adjAddr >= EepromThreshold)
|
||||
{
|
||||
return SaveProvider.Read8(adjAddr);
|
||||
}
|
||||
|
||||
return GetUshort(Rom, adjAddr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override uint Read32Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x4: // I/O Registers
|
||||
byte f0 = ReadHwio8(debug, addr++);
|
||||
byte f1 = ReadHwio8(debug, addr++);
|
||||
byte f2 = ReadHwio8(debug, addr++);
|
||||
byte f3 = ReadHwio8(debug, addr++);
|
||||
|
||||
uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0));
|
||||
|
||||
return u32;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Write8Unregistered(bool debug, uint addr, byte val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x4: // I/O Registers
|
||||
// addr &= 0x400FFFF;
|
||||
WriteHwio8(debug, addr, val);
|
||||
break;
|
||||
case 0xA: // ROM / EEPROM
|
||||
case 0xB: // ROM / EEPROM
|
||||
case 0xC: // ROM / EEPROM
|
||||
case 0xD: // ROM / EEPROM
|
||||
uint adjAddr = addr & 0x1FFFFFF;
|
||||
if (adjAddr >= EepromThreshold)
|
||||
{
|
||||
SaveProvider.Write8(adjAddr, val);
|
||||
}
|
||||
break;
|
||||
case 0xE: // Game Pak SRAM/Flash
|
||||
case 0xF: // Game Pak SRAM/Flash
|
||||
SaveProvider.Write8(addr, val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Write16Unregistered(bool debug, uint addr, ushort val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
break;
|
||||
case 0x5: // PPU Palettes
|
||||
addr &= 0x3FF;
|
||||
if (GetUshort(Gba.Ppu.Renderer.Palettes, addr) != val)
|
||||
{
|
||||
SetUshort(Gba.Ppu.Renderer.Palettes, addr, val);
|
||||
}
|
||||
break;
|
||||
case 0xA: // ROM / EEPROM
|
||||
case 0xB: // ROM / EEPROM
|
||||
case 0xC: // ROM / EEPROM
|
||||
case 0xD: // ROM / EEPROM
|
||||
uint adjAddr = addr & 0x1FFFFFF;
|
||||
if (adjAddr >= EepromThreshold)
|
||||
{
|
||||
SaveProvider.Write8(adjAddr, (byte)val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Write32Unregistered(bool debug, uint addr, uint val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 16));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 24));
|
||||
break;
|
||||
case 0x5: // PPU Palettes
|
||||
addr &= 0x3FF;
|
||||
if (GetUint(Gba.Ppu.Renderer.Palettes, addr) != val)
|
||||
{
|
||||
SetUint(Gba.Ppu.Renderer.Palettes, addr, val);
|
||||
}
|
||||
return;
|
||||
case 0x6: // PPU VRAM
|
||||
addr &= 0x1FFFF;
|
||||
if (addr < 0x18000)
|
||||
{
|
||||
SetUint(Gba.Ppu.Vram, addr, val);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public byte ReadHwio8(bool debug, uint addr)
|
||||
{
|
||||
if (LogHwioAccesses && (addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioWriteLog.TryGetValue(addr, out count);
|
||||
HwioWriteLog[addr] = count + 1;
|
||||
}
|
||||
|
||||
if (addr >= 0x4000000 && addr <= 0x4000056) // PPU
|
||||
{
|
||||
return Gba.Ppu.ReadHwio8(addr);
|
||||
}
|
||||
else if (addr >= 0x4000060 && addr <= 0x40000A8) // Sound
|
||||
{
|
||||
return Gba.GbaAudio.ReadHwio8(addr);
|
||||
}
|
||||
else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA
|
||||
{
|
||||
return Gba.Dma.ReadHwio8(addr);
|
||||
}
|
||||
else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer
|
||||
{
|
||||
return Gba.Timers.ReadHwio8(addr);
|
||||
}
|
||||
else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial
|
||||
{
|
||||
|
||||
}
|
||||
else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad
|
||||
{
|
||||
return Gba.Keypad.ReadHwio8(addr);
|
||||
}
|
||||
else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications
|
||||
{
|
||||
switch (addr) {
|
||||
case 0x4000135: return 0x80;
|
||||
}
|
||||
}
|
||||
else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control
|
||||
{
|
||||
return Gba.HwControl.ReadHwio8(addr);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteHwio8(bool debug, uint addr, byte val)
|
||||
{
|
||||
if (LogHwioAccesses && (addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioReadLog.TryGetValue(addr, out count);
|
||||
HwioReadLog[addr] = count + 1;
|
||||
}
|
||||
|
||||
if (addr >= 0x4000000 && addr <= 0x4000056) // PPU
|
||||
{
|
||||
Gba.Ppu.WriteHwio8(addr, val);
|
||||
}
|
||||
else if (addr >= 0x4000060 && addr <= 0x40000A7) // Sound
|
||||
{
|
||||
Gba.GbaAudio.WriteHwio8(addr, val);
|
||||
}
|
||||
else if (addr >= 0x40000B0 && addr <= 0x40000DF) // DMA
|
||||
{
|
||||
Gba.Dma.WriteHwio8(addr, val);
|
||||
}
|
||||
else if (addr >= 0x4000100 && addr <= 0x400010F) // Timer
|
||||
{
|
||||
Gba.Timers.WriteHwio8(addr, val);
|
||||
}
|
||||
else if (addr >= 0x4000120 && addr <= 0x400012C) // Serial
|
||||
{
|
||||
|
||||
}
|
||||
else if (addr >= 0x4000130 && addr <= 0x4000132) // Keypad
|
||||
{
|
||||
|
||||
}
|
||||
else if (addr >= 0x4000134 && addr <= 0x400015A) // Serial Communications
|
||||
{
|
||||
|
||||
}
|
||||
else if (addr >= 0x4000200 && addr <= 0x4FF0800) // Interrupt, Waitstate, and Power-Down Control
|
||||
{
|
||||
Gba.HwControl.WriteHwio8(addr, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/MemoryGba.cs.meta
Normal file
11
Assets/emulator/MemoryGba.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3ab94683bca44e4db631e74b158b993
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
449
Assets/emulator/MemoryNds7.cs
Normal file
449
Assets/emulator/MemoryNds7.cs
Normal file
@ -0,0 +1,449 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Concurrent;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.InteropServices;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed unsafe class MemoryNds7 : Memory
|
||||
{
|
||||
Nds Nds;
|
||||
|
||||
public MemoryNds7(Nds nds, ProviderNds provider)
|
||||
{
|
||||
Nds = nds;
|
||||
|
||||
SaveProvider = new NullSaveProvider();
|
||||
|
||||
for (uint i = 0; i < Arm7BiosSize && i < provider.Bios7.Length; i++)
|
||||
{
|
||||
Arm7Bios[i] = provider.Bios7[i];
|
||||
}
|
||||
}
|
||||
|
||||
public const int Arm7BiosSize = 16384;
|
||||
public const int Arm7WramSize = 65536;
|
||||
|
||||
public byte[] Arm7Bios = new byte[Arm7BiosSize];
|
||||
public byte[] Arm7Wram = new byte[Arm7WramSize];
|
||||
|
||||
public byte RCNT;
|
||||
|
||||
public override void InitPageTable(byte*[] table, uint[] maskTable, bool write)
|
||||
{
|
||||
byte* arm7Bios = TryPinByteArray(Arm7Bios);
|
||||
byte* mainRam = TryPinByteArray(Nds.MainRam);
|
||||
byte* arm7Wram = TryPinByteArray(Arm7Wram);
|
||||
|
||||
// 12 bits shaved off already, shave off another 12 to get 24
|
||||
for (uint i = 0; i < 1048576; i++)
|
||||
{
|
||||
uint addr = (uint)(i << 12);
|
||||
switch (i >> 12)
|
||||
{
|
||||
case 0x0: // BIOS
|
||||
if (!write)
|
||||
{
|
||||
table[i] = arm7Bios;
|
||||
}
|
||||
maskTable[i] = 0x00003FFF;
|
||||
break;
|
||||
case 0x2: // Main Memory
|
||||
table[i] = mainRam;
|
||||
maskTable[i] = 0x003FFFFF;
|
||||
break;
|
||||
case 0x3: // Shared RAM / ARM7 WRAM
|
||||
if (addr >= 0x03800000)
|
||||
{
|
||||
table[i] = arm7Wram;
|
||||
maskTable[i] = 0x0000FFFF;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~MemoryNds7()
|
||||
{
|
||||
Console.WriteLine("Cleaning up NDS7 memory...");
|
||||
UnpinByteArray(Arm7Bios);
|
||||
UnpinByteArray(Nds.MainRam);
|
||||
UnpinByteArray(Arm7Wram);
|
||||
}
|
||||
|
||||
public (byte[] array, uint offset) GetSharedRamParams(uint addr)
|
||||
{
|
||||
switch (Nds.MemoryControl.SharedRamControl)
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
addr &= 0xFFFF; // ARM7 WRAM
|
||||
return (Arm7Wram, addr);
|
||||
case 1:
|
||||
addr &= 0x3FFF; // 1st half of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
case 2:
|
||||
addr &= 0x3FFF; // 2st half of Shared RAM
|
||||
addr += 0x4000;
|
||||
return (Nds.SharedRam, addr);
|
||||
case 3:
|
||||
addr &= 0x7FFF; // All 32k of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
}
|
||||
}
|
||||
|
||||
public override byte Read8Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetByte(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
return ReadHwio8(debug, addr);
|
||||
case 0x6: // ARM7 VRAM
|
||||
return Nds.Ppu.ReadVram8Arm7(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override ushort Read16Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUshort(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
byte f0 = ReadHwio8(debug, addr++);
|
||||
byte f1 = ReadHwio8(debug, addr++);
|
||||
|
||||
ushort u16 = (ushort)((f1 << 8) | (f0 << 0));
|
||||
|
||||
return u16;
|
||||
case 0x6: // VRAM
|
||||
return (ushort)(
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 1) << 8)
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override uint Read32Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUint(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
byte f0 = ReadHwio8(debug, addr++);
|
||||
byte f1 = ReadHwio8(debug, addr++);
|
||||
byte f2 = ReadHwio8(debug, addr++);
|
||||
byte f3 = ReadHwio8(debug, addr++);
|
||||
|
||||
uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0));
|
||||
|
||||
return u32;
|
||||
case 0x6: // VRAM
|
||||
return (uint)(
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 1) << 8) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 2) << 16) |
|
||||
(Nds.Ppu.ReadVram8Arm7(addr + 3) << 24)
|
||||
);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void Write8Unregistered(bool debug, uint addr, byte val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetByte(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr, val);
|
||||
break;
|
||||
case 0x6: // ARM7 VRAM
|
||||
Nds.Ppu.WriteVram8Arm7(addr, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write16Unregistered(bool debug, uint addr, ushort val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUshort(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
break;
|
||||
case 0x6: // ARM7 VRAM
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 1, (byte)(val >> 8));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write32Unregistered(bool debug, uint addr, uint val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUint(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 16));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 24));
|
||||
break;
|
||||
case 0x6: // ARM7 VRAM
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 1, (byte)(val >> 8));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 2, (byte)(val >> 16));
|
||||
Nds.Ppu.WriteVram8Arm7(addr + 3, (byte)(val >> 24));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public byte ReadHwio8(bool debug, uint addr)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioReadLog)
|
||||
{
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioReadLog.TryGetValue(addr, out count);
|
||||
HwioReadLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special exceptions for cleanly defined blocks of MMIO
|
||||
if (addr >= 0x4000400 && addr < 0x4000500) // Audio channels
|
||||
{
|
||||
return Nds.Audio.ReadHwio8Channels(addr);
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
return Nds.Ppu.ReadHwio8Arm7(addr);
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
return Nds.Dma7.ReadHwio8(addr);
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
return Nds.Timers7.ReadHwio8(addr);
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
case 0x4100000: case 0x4100001: case 0x4100002: case 0x4100003: // IPCFIFORECV
|
||||
return Nds.Ipcs[1].ReadHwio8(addr);
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x4100010: case 0x4100011: case 0x4100012: case 0x4100013: // Slot 1 Data In
|
||||
return Nds.Cartridge.ReadHwio8(true, addr);
|
||||
|
||||
case 0x40001C0: case 0x40001C1: // SPICNT
|
||||
case 0x40001C2: case 0x40001C3: // SPIDATA
|
||||
return Nds.Spi.ReadHwio8(addr);
|
||||
|
||||
case 0x4000136: case 0x4000137: // EXTKEYIN
|
||||
// Console.WriteLine(Hex(Nds7.Cpu.R[15], 8));
|
||||
goto case 0x4000130;
|
||||
case 0x4000130: case 0x4000131: // KEYINPUT
|
||||
return Nds.Keypad.ReadHwio8(addr);
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMSTAT
|
||||
return Nds.MemoryControl.ReadHwio8Nds7(addr);
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
return Nds.HwControl7.ReadHwio8(addr);
|
||||
|
||||
case 0x4000134:
|
||||
return 0x80;
|
||||
case 0x4000135: // Stubbed RCNT
|
||||
return 0;
|
||||
|
||||
case 0x4000138: case 0x4000139: // RTC
|
||||
return Nds.Rtc.ReadHwio8(addr);
|
||||
|
||||
case 0x4000240: case 0x4000241: // Memory Control Status
|
||||
return Nds.MemoryControl.ReadHwio8Nds7(addr);
|
||||
|
||||
case 0x4000500: case 0x4000501: // SOUNDCNT
|
||||
case 0x4000504: case 0x4000505: // SOUNDBIAS
|
||||
case 0x4000508: case 0x4000509: // SNDCAPCNT
|
||||
return Nds.Audio.ReadHwio8(addr);
|
||||
|
||||
case 0x4000300:
|
||||
// Console.WriteLine("NDS7 POSTFLG read");
|
||||
return Nds.HwControl7.Postflg;
|
||||
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1
|
||||
return Nds.ReadHwio8Arm7(addr);
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS7: Unmapped MMIO read addr:{Hex(addr, 8)}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteHwio8(bool debug, uint addr, byte val)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioWriteLog)
|
||||
{
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioWriteLog.TryGetValue(addr, out count);
|
||||
HwioWriteLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special exceptions for cleanly defined blocks of MMIO
|
||||
if (addr >= 0x4000400 && addr < 0x4000500) // Audio channels
|
||||
{
|
||||
Nds.Audio.WriteHwio8Channels(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
Nds.Ppu.WriteHwio8Arm7(addr, val); return;
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
Nds.Dma7.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
Nds.Timers7.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
Nds.Ipcs[1].WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x40001A8: case 0x40001A9: case 0x40001AA: case 0x40001AB: // Slot 1 Command 0-3
|
||||
case 0x40001AC: case 0x40001AD: case 0x40001AE: case 0x40001AF: // Slot 1 Command 4-7
|
||||
Nds.Cartridge.WriteHwio8(true, addr, val); return;
|
||||
|
||||
case 0x40001B0: case 0x40001B1: case 0x40001B2: case 0x40001B3: // Slot 1 KEY2 encryption seed
|
||||
case 0x40001B4: case 0x40001B5: case 0x40001B6: case 0x40001B7:
|
||||
case 0x40001B8: case 0x40001B9: case 0x40001BA: case 0x40001BB:
|
||||
return;
|
||||
|
||||
case 0x40001C0: case 0x40001C1: // SPICNT
|
||||
case 0x40001C2: case 0x40001C3: // SPIDATA
|
||||
Nds.Spi.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMSTAT
|
||||
Nds.MemoryControl.WriteHwio8Nds7(addr, val); return;
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
Nds.HwControl7.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000134: case 0x4000135: // Stubbed RCNT
|
||||
return;
|
||||
|
||||
case 0x4000138: case 0x4000139: // RTC
|
||||
Nds.Rtc.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000500: case 0x4000501: // SOUNDCNT
|
||||
case 0x4000504: case 0x4000505: // SOUNDBIAS
|
||||
case 0x4000508: case 0x4000509: // SNDCAPCNT
|
||||
Nds.Audio.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000300:
|
||||
Console.WriteLine("NDS7 POSTFLG write");
|
||||
Nds.HwControl7.Postflg = (byte)(val & 1);
|
||||
return;
|
||||
|
||||
case 0x4000301:
|
||||
if ((val & 0b11000000) == 0b10000000)
|
||||
{
|
||||
Nds.Cpu7.Halted = true;
|
||||
}
|
||||
return;
|
||||
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1
|
||||
Nds.WriteHwio8Arm7(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS7: Unmapped MMIO write addr:{Hex(addr, 8)} val:{Hex(val, 2)}");
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/MemoryNds7.cs.meta
Normal file
11
Assets/emulator/MemoryNds7.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3898147a3efcd8499a8a51b3f945ff3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
622
Assets/emulator/MemoryNds9.cs
Normal file
622
Assets/emulator/MemoryNds9.cs
Normal file
@ -0,0 +1,622 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Concurrent;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.InteropServices;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public sealed unsafe class MemoryNds9 : Memory
|
||||
{
|
||||
Nds Nds;
|
||||
|
||||
public MemoryNds9(Nds nds, ProviderNds provider)
|
||||
{
|
||||
Nds = nds;
|
||||
|
||||
SaveProvider = new NullSaveProvider();
|
||||
|
||||
for (uint i = 0; i < Arm9BiosSize && i < provider.Bios9.Length; i++)
|
||||
{
|
||||
Arm9Bios[i] = provider.Bios9[i];
|
||||
}
|
||||
}
|
||||
|
||||
public const int Arm9BiosSize = 4096;
|
||||
public byte[] Arm9Bios = new byte[Arm9BiosSize];
|
||||
public const int ItcmSize = 32768;
|
||||
public byte[] Itcm = new byte[ItcmSize];
|
||||
public const int DtcmSize = 16384;
|
||||
public byte[] Dtcm = new byte[DtcmSize];
|
||||
|
||||
public uint DtcmBase = 0;
|
||||
public uint ItcmVirtualSize = 0;
|
||||
public uint DtcmVirtualSize = 0;
|
||||
public bool ItcmLoadMode = false;
|
||||
public bool DtcmLoadMode = false;
|
||||
|
||||
public override void InitPageTable(byte*[] table, uint[] maskTable, bool write)
|
||||
{
|
||||
byte* mainRam = TryPinByteArray(Nds.MainRam);
|
||||
byte* arm9Bios = TryPinByteArray(Arm9Bios);
|
||||
byte* dtcm = TryPinByteArray(Dtcm);
|
||||
byte* itcm = TryPinByteArray(Itcm);
|
||||
|
||||
// 12 bits shaved off already, shave off another 12 to get 24
|
||||
for (uint i = 0; i < 1048576; i++)
|
||||
{
|
||||
table[i] = null; // Clear everything out first, since on ARM9 things can move around
|
||||
|
||||
uint addr = (uint)(i << 12);
|
||||
switch (i >> 12)
|
||||
{
|
||||
case 0x2: // Main Memory
|
||||
table[i] = mainRam;
|
||||
maskTable[i] = 0x003FFFFF;
|
||||
break;
|
||||
case 0xFF: // BIOS
|
||||
if (!write)
|
||||
{
|
||||
table[i] = arm9Bios;
|
||||
}
|
||||
maskTable[i] = 0x00000FFF;
|
||||
break;
|
||||
}
|
||||
|
||||
if (addr >= DtcmBase && addr < DtcmBase + DtcmVirtualSize)
|
||||
{
|
||||
|
||||
if (write || !DtcmLoadMode)
|
||||
{
|
||||
// Console.WriteLine("DTCM page set at " + Util.Hex(addr, 8));
|
||||
table[i] = dtcm;
|
||||
}
|
||||
maskTable[i] = 0x00003FFF;
|
||||
}
|
||||
|
||||
// ITCM is immovable
|
||||
// ITCM has higher priority so write pages in after DTCM
|
||||
if (addr < ItcmVirtualSize)
|
||||
{
|
||||
if (write || !ItcmLoadMode)
|
||||
{
|
||||
table[i] = itcm;
|
||||
}
|
||||
maskTable[i] = 0x00007FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~MemoryNds9()
|
||||
{
|
||||
Console.WriteLine("Cleaning up NDS9 memory...");
|
||||
UnpinByteArray(Nds.MainRam);
|
||||
UnpinByteArray(Arm9Bios);
|
||||
UnpinByteArray(Dtcm);
|
||||
UnpinByteArray(Itcm);
|
||||
}
|
||||
|
||||
public void UpdateTcmSettings()
|
||||
{
|
||||
// Console.WriteLine("Data TCM Settings: " + Util.Hex(Nds.Cp15.DataTcmSettings, 8));
|
||||
ItcmVirtualSize = 512U << (int)((Nds.Cp15.InstTcmSettings >> 1) & 0x1F);
|
||||
DtcmVirtualSize = 512U << (int)((Nds.Cp15.DataTcmSettings >> 1) & 0x1F);
|
||||
|
||||
DtcmBase = (uint)(Nds.Cp15.DataTcmSettings & 0xFFFFF000);
|
||||
|
||||
ItcmLoadMode = BitTest(Nds.Cp15.ControlRegister, 19);
|
||||
DtcmLoadMode = BitTest(Nds.Cp15.ControlRegister, 17);
|
||||
|
||||
Console.WriteLine("ITCM set to: " + Util.Hex(0, 8) + " - " + Util.Hex(ItcmVirtualSize - 1, 8));
|
||||
Console.WriteLine("DTCM set to: " + Util.Hex(DtcmBase, 8) + " - " + Util.Hex(DtcmBase + DtcmVirtualSize - 1, 8));
|
||||
|
||||
InitPageTables();
|
||||
}
|
||||
|
||||
public (byte[] array, uint offset) GetSharedRamParams(uint addr)
|
||||
{
|
||||
switch (Nds.MemoryControl.SharedRamControl)
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
addr &= 0x7FFF; // All 32k of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
case 1:
|
||||
addr &= 0x3FFF; // 2nd half of Shared RAM
|
||||
addr += 0x4000;
|
||||
return (Nds.SharedRam, addr);
|
||||
case 2:
|
||||
addr &= 0x3FFF; // 1st half of Shared RAM
|
||||
return (Nds.SharedRam, addr);
|
||||
case 3:
|
||||
// throw new NotImplementedException("Implement unmapping Shared RAM from ARM9 without EmptyPage, since some game can possibly try to write to the EmptyPage");
|
||||
EmptyPage[0] = 0;
|
||||
return (EmptyPage, 0); // Unmapped
|
||||
}
|
||||
}
|
||||
|
||||
public override byte Read8Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetByte(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
return ReadHwio8(debug, addr);
|
||||
case 0x5: // PPU Palettes
|
||||
return Nds.Ppu.ReadPalettes8(addr);
|
||||
case 0x6: // VRAM
|
||||
return Nds.Ppu.ReadVram8Arm9(addr);
|
||||
case 0x7: // PPU OAM
|
||||
return Nds.Ppu.ReadOam8(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override ushort Read16Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUshort(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
byte f0 = ReadHwio8(debug, addr++);
|
||||
byte f1 = ReadHwio8(debug, addr++);
|
||||
|
||||
ushort u16 = (ushort)((f1 << 8) | (f0 << 0));
|
||||
|
||||
return u16;
|
||||
case 0x5: // PPU Palettes
|
||||
return Nds.Ppu.ReadPalettes16(addr);
|
||||
case 0x6: // VRAM
|
||||
return (ushort)(
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 1) << 8)
|
||||
);
|
||||
case 0x7: // PPU OAM
|
||||
return Nds.Ppu.ReadOam16(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override uint Read32Unregistered(bool debug, uint addr)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
return GetUint(array, offset);
|
||||
case 0x4: // I/O Registers
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
return Nds.Ppu3D.ReadHwio32(addr);
|
||||
}
|
||||
|
||||
byte f0 = ReadHwio8(debug, addr + 0);
|
||||
byte f1 = ReadHwio8(debug, addr + 1);
|
||||
byte f2 = ReadHwio8(debug, addr + 2);
|
||||
byte f3 = ReadHwio8(debug, addr + 3);
|
||||
|
||||
uint u32 = (uint)((f3 << 24) | (f2 << 16) | (f1 << 8) | (f0 << 0));
|
||||
|
||||
return u32;
|
||||
case 0x5: // PPU Palettes
|
||||
return Nds.Ppu.ReadPalettes32(addr);
|
||||
case 0x6: // VRAM
|
||||
return (uint)(
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 0) << 0) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 1) << 8) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 2) << 16) |
|
||||
(Nds.Ppu.ReadVram8Arm9(addr + 3) << 24)
|
||||
);
|
||||
case 0x7: // PPU OAM
|
||||
return Nds.Ppu.ReadOam32(addr);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override void Write8Unregistered(bool debug, uint addr, byte val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetByte(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr, val);
|
||||
break;
|
||||
case 0x5: // PPU Palettes - duplicated across upper-lower in 8-bit??
|
||||
Console.WriteLine("NDS: 8-bit write to palettes");
|
||||
// Nds.Ppu.WritePalettes8(addr + 0, val);
|
||||
// Nds.Ppu.WritePalettes8(addr + 1, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write16Unregistered(bool debug, uint addr, ushort val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUshort(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
break;
|
||||
case 0x5: // PPU Palettes
|
||||
Nds.Ppu.WritePalettes16(addr, val);
|
||||
break;
|
||||
case 0x6: // VRAM
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 1, (byte)(val >> 8));
|
||||
break;
|
||||
case 0x7: // PPU OAM
|
||||
Nds.Ppu.WriteOam16(addr, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write32Unregistered(bool debug, uint addr, uint val)
|
||||
{
|
||||
switch (addr >> 24)
|
||||
{
|
||||
case 0x3: // Shared RAM
|
||||
(byte[] array, uint offset) = GetSharedRamParams(addr);
|
||||
SetUint(array, offset, val);
|
||||
break;
|
||||
case 0x4: // I/O Registers
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
Nds.Ppu3D.WriteHwio32(addr, val);
|
||||
return;
|
||||
}
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 0));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 8));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 16));
|
||||
WriteHwio8(debug, addr++, (byte)(val >> 24));
|
||||
break;
|
||||
case 0x5: // PPU Palettes
|
||||
Nds.Ppu.WritePalettes32(addr, val);
|
||||
break;
|
||||
case 0x6: // VRAM
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 0, (byte)(val >> 0));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 1, (byte)(val >> 8));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 2, (byte)(val >> 16));
|
||||
Nds.Ppu.WriteVram8Arm9(addr + 3, (byte)(val >> 24));
|
||||
break;
|
||||
case 0x7: // PPU OAM
|
||||
Nds.Ppu.WriteOam32(addr, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public byte ReadHwio8(bool debug, uint addr)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioReadLog) {
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioReadLog.TryGetValue(addr, out count);
|
||||
HwioReadLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
Console.Error.WriteLine("8-bit or 16-bit read to 3D");
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
// Engine A
|
||||
case 0x4000000: case 0x4000001: case 0x4000002: case 0x4000003: // DISPCNT A
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
case 0x4000008: case 0x4000009: // BG0CNT
|
||||
case 0x400000A: case 0x400000B: // BG1CNT
|
||||
case 0x400000C: case 0x400000D: // BG2CNT
|
||||
case 0x400000E: case 0x400000F: // BG3CNT
|
||||
case 0x4000010: case 0x4000011: case 0x4000012: case 0x4000013: // BG0OFS
|
||||
case 0x4000014: case 0x4000015: case 0x4000016: case 0x4000017: // BG1OFS
|
||||
case 0x4000018: case 0x4000019: case 0x400001A: case 0x400001B: // BG2OFS
|
||||
case 0x400001C: case 0x400001D: case 0x400001E: case 0x400001F: // BG3OFS
|
||||
case 0x4000020: case 0x4000021: case 0x4000022: case 0x4000023: // BG2PA/PB
|
||||
case 0x4000024: case 0x4000025: case 0x4000026: case 0x4000027: // BG2PC/PD
|
||||
case 0x4000028: case 0x4000029: case 0x400002A: case 0x400002B: // BG2X
|
||||
case 0x400002C: case 0x400002D: case 0x400002E: case 0x400002F: // BG2Y
|
||||
case 0x4000030: case 0x4000031: case 0x4000032: case 0x4000033: // BG3PA/PB
|
||||
case 0x4000034: case 0x4000035: case 0x4000036: case 0x4000037: // BG3PC/PD
|
||||
case 0x4000038: case 0x4000039: case 0x400003A: case 0x400003B: // BG3X
|
||||
case 0x400003C: case 0x400003D: case 0x400003E: case 0x400003F: // BG3Y
|
||||
case 0x4000040: case 0x4000041: case 0x4000042: case 0x4000043: // WINH
|
||||
case 0x4000044: case 0x4000045: case 0x4000046: case 0x4000047: // WINV
|
||||
case 0x4000048: case 0x4000049: case 0x400004A: case 0x400004B: // WININ/OUT
|
||||
case 0x400004C: case 0x400004D: // MOSAIC
|
||||
case 0x4000050: case 0x4000051: // BLDCNT
|
||||
case 0x4000052: case 0x4000053: // BLDALPHA
|
||||
case 0x4000054: case 0x4000055: // BLDY
|
||||
case 0x4000060: case 0x4000061: // DISP3DCNT
|
||||
case 0x4000064: case 0x4000065: case 0x4000066: case 0x4000067: // DISPCAPCNT
|
||||
case 0x400006C: case 0x400006D: // MASTER_BRIGHT
|
||||
|
||||
// Engine B
|
||||
case 0x4001000: case 0x4001001: case 0x4001002: case 0x4001003: // DISPCNT A
|
||||
case 0x4001008: case 0x4001009: // BG0CNT
|
||||
case 0x400100A: case 0x400100B: // BG1CNT
|
||||
case 0x400100C: case 0x400100D: // BG2CNT
|
||||
case 0x400100E: case 0x400100F: // BG3CNT
|
||||
case 0x4001010: case 0x4001011: case 0x4001012: case 0x4001013: // BG0OFS
|
||||
case 0x4001014: case 0x4001015: case 0x4001016: case 0x4001017: // BG1OFS
|
||||
case 0x4001018: case 0x4001019: case 0x400101A: case 0x400101B: // BG2OFS
|
||||
case 0x400101C: case 0x400101D: case 0x400101E: case 0x400101F: // BG3OFS
|
||||
case 0x4001020: case 0x4001021: case 0x4001022: case 0x4001023: // BG2PA/PB
|
||||
case 0x4001024: case 0x4001025: case 0x4001026: case 0x4001027: // BG2PC/PD
|
||||
case 0x4001028: case 0x4001029: case 0x400102A: case 0x400102B: // BG2X
|
||||
case 0x400102C: case 0x400102D: case 0x400102E: case 0x400102F: // BG2Y
|
||||
case 0x4001030: case 0x4001031: case 0x4001032: case 0x4001033: // BG3PA/PB
|
||||
case 0x4001034: case 0x4001035: case 0x4001036: case 0x4001037: // BG3PC/PD
|
||||
case 0x4001038: case 0x4001039: case 0x400103A: case 0x400103B: // BG3X
|
||||
case 0x400103C: case 0x400103D: case 0x400103E: case 0x400103F: // BG3Y
|
||||
case 0x4001040: case 0x4001041: case 0x4001042: case 0x4001043: // WINH
|
||||
case 0x4001044: case 0x4001045: case 0x4001046: case 0x4001047: // WINV
|
||||
case 0x4001048: case 0x4001049: case 0x400104A: case 0x400104B: // WININ/OUT
|
||||
case 0x400104C: case 0x400104D: // MOSAIC
|
||||
case 0x4001050: case 0x4001051: // BLDCNT
|
||||
case 0x4001052: case 0x4001053: // BLDALPHA
|
||||
case 0x4001054: case 0x4001055: // BLDY
|
||||
case 0x400106C: case 0x400106D: // MASTER_BRIGHT
|
||||
return Nds.Ppu.ReadHwio8Arm9(addr);
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
return Nds.Dma9.ReadHwio8(addr);
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
return Nds.Timers9.ReadHwio8(addr);
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
case 0x4100000: case 0x4100001: case 0x4100002: case 0x4100003: // IPCFIFORECV
|
||||
return Nds.Ipcs[0].ReadHwio8(addr);
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x4100010: case 0x4100011: case 0x4100012: case 0x4100013: // Slot 1 Data In
|
||||
return Nds.Cartridge.ReadHwio8(false, addr);
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
return Nds.HwControl9.ReadHwio8(addr);
|
||||
|
||||
case 0x4000130: case 0x4000131: // KEYINPUT
|
||||
return Nds.Keypad.ReadHwio8(addr);
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMCNT
|
||||
case 0x4000240: case 0x4000241: case 0x4000242: case 0x4000243: // VRAMCNT
|
||||
case 0x4000244: case 0x4000245: case 0x4000246: case 0x4000247: // VRAMCNT, WRAMCNT
|
||||
case 0x4000248: case 0x4000249: // VRAMCNT
|
||||
return Nds.MemoryControl.ReadHwio8Nds9(addr);
|
||||
|
||||
case 0x4000280: case 0x4000281: case 0x4000282: case 0x4000283: // DIVCNT B3
|
||||
case 0x4000290: case 0x4000291: case 0x4000292: case 0x4000293: // DIV_NUMER
|
||||
case 0x4000294: case 0x4000295: case 0x4000296: case 0x4000297: // DIV_NUMER
|
||||
case 0x4000298: case 0x4000299: case 0x400029A: case 0x400029B: // DIV_DENOM
|
||||
case 0x400029C: case 0x400029D: case 0x400029E: case 0x400029F: // DIV_DENOM
|
||||
case 0x40002A0: case 0x40002A1: case 0x40002A2: case 0x40002A3: // DIV_RESULT
|
||||
case 0x40002A4: case 0x40002A5: case 0x40002A6: case 0x40002A7: // DIV_RESULT
|
||||
case 0x40002A8: case 0x40002A9: case 0x40002AA: case 0x40002AB: // DIVREM_RESULT
|
||||
case 0x40002AC: case 0x40002AD: case 0x40002AE: case 0x40002AF: // DIVREM_RESULT
|
||||
case 0x40002B0: case 0x40002B1: // SQRTCNT
|
||||
case 0x40002B4: case 0x40002B5: case 0x40002B6: case 0x40002B7: // SQRT_RESULT
|
||||
case 0x40002B8: case 0x40002B9: case 0x40002BA: case 0x40002BB: // SQRT_PARAM
|
||||
case 0x40002BC: case 0x40002BD: case 0x40002BE: case 0x40002BF: // SQRT_PARAM
|
||||
return Nds.Math.ReadHwio8(addr);
|
||||
|
||||
case 0x4000300:
|
||||
// Console.WriteLine("NDS9 POSTFLG read");
|
||||
return Nds.HwControl9.Postflg;
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307: // POWCNT1
|
||||
return Nds.ReadHwio8Arm9(addr);
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS9: Unmapped MMIO read addr:{Hex(addr, 8)}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteHwio8(bool debug, uint addr, byte val)
|
||||
{
|
||||
if (LogHwioAccesses)
|
||||
{
|
||||
lock (HwioWriteLog) {
|
||||
if ((addr & ~1) != 0 && !debug)
|
||||
{
|
||||
uint count;
|
||||
HwioWriteLog.TryGetValue(addr, out count);
|
||||
HwioWriteLog[addr] = count + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (addr >= 0x4000320 && addr < 0x40006A4) // 3D
|
||||
{
|
||||
// Console.Error.WriteLine($"8-bit or 16-bit write to 3D addr:{Hex(addr, 8)} val:{Hex(val, 2)}");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
// Engine A
|
||||
case 0x4000000: case 0x4000001: case 0x4000002: case 0x4000003: // DISPCNT A
|
||||
case 0x4000004: case 0x4000005: // DISPSTAT
|
||||
case 0x4000006: case 0x4000007: // VCOUNT
|
||||
case 0x4000008: case 0x4000009: // BG0CNT
|
||||
case 0x400000A: case 0x400000B: // BG1CNT
|
||||
case 0x400000C: case 0x400000D: // BG2CNT
|
||||
case 0x400000E: case 0x400000F: // BG3CNT
|
||||
case 0x4000010: case 0x4000011: case 0x4000012: case 0x4000013: // BG0OFS
|
||||
case 0x4000014: case 0x4000015: case 0x4000016: case 0x4000017: // BG1OFS
|
||||
case 0x4000018: case 0x4000019: case 0x400001A: case 0x400001B: // BG2OFS
|
||||
case 0x400001C: case 0x400001D: case 0x400001E: case 0x400001F: // BG3OFS
|
||||
case 0x4000020: case 0x4000021: case 0x4000022: case 0x4000023: // BG2PA/PB
|
||||
case 0x4000024: case 0x4000025: case 0x4000026: case 0x4000027: // BG2PC/PD
|
||||
case 0x4000028: case 0x4000029: case 0x400002A: case 0x400002B: // BG2X
|
||||
case 0x400002C: case 0x400002D: case 0x400002E: case 0x400002F: // BG2Y
|
||||
case 0x4000030: case 0x4000031: case 0x4000032: case 0x4000033: // BG3PA/PB
|
||||
case 0x4000034: case 0x4000035: case 0x4000036: case 0x4000037: // BG3PC/PD
|
||||
case 0x4000038: case 0x4000039: case 0x400003A: case 0x400003B: // BG3X
|
||||
case 0x400003C: case 0x400003D: case 0x400003E: case 0x400003F: // BG3Y
|
||||
case 0x4000040: case 0x4000041: case 0x4000042: case 0x4000043: // WINH
|
||||
case 0x4000044: case 0x4000045: case 0x4000046: case 0x4000047: // WINV
|
||||
case 0x4000048: case 0x4000049: case 0x400004A: case 0x400004B: // WININ/OUT
|
||||
case 0x400004C: case 0x400004D: // MOSAIC
|
||||
case 0x4000050: case 0x4000051: // BLDCNT
|
||||
case 0x4000052: case 0x4000053: // BLDALPHA
|
||||
case 0x4000054: case 0x4000055: // BLDY
|
||||
case 0x4000060: case 0x4000061: // DISP3DCNT
|
||||
case 0x4000064: case 0x4000065: case 0x4000066: case 0x4000067: // DISPCAPCNT
|
||||
case 0x400006C: case 0x400006D: // MASTER_BRIGHT
|
||||
|
||||
// Engine B
|
||||
case 0x4001000: case 0x4001001: case 0x4001002: case 0x4001003: // DISPCNT A
|
||||
case 0x4001008: case 0x4001009: // BG0CNT
|
||||
case 0x400100A: case 0x400100B: // BG1CNT
|
||||
case 0x400100C: case 0x400100D: // BG2CNT
|
||||
case 0x400100E: case 0x400100F: // BG3CNT
|
||||
case 0x4001010: case 0x4001011: case 0x4001012: case 0x4001013: // BG0OFS
|
||||
case 0x4001014: case 0x4001015: case 0x4001016: case 0x4001017: // BG1OFS
|
||||
case 0x4001018: case 0x4001019: case 0x400101A: case 0x400101B: // BG2OFS
|
||||
case 0x400101C: case 0x400101D: case 0x400101E: case 0x400101F: // BG3OFS
|
||||
case 0x4001020: case 0x4001021: case 0x4001022: case 0x4001023: // BG2PA/PB
|
||||
case 0x4001024: case 0x4001025: case 0x4001026: case 0x4001027: // BG2PC/PD
|
||||
case 0x4001028: case 0x4001029: case 0x400102A: case 0x400102B: // BG2X
|
||||
case 0x400102C: case 0x400102D: case 0x400102E: case 0x400102F: // BG2Y
|
||||
case 0x4001030: case 0x4001031: case 0x4001032: case 0x4001033: // BG3PA/PB
|
||||
case 0x4001034: case 0x4001035: case 0x4001036: case 0x4001037: // BG3PC/PD
|
||||
case 0x4001038: case 0x4001039: case 0x400103A: case 0x400103B: // BG3X
|
||||
case 0x400103C: case 0x400103D: case 0x400103E: case 0x400103F: // BG3Y
|
||||
case 0x4001040: case 0x4001041: case 0x4001042: case 0x4001043: // WINH
|
||||
case 0x4001044: case 0x4001045: case 0x4001046: case 0x4001047: // WINV
|
||||
case 0x4001048: case 0x4001049: case 0x400104A: case 0x400104B: // WININ/OUT
|
||||
case 0x400104C: case 0x400104D: // MOSAIC
|
||||
case 0x4001050: case 0x4001051: // BLDCNT
|
||||
case 0x4001052: case 0x4001053: // BLDALPHA
|
||||
case 0x4001054: case 0x4001055: // BLDY
|
||||
case 0x400106C: case 0x400106D: // MASTER_BRIGHT
|
||||
Nds.Ppu.WriteHwio8Arm9(addr, val); return;
|
||||
|
||||
case 0x40000B0: case 0x40000B1: case 0x40000B2: case 0x40000B3: // DMA0SAD
|
||||
case 0x40000B4: case 0x40000B5: case 0x40000B6: case 0x40000B7: // DMA0DAD
|
||||
case 0x40000B8: case 0x40000B9: case 0x40000BA: case 0x40000BB: // DMA0CNT
|
||||
case 0x40000BC: case 0x40000BD: case 0x40000BE: case 0x40000BF: // DMA1SAD
|
||||
case 0x40000C0: case 0x40000C1: case 0x40000C2: case 0x40000C3: // DMA1DAD
|
||||
case 0x40000C4: case 0x40000C5: case 0x40000C6: case 0x40000C7: // DMA1CNT
|
||||
case 0x40000C8: case 0x40000C9: case 0x40000CA: case 0x40000CB: // DMA2SAD
|
||||
case 0x40000CC: case 0x40000CD: case 0x40000CE: case 0x40000CF: // DMA2DAD
|
||||
case 0x40000D0: case 0x40000D1: case 0x40000D2: case 0x40000D3: // DMA2CNT
|
||||
case 0x40000D4: case 0x40000D5: case 0x40000D6: case 0x40000D7: // DMA3SAD
|
||||
case 0x40000D8: case 0x40000D9: case 0x40000DA: case 0x40000DB: // DMA3DAD
|
||||
case 0x40000DC: case 0x40000DD: case 0x40000DE: case 0x40000DF: // DMA3CNT
|
||||
case 0x40000E0: case 0x40000E1: case 0x40000E2: case 0x40000E3: // DMA0 Fill Data
|
||||
case 0x40000E4: case 0x40000E5: case 0x40000E6: case 0x40000E7: // DMA1 Fill Data
|
||||
case 0x40000E8: case 0x40000E9: case 0x40000EA: case 0x40000EB: // DMA2 Fill Data
|
||||
case 0x40000EC: case 0x40000ED: case 0x40000EE: case 0x40000EF: // DMA3 Fill Data
|
||||
Nds.Dma9.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000100: case 0x4000101: case 0x4000102: case 0x4000103: // Timer 0
|
||||
case 0x4000104: case 0x4000105: case 0x4000106: case 0x4000107: // Timer 1
|
||||
case 0x4000108: case 0x4000109: case 0x400010A: case 0x400010B: // Timer 2
|
||||
case 0x400010C: case 0x400010D: case 0x400010E: case 0x400010F: // Timer 3
|
||||
Nds.Timers9.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000180: case 0x4000181: case 0x4000182: case 0x4000183: // IPCSYNC
|
||||
case 0x4000184: case 0x4000185: case 0x4000186: case 0x4000187: // IPCFIFOCNT
|
||||
case 0x4000188: case 0x4000189: case 0x400018A: case 0x400018B: // IPCFIFOSEND
|
||||
Nds.Ipcs[0].WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x40001A0: case 0x40001A1: // AUXSPICNT
|
||||
case 0x40001A2: case 0x40001A3: // AUXSPIDATA
|
||||
case 0x40001A4: case 0x40001A5: case 0x40001A6: case 0x40001A7: // ROMCTRL
|
||||
case 0x40001A8: case 0x40001A9: case 0x40001AA: case 0x40001AB: // Slot 1 Command 0-3
|
||||
case 0x40001AC: case 0x40001AD: case 0x40001AE: case 0x40001AF: // Slot 1 Command 4-7
|
||||
Nds.Cartridge.WriteHwio8(false, addr, val); return;
|
||||
|
||||
case 0x40001B0: case 0x40001B1: case 0x40001B2: case 0x40001B3: // Slot 1 KEY2 encryption seed
|
||||
case 0x40001B4: case 0x40001B5: case 0x40001B6: case 0x40001B7:
|
||||
case 0x40001B8: case 0x40001B9: case 0x40001BA: case 0x40001BB:
|
||||
return;
|
||||
|
||||
case 0x4000208: case 0x4000209: case 0x400020A: case 0x400020B: // IME
|
||||
case 0x4000210: case 0x4000211: case 0x4000212: case 0x4000213: // IE
|
||||
case 0x4000214: case 0x4000215: case 0x4000216: case 0x4000217: // IF
|
||||
Nds.HwControl9.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000204: case 0x4000205: // EXMEMCNT
|
||||
case 0x4000240: case 0x4000241: case 0x4000242: case 0x4000243: // VRAMCNT
|
||||
case 0x4000244: case 0x4000245: case 0x4000246: case 0x4000247: // VRAMCNT, WRAMCNT
|
||||
case 0x4000248: case 0x4000249: // VRAMCNT
|
||||
Nds.MemoryControl.WriteHwio8Nds9(addr, val); return;
|
||||
|
||||
case 0x4000280: case 0x4000281: case 0x4000282: case 0x4000283: // DIVCNT B3
|
||||
case 0x4000290: case 0x4000291: case 0x4000292: case 0x4000293: // DIV_NUMER
|
||||
case 0x4000294: case 0x4000295: case 0x4000296: case 0x4000297: // DIV_NUMER
|
||||
case 0x4000298: case 0x4000299: case 0x400029A: case 0x400029B: // DIV_DENOM
|
||||
case 0x400029C: case 0x400029D: case 0x400029E: case 0x400029F: // DIV_DENOM
|
||||
case 0x40002A0: case 0x40002A1: case 0x40002A2: case 0x40002A3: // DIV_RESULT
|
||||
case 0x40002A4: case 0x40002A5: case 0x40002A6: case 0x40002A7: // DIV_RESULT
|
||||
case 0x40002A8: case 0x40002A9: case 0x40002AA: case 0x40002AB: // DIVREM_RESULT
|
||||
case 0x40002AC: case 0x40002AD: case 0x40002AE: case 0x40002AF: // DIVREM_RESULT
|
||||
case 0x40002B0: case 0x40002B1: // SQRTCNT
|
||||
case 0x40002B4: case 0x40002B5: case 0x40002B6: case 0x40002B7: // SQRT_RESULT
|
||||
case 0x40002B8: case 0x40002B9: case 0x40002BA: case 0x40002BB: // SQRT_PARAM
|
||||
case 0x40002BC: case 0x40002BD: case 0x40002BE: case 0x40002BF: // SQRT_PARAM
|
||||
Nds.Math.WriteHwio8(addr, val); return;
|
||||
|
||||
case 0x4000300:
|
||||
Console.WriteLine("NDS9 POSTFLG write");
|
||||
Nds.HwControl9.Postflg = (byte)(val & 0b11);
|
||||
return;
|
||||
case 0x4000304: case 0x4000305: case 0x4000306: case 0x4000307:// POWCNT1
|
||||
Nds.WriteHwio8Arm9(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
// Console.WriteLine($"NDS9: Unmapped MMIO write addr:{Hex(addr, 8)} val:{Hex(val, 2)}");
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/MemoryNds9.cs.meta
Normal file
11
Assets/emulator/MemoryNds9.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c562e007e77a5c448b8dfa6621f2efb8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
179
Assets/emulator/MemoryUtil.cs
Normal file
179
Assets/emulator/MemoryUtil.cs
Normal file
@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Collections.Concurrent;
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public static unsafe class MemoryUtil
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong GetUlong(byte[] arr, uint addr)
|
||||
{
|
||||
return
|
||||
((ulong)arr[addr + 0] << 0) |
|
||||
((ulong)arr[addr + 1] << 8) |
|
||||
((ulong)arr[addr + 2] << 16) |
|
||||
((ulong)arr[addr + 3] << 24) |
|
||||
((ulong)arr[addr + 4] << 32) |
|
||||
((ulong)arr[addr + 5] << 40) |
|
||||
((ulong)arr[addr + 6] << 48) |
|
||||
((ulong)arr[addr + 7] << 56);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetUint(byte[] arr, uint addr)
|
||||
{
|
||||
return (uint)(
|
||||
(arr[addr + 0] << 0) |
|
||||
(arr[addr + 1] << 8) |
|
||||
(arr[addr + 2] << 16) |
|
||||
(arr[addr + 3] << 24)
|
||||
);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ushort GetUshort(byte[] arr, uint addr)
|
||||
{
|
||||
return (ushort)(
|
||||
(arr[addr + 0] << 0) |
|
||||
(arr[addr + 1] << 8)
|
||||
);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte GetByte(byte[] arr, uint addr)
|
||||
{
|
||||
return arr[addr];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong GetUlong(byte* arr, uint addr)
|
||||
{
|
||||
return *(ulong*)(arr + addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetUint(byte* arr, uint addr)
|
||||
{
|
||||
return *(uint*)(arr + addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ushort GetUshort(byte* arr, uint addr)
|
||||
{
|
||||
return *(ushort*)(arr + addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static byte GetByte(byte* arr, uint addr)
|
||||
{
|
||||
return *(byte*)(arr + addr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetUlong(byte[] arr, uint addr, ulong val)
|
||||
{
|
||||
arr[addr + 0] = (byte)(val >> 0);
|
||||
arr[addr + 1] = (byte)(val >> 8);
|
||||
arr[addr + 2] = (byte)(val >> 16);
|
||||
arr[addr + 3] = (byte)(val >> 24);
|
||||
arr[addr + 4] = (byte)(val >> 32);
|
||||
arr[addr + 5] = (byte)(val >> 40);
|
||||
arr[addr + 6] = (byte)(val >> 48);
|
||||
arr[addr + 7] = (byte)(val >> 56);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetUint(byte[] arr, uint addr, uint val)
|
||||
{
|
||||
arr[addr + 0] = (byte)(val >> 0);
|
||||
arr[addr + 1] = (byte)(val >> 8);
|
||||
arr[addr + 2] = (byte)(val >> 16);
|
||||
arr[addr + 3] = (byte)(val >> 24);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetUshort(byte[] arr, uint addr, ushort val)
|
||||
{
|
||||
arr[addr + 0] = (byte)(val >> 0);
|
||||
arr[addr + 1] = (byte)(val >> 8);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetByte(byte[] arr, uint addr, byte val)
|
||||
{
|
||||
arr[addr] = val;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetUlong(byte* arr, uint addr, ulong val)
|
||||
{
|
||||
*(ulong*)(arr + addr) = val;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetUint(byte* arr, uint addr, uint val)
|
||||
{
|
||||
*(uint*)(arr + addr) = val;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetUshort(byte* arr, uint addr, ushort val)
|
||||
{
|
||||
*(ushort*)(arr + addr) = val;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetByte(byte* arr, uint addr, byte val)
|
||||
{
|
||||
*(byte*)(arr + addr) = val;
|
||||
}
|
||||
|
||||
public static byte* AllocateUnmanagedArray(int size)
|
||||
{
|
||||
byte* arr = (byte*)Marshal.AllocHGlobal(size).ToPointer();
|
||||
|
||||
// Zero out array
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
arr[i] = 0;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static ushort* AllocateUnmanagedArray16(int size)
|
||||
{
|
||||
ushort* arr = (ushort*)Marshal.AllocHGlobal(size * sizeof(ushort)).ToPointer();
|
||||
|
||||
// Zero out array
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
arr[i] = 0;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static uint* AllocateUnmanagedArray32(int size)
|
||||
{
|
||||
uint* arr = (uint*)Marshal.AllocHGlobal(size * sizeof(uint)).ToPointer();
|
||||
|
||||
// Zero out array
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
arr[i] = 0;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
public static void FreeUnmanagedArray(void* arr)
|
||||
{
|
||||
Marshal.FreeHGlobal(new IntPtr(arr));
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/MemoryUtil.cs.meta
Normal file
11
Assets/emulator/MemoryUtil.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d5d8289e96e59342bab0324b6d66a91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
396
Assets/emulator/Nds.cs
Normal file
396
Assets/emulator/Nds.cs
Normal file
@ -0,0 +1,396 @@
|
||||
using System;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using static OptimeGBA.Bits;
|
||||
using static Util;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public unsafe sealed class Nds
|
||||
{
|
||||
public ProviderNds Provider;
|
||||
|
||||
// ARM9 side
|
||||
public MemoryNds9 Mem9;
|
||||
public Arm7 Cpu9;
|
||||
public HwControlNds HwControl9;
|
||||
public DmaNds Dma9;
|
||||
public Timers Timers9;
|
||||
public Nds9Math Math;
|
||||
|
||||
// ARM7 side
|
||||
public MemoryNds7 Mem7;
|
||||
public Arm7 Cpu7;
|
||||
public HwControlNds HwControl7;
|
||||
public Spi Spi;
|
||||
public NdsAudio Audio;
|
||||
public DmaNds Dma7;
|
||||
public Timers Timers7;
|
||||
|
||||
public Scheduler Scheduler;
|
||||
|
||||
public Cp15 Cp15;
|
||||
|
||||
public CartridgeNds Cartridge;
|
||||
|
||||
// Based off of EXMEMCNT ownership rules, there 1 is ARM7
|
||||
public Ipc[] Ipcs; // 0: ARM9 to ARM7, 1: ARM7 to ARM9
|
||||
|
||||
public PpuNds Ppu;
|
||||
public PpuNds3D Ppu3D;
|
||||
|
||||
public MemoryControlNds MemoryControl;
|
||||
|
||||
public Keypad Keypad = new Keypad();
|
||||
|
||||
public RtcNds Rtc;
|
||||
|
||||
public byte[] MainRam = new byte[4194304];
|
||||
public byte[] SharedRam = new byte[32768];
|
||||
|
||||
public int Arm9PendingTicks;
|
||||
|
||||
public ulong Steps;
|
||||
|
||||
public Nds(ProviderNds provider)
|
||||
{
|
||||
Provider = provider;
|
||||
Scheduler = new Scheduler();
|
||||
|
||||
Ipcs = new Ipc[] {
|
||||
new Ipc(this, 0),
|
||||
new Ipc(this, 1),
|
||||
};
|
||||
|
||||
Cp15 = new Cp15(this);
|
||||
|
||||
Cartridge = new CartridgeNds(this);
|
||||
|
||||
Ppu = new PpuNds(this, Scheduler);
|
||||
Ppu3D = new PpuNds3D(this, Scheduler);
|
||||
|
||||
MemoryControl = new MemoryControlNds();
|
||||
|
||||
Rtc = new RtcNds();
|
||||
|
||||
// ARM9 Init
|
||||
Mem9 = new MemoryNds9(this, Provider);
|
||||
Cpu9 = new Arm7(StateChangeArm9, Mem9, true, true, Cp15);
|
||||
HwControl9 = new HwControlNds(Cpu9);
|
||||
Dma9 = new DmaNds(false, Mem9, HwControl9);
|
||||
Timers9 = new Timers(null, HwControl9, Scheduler, true, false);
|
||||
Math = new Nds9Math(this);
|
||||
Mem9.InitPageTables();
|
||||
Cpu9.InitFlushPipeline();
|
||||
Cpu9.SetVectorMode(true);
|
||||
// screw it
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing8And16,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing32,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing8And16InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu9.SetTimingsTable(
|
||||
Cpu9.Timing32InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
|
||||
// ARM7 init
|
||||
Mem7 = new MemoryNds7(this, Provider);
|
||||
Spi = new Spi(this);
|
||||
Audio = new NdsAudio(this);
|
||||
Cpu7 = new Arm7(StateChangeArm7, Mem7, false, false, null);
|
||||
HwControl7 = new HwControlNds(Cpu7);
|
||||
Dma7 = new DmaNds(true, Mem7, HwControl7);
|
||||
Timers7 = new Timers(null, HwControl7, Scheduler, true, true);
|
||||
Mem7.InitPageTables();
|
||||
Cpu7.InitFlushPipeline();
|
||||
// screw it
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing8And16,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing32,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing8And16InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
Cpu7.SetTimingsTable(
|
||||
Cpu7.Timing32InstrFetch,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
|
||||
#if UNSAFE
|
||||
Console.WriteLine("Starting in memory UNSAFE mode");
|
||||
#else
|
||||
Console.WriteLine("Starting in memory SAFE mode");
|
||||
#endif
|
||||
|
||||
if (provider.DirectBoot)
|
||||
{
|
||||
var rom = provider.Rom;
|
||||
|
||||
// Firmware init
|
||||
MemoryControl.SharedRamControl = 3;
|
||||
HwControl7.Postflg = 1;
|
||||
HwControl9.Postflg = 1;
|
||||
|
||||
Cartridge.Slot1Enable = true;
|
||||
|
||||
Cpu9.IRQDisable = true;
|
||||
Cpu9.FIQDisable = true;
|
||||
|
||||
// Thanks Hydr8gon / fleroviux lol
|
||||
Mem7.Write16(0x4000184, 0x8501); // IPCFIFOCNT7
|
||||
Mem9.Write16(0x4000184, 0x8501); // IPCFIFOCNT9
|
||||
|
||||
Cp15.TransferTo(0, 0x0005707D, 1, 0, 0); // CP15 Control
|
||||
Cp15.TransferTo(0, 0x0300000A, 9, 1, 0); // Data TCM base/size
|
||||
Cp15.TransferTo(0, 0x00000020, 9, 1, 1); // Instruction TCM size
|
||||
Mem9.Write8(0x4000247, 0x03); // WRAMCNT
|
||||
Mem9.Write16(0x4000304, 0x0001); // POWCNT1
|
||||
Mem7.Write16(0x4000504, 0x0200); // SOUNDBIAS
|
||||
|
||||
Mem9.Write32(0x027FF800, 0x1FC2); // Chip ID 1
|
||||
Mem9.Write32(0x027FF804, 0x1FC2); // Chip ID 2
|
||||
Mem9.Write16(0x027FF850, 0x5835); // ARM7 BIOS CRC
|
||||
Mem9.Write16(0x027FF880, 0x0007); // Message from ARM9 to ARM7
|
||||
Mem9.Write16(0x027FF884, 0x0006); // ARM7 boot task
|
||||
Mem9.Write32(0x027FFC00, 0x1FC2); // Copy of chip ID 1
|
||||
Mem9.Write32(0x027FFC04, 0x1FC2); // Copy of chip ID 2
|
||||
Mem9.Write16(0x027FFC10, 0x5835); // Copy of ARM7 BIOS CRC
|
||||
Mem9.Write16(0x027FFC40, 0x0001); // Boot indicator
|
||||
|
||||
Mem9.Write32(0x027FF864, 0);
|
||||
Mem9.Write32(0x027FF868, (uint)(GetUshort(Provider.Firmware, 0x20) << 3));
|
||||
|
||||
Mem9.Write16(0x027FF874, GetUshort(Provider.Firmware, 0x26));
|
||||
Mem9.Write16(0x027FF876, GetUshort(Provider.Firmware, 0x04));
|
||||
|
||||
// Copy in header
|
||||
if (rom.Length >= 0x170)
|
||||
{
|
||||
for (uint i = 0; i < 0x170; i++)
|
||||
{
|
||||
Mem9.Write8(0x027FFE00 + i, rom[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < 0x70; i++)
|
||||
{
|
||||
Mem9.Write8(0x27FFC80 + i, Provider.Firmware[0x3FF00 + i]);
|
||||
}
|
||||
|
||||
Mem9.Write32(0x027FF864, 0);
|
||||
Mem9.Write32(0x027FF868, (uint)(GetUshort(Provider.Firmware, 0x20) << 3));
|
||||
|
||||
Mem9.Write16(0x027FF874, GetUshort(Provider.Firmware, 0x26));
|
||||
Mem9.Write16(0x027FF876, GetUshort(Provider.Firmware, 0x04));
|
||||
|
||||
if (rom.Length >= 0x20)
|
||||
{
|
||||
uint arm7RomOffset = GetUint(rom, 0x30);
|
||||
uint arm7EntryAddr = GetUint(rom, 0x34);
|
||||
uint arm7RamAddr = GetUint(rom, 0x38);
|
||||
uint arm7Size = GetUint(rom, 0x3C);
|
||||
|
||||
// ROM offset is aligned by 0x1000
|
||||
Console.WriteLine("ARM7 ROM Offset: " + Hex(arm7RomOffset, 8));
|
||||
Console.WriteLine("ARM7 RAM Address: " + Hex(arm7RamAddr, 8));
|
||||
Console.WriteLine("ARM7 Entry: " + Hex(arm7EntryAddr, 8));
|
||||
Console.WriteLine("ARM7 Size: " + arm7Size);
|
||||
for (uint i = 0; i < arm7Size; i++)
|
||||
{
|
||||
Mem7.Write8(arm7RamAddr + i, rom[arm7RomOffset + i]);
|
||||
}
|
||||
Cpu7.R[13] = 0x3002F7C;
|
||||
Cpu7.SetModeReg(13, Arm7Mode.IRQ, 0x3003F80);
|
||||
Cpu7.SetModeReg(13, Arm7Mode.SVC, 0x3003FC0);
|
||||
Cpu7.R[12] = arm7EntryAddr;
|
||||
Cpu7.R[14] = arm7EntryAddr;
|
||||
Cpu7.R[15] = arm7EntryAddr;
|
||||
Cpu7.InitFlushPipeline();
|
||||
|
||||
uint arm9RomOffset = GetUint(rom, 0x20);
|
||||
uint arm9EntryAddr = GetUint(rom, 0x24);
|
||||
uint arm9RamAddr = GetUint(rom, 0x28);
|
||||
uint arm9Size = GetUint(rom, 0x2C);
|
||||
|
||||
Console.WriteLine("ARM9 ROM Offset: " + Hex(arm9RomOffset, 8));
|
||||
Console.WriteLine("ARM9 RAM Address: " + Hex(arm9RamAddr, 8));
|
||||
Console.WriteLine("ARM9 Entry: " + Hex(arm9EntryAddr, 8));
|
||||
Console.WriteLine("ARM9 Size: " + arm9Size);
|
||||
for (uint i = 0; i < arm9Size; i++)
|
||||
{
|
||||
Mem9.Write8(arm9RamAddr + i, rom[arm9RomOffset + i]);
|
||||
}
|
||||
Cpu9.R[13] = 0x380FD80;
|
||||
Cpu9.SetModeReg(13, Arm7Mode.IRQ, 0x380FF80);
|
||||
Cpu9.SetModeReg(13, Arm7Mode.SVC, 0x380FFC0);
|
||||
Cpu9.R[12] = arm9EntryAddr;
|
||||
Cpu9.R[14] = arm9EntryAddr;
|
||||
Cpu9.R[15] = arm9EntryAddr;
|
||||
Cpu9.InitFlushPipeline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public uint Step()
|
||||
{
|
||||
Steps++;
|
||||
|
||||
long beforeTicks = Scheduler.CurrentTicks;
|
||||
|
||||
while (Scheduler.CurrentTicks < Scheduler.NextEventTicks)
|
||||
{
|
||||
// Running both CPUs at 1CPI at 32 MHz causes the firmware to loop the setup screen,
|
||||
// so don't do that when not debugging simple test ROMs
|
||||
// Cpu7.Execute();
|
||||
// Cpu9.Execute();
|
||||
// Scheduler.CurrentTicks += 1;
|
||||
|
||||
// TODO: Proper NDS timings
|
||||
// TODO: Figure out a better way to implement halting
|
||||
uint ticks7 = 0;
|
||||
// Run 32 ARM7 instructions at a time, who needs tight synchronization
|
||||
const uint instrsAtATime = 32;
|
||||
if (!Cpu7.Halted)
|
||||
{
|
||||
for (uint i = 0; i < instrsAtATime; i++)
|
||||
{
|
||||
if (!Cpu7.Halted)
|
||||
{
|
||||
ticks7 += Cpu7.Execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
ticks7 += instrsAtATime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ticks7 += instrsAtATime;
|
||||
}
|
||||
|
||||
Arm9PendingTicks += (int)ticks7 * 2; // ARM9 runs at twice the speed of ARM7
|
||||
while (Arm9PendingTicks > 0)
|
||||
{
|
||||
if (!Cpu9.Halted)
|
||||
{
|
||||
Arm9PendingTicks -= (int)Cpu9.Execute();
|
||||
}
|
||||
else
|
||||
{
|
||||
Arm9PendingTicks -= (int)(Scheduler.NextEventTicks - Scheduler.CurrentTicks) * 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ppu3D.Run();
|
||||
|
||||
Scheduler.CurrentTicks += ticks7;
|
||||
}
|
||||
|
||||
long current = Scheduler.CurrentTicks;
|
||||
long next = Scheduler.NextEventTicks;
|
||||
Scheduler.PopFirstEvent().Callback(current - next);
|
||||
|
||||
return (uint)(Scheduler.CurrentTicks - beforeTicks);
|
||||
}
|
||||
|
||||
public void DoNothing(long cyclesLate) { }
|
||||
|
||||
public void Tick(uint cycles)
|
||||
{
|
||||
Scheduler.CurrentTicks += cycles;
|
||||
}
|
||||
|
||||
public void HaltSkip(long cyclesOffset) { }
|
||||
|
||||
// POWCNT1
|
||||
public bool EnableDisplay;
|
||||
public bool Enable2DEngineA;
|
||||
public bool Enable3DRenderingEngine;
|
||||
public bool Enable3DGeometryEngine;
|
||||
public bool Enable2DEngineB;
|
||||
public bool DisplaySwap;
|
||||
|
||||
public byte ReadHwio8Arm9(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
if (EnableDisplay) val = BitSet(val, 0);
|
||||
if (Enable2DEngineA) val = BitSet(val, 1);
|
||||
if (Enable3DRenderingEngine) val = BitSet(val, 2);
|
||||
if (Enable3DGeometryEngine) val = BitSet(val, 3);
|
||||
break;
|
||||
case 0x4000305:
|
||||
if (Enable2DEngineB) val = BitSet(val, 1);
|
||||
if (DisplaySwap) val = BitSet(val, 7);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Arm9(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
EnableDisplay = BitTest(val, 0);
|
||||
Enable2DEngineA = BitTest(val, 1);
|
||||
Enable3DRenderingEngine = BitTest(val, 2);
|
||||
Enable3DGeometryEngine = BitTest(val, 3);
|
||||
break;
|
||||
case 0x4000305:
|
||||
Enable2DEngineB = BitTest(val, 1);
|
||||
DisplaySwap = BitTest(val, 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void StateChangeArm9() { }
|
||||
|
||||
// POWCNT2
|
||||
public bool EnableSpeakers;
|
||||
public bool EnableWifi;
|
||||
|
||||
public byte ReadHwio8Arm7(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
if (EnableSpeakers) val = BitSet(val, 0);
|
||||
if (EnableWifi) val = BitSet(val, 1);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Arm7(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000304:
|
||||
EnableSpeakers = BitTest(val, 0);
|
||||
EnableWifi = BitTest(val, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void StateChangeArm7() { }
|
||||
}
|
||||
}
|
11
Assets/emulator/Nds.cs.meta
Normal file
11
Assets/emulator/Nds.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4cdddf1f287aa24d91a6aa89a4bae70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
276
Assets/emulator/Nds9Math.cs
Normal file
276
Assets/emulator/Nds9Math.cs
Normal file
@ -0,0 +1,276 @@
|
||||
using System;
|
||||
using static OptimeGBA.Bits;
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public unsafe sealed class Nds9Math
|
||||
{
|
||||
public Nds Nds;
|
||||
|
||||
public Nds9Math(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
}
|
||||
|
||||
public long DIV_NUMER;
|
||||
public long DIV_DENOM;
|
||||
|
||||
public long DIV_RESULT;
|
||||
public long DIVREM_RESULT;
|
||||
|
||||
public uint SQRT_RESULT;
|
||||
public ulong SQRT_PARAM;
|
||||
|
||||
// DIVCNT
|
||||
public uint DivisionMode;
|
||||
public bool DividedByZero;
|
||||
public bool DivideBusy;
|
||||
|
||||
// SQRTCNT
|
||||
public bool SqrtUse64BitInput;
|
||||
public bool SqrtBusy;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000280: // DIVCNT B0
|
||||
val |= (byte)(DivisionMode & 0b11);
|
||||
break;
|
||||
case 0x4000281: // DIVCNT B1
|
||||
if (DividedByZero) val = BitSet(val, 6);
|
||||
if (DivideBusy) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x4000282: // DIVCNT B2
|
||||
case 0x4000283: // DIVCNT B3
|
||||
break;
|
||||
|
||||
case 0x4000290: // DIV_NUMER B0
|
||||
case 0x4000291: // DIV_NUMER B1
|
||||
case 0x4000292: // DIV_NUMER B2
|
||||
case 0x4000293: // DIV_NUMER B3
|
||||
case 0x4000294: // DIV_NUMER B4
|
||||
case 0x4000295: // DIV_NUMER B5
|
||||
case 0x4000296: // DIV_NUMER B6
|
||||
case 0x4000297: // DIV_NUMER B7
|
||||
return GetByteIn(DIV_NUMER, addr & 7);
|
||||
case 0x4000298: // DIV_DENOM B0
|
||||
case 0x4000299: // DIV_DENOM B1
|
||||
case 0x400029A: // DIV_DENOM B2
|
||||
case 0x400029B: // DIV_DENOM B3
|
||||
case 0x400029C: // DIV_DENOM B4
|
||||
case 0x400029D: // DIV_DENOM B5
|
||||
case 0x400029E: // DIV_DENOM B6
|
||||
case 0x400029F: // DIV_DENOM B7
|
||||
return GetByteIn(DIV_DENOM, addr & 7);
|
||||
case 0x40002A0: // DIV_RESULT B0
|
||||
case 0x40002A1: // DIV_RESULT B1
|
||||
case 0x40002A2: // DIV_RESULT B2
|
||||
case 0x40002A3: // DIV_RESULT B3
|
||||
case 0x40002A4: // DIV_RESULT B4
|
||||
case 0x40002A5: // DIV_RESULT B5
|
||||
case 0x40002A6: // DIV_RESULT B6
|
||||
case 0x40002A7: // DIV_RESULT B7
|
||||
return GetByteIn(DIV_RESULT, addr & 7);
|
||||
case 0x40002A8: // DIVREM_RESULT B0
|
||||
case 0x40002A9: // DIVREM_RESULT B1
|
||||
case 0x40002AA: // DIVREM_RESULT B2
|
||||
case 0x40002AB: // DIVREM_RESULT B3
|
||||
case 0x40002AC: // DIVREM_RESULT B4
|
||||
case 0x40002AD: // DIVREM_RESULT B5
|
||||
case 0x40002AE: // DIVREM_RESULT B6
|
||||
case 0x40002AF: // DIVREM_RESULT B7
|
||||
return GetByteIn(DIVREM_RESULT, addr & 7);
|
||||
|
||||
case 0x40002B0: // SQRTCNT B0
|
||||
if (SqrtUse64BitInput) val = BitSet(val, 0);
|
||||
break;
|
||||
case 0x40002B1: // SQRTCNT B0
|
||||
break;
|
||||
|
||||
case 0x40002B4: // SQRT_RESULT B0
|
||||
case 0x40002B5: // SQRT_RESULT B1
|
||||
case 0x40002B6: // SQRT_RESULT B2
|
||||
case 0x40002B7: // SQRT_RESULT B3
|
||||
return GetByteIn(SQRT_RESULT, addr & 3);
|
||||
case 0x40002B8: // SQRT_PARAM B0
|
||||
case 0x40002B9: // SQRT_PARAM B1
|
||||
case 0x40002BA: // SQRT_PARAM B2
|
||||
case 0x40002BB: // SQRT_PARAM B3
|
||||
case 0x40002BC: // SQRT_PARAM B4
|
||||
case 0x40002BD: // SQRT_PARAM B5
|
||||
case 0x40002BE: // SQRT_PARAM B6
|
||||
case 0x40002BF: // SQRT_PARAM B7
|
||||
return GetByteIn(SQRT_PARAM, addr & 7);
|
||||
|
||||
default:
|
||||
throw new NotImplementedException("Read from DS math @ " + Util.Hex(addr, 8));
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000280: // DIVCNT B0
|
||||
DivisionMode = (byte)(val & 0b11);
|
||||
Divide();
|
||||
break;
|
||||
case 0x4000281: // DIVCNT B1
|
||||
case 0x4000282: // DIVCNT B2
|
||||
case 0x4000283: // DIVCNT B3
|
||||
break;
|
||||
|
||||
case 0x4000290: // DIV_NUMER B0
|
||||
case 0x4000291: // DIV_NUMER B1
|
||||
case 0x4000292: // DIV_NUMER B2
|
||||
case 0x4000293: // DIV_NUMER B3
|
||||
case 0x4000294: // DIV_NUMER B4
|
||||
case 0x4000295: // DIV_NUMER B5
|
||||
case 0x4000296: // DIV_NUMER B6
|
||||
case 0x4000297: // DIV_NUMER B7
|
||||
DIV_NUMER = SetByteIn(DIV_NUMER, val, addr & 7);
|
||||
Divide();
|
||||
break;
|
||||
case 0x4000298: // DIV_DENOM B0
|
||||
case 0x4000299: // DIV_DENOM B1
|
||||
case 0x400029A: // DIV_DENOM B2
|
||||
case 0x400029B: // DIV_DENOM B3
|
||||
case 0x400029C: // DIV_DENOM B4
|
||||
case 0x400029D: // DIV_DENOM B5
|
||||
case 0x400029E: // DIV_DENOM B6
|
||||
case 0x400029F: // DIV_DENOM B7
|
||||
DIV_DENOM = SetByteIn(DIV_DENOM, val, addr & 7);
|
||||
Divide();
|
||||
break;
|
||||
|
||||
case 0x40002B0: // SQRTCNT B0
|
||||
SqrtUse64BitInput = BitTest(val, 0);
|
||||
TakeSquareRoot();
|
||||
break;
|
||||
case 0x40002B1: // SQRTCNT B0
|
||||
break;
|
||||
|
||||
case 0x40002B8: // SQRT_PARAM B0
|
||||
case 0x40002B9: // SQRT_PARAM B1
|
||||
case 0x40002BA: // SQRT_PARAM B2
|
||||
case 0x40002BB: // SQRT_PARAM B3
|
||||
case 0x40002BC: // SQRT_PARAM B4
|
||||
case 0x40002BD: // SQRT_PARAM B5
|
||||
case 0x40002BE: // SQRT_PARAM B6
|
||||
case 0x40002BF: // SQRT_PARAM B7
|
||||
SQRT_PARAM = SetByteIn(SQRT_PARAM, val, addr & 7);
|
||||
TakeSquareRoot();
|
||||
return;
|
||||
|
||||
// default:
|
||||
// throw new NotImplementedException("Write to DS math @ " + Util.Hex(addr, 8));
|
||||
}
|
||||
}
|
||||
|
||||
public void Divide()
|
||||
{
|
||||
DividedByZero = DIV_DENOM == 0;
|
||||
|
||||
switch (DivisionMode)
|
||||
{
|
||||
case 0: // 32bit / 32bit
|
||||
if ((int)DIV_NUMER == int.MinValue && (int)DIV_DENOM == -1) // Overflow
|
||||
{
|
||||
DIV_RESULT = (long)(int)DIV_NUMER ^ (0xFFFFFFFFL << 32);
|
||||
DIVREM_RESULT = 0;
|
||||
}
|
||||
else if ((int)DIV_DENOM != 0)
|
||||
{
|
||||
DIV_RESULT = (int)DIV_NUMER / (int)DIV_DENOM;
|
||||
DIVREM_RESULT = (int)DIV_NUMER % (int)DIV_DENOM;
|
||||
}
|
||||
else // Division by 0
|
||||
{
|
||||
DIV_RESULT = (((int)DIV_NUMER < 0) ? 1 : -1) ^ (0xFFFFFFFFL << 32);
|
||||
DIVREM_RESULT = (int)DIV_NUMER;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
case 1: // 64bit / 32bit
|
||||
if (DIV_NUMER == long.MinValue && (int)DIV_DENOM == -1) // Overflow
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER;
|
||||
DIVREM_RESULT = 0;
|
||||
}
|
||||
else if ((int)DIV_DENOM != 0)
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER / (int)DIV_DENOM;
|
||||
DIVREM_RESULT = DIV_NUMER % (int)DIV_DENOM;
|
||||
}
|
||||
else // Division by 0
|
||||
{
|
||||
DIV_RESULT = (DIV_NUMER < 0) ? 1 : -1;
|
||||
DIVREM_RESULT = DIV_NUMER;
|
||||
}
|
||||
break;
|
||||
case 2: // 64bit / 64bit
|
||||
if (DIV_NUMER == long.MinValue && DIV_DENOM == -1) // Overflow
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER;
|
||||
DIVREM_RESULT = 0;
|
||||
}
|
||||
else if (DIV_DENOM != 0)
|
||||
{
|
||||
DIV_RESULT = DIV_NUMER / DIV_DENOM;
|
||||
DIVREM_RESULT = DIV_NUMER % DIV_DENOM;
|
||||
}
|
||||
else // Division by 0
|
||||
{
|
||||
DIV_RESULT = (DIV_NUMER < 0) ? 1 : -1;
|
||||
DIVREM_RESULT = DIV_NUMER;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Console.WriteLine("Divison Mode: " + DivisionMode);
|
||||
// Console.WriteLine("Numerator : " + DIV_NUMER);
|
||||
// Console.WriteLine("Demoninator: " + DIV_DENOM);
|
||||
// Console.WriteLine("Result : " + DIV_RESULT);
|
||||
// Console.WriteLine("Remainder : " + DIVREM_RESULT);
|
||||
}
|
||||
|
||||
public void TakeSquareRoot()
|
||||
{
|
||||
if (SqrtUse64BitInput)
|
||||
{
|
||||
ulong val = SQRT_PARAM;
|
||||
|
||||
uint final = 0;
|
||||
ulong rem = 0;
|
||||
uint prod = 0;
|
||||
|
||||
const uint nbits = 32;
|
||||
const int topShift = 62;
|
||||
|
||||
for (int i = 0; i < nbits; i++)
|
||||
{
|
||||
rem = (rem << 2) + ((val >> topShift) & 0x3);
|
||||
val <<= 2;
|
||||
final <<= 1;
|
||||
|
||||
prod = (final << 1) + 1;
|
||||
if (rem >= prod)
|
||||
{
|
||||
rem -= prod;
|
||||
final++;
|
||||
}
|
||||
}
|
||||
|
||||
SQRT_RESULT = final;
|
||||
}
|
||||
else
|
||||
{
|
||||
SQRT_RESULT = (uint)Math.Sqrt((uint)SQRT_PARAM);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/Nds9Math.cs.meta
Normal file
11
Assets/emulator/Nds9Math.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 969688026ca7e194dbc2b0acc79a84d9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
525
Assets/emulator/NdsAudio.cs
Normal file
525
Assets/emulator/NdsAudio.cs
Normal file
@ -0,0 +1,525 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using static OptimeGBA.MemoryUtil;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public class CustomSample
|
||||
{
|
||||
public short[] Data; // In PCM16
|
||||
public uint LoopPoint; // In samples
|
||||
public uint RepeatMode;
|
||||
|
||||
public CustomSample(short[] data, uint loopPoint, uint repeatMode)
|
||||
{
|
||||
Data = data;
|
||||
LoopPoint = loopPoint;
|
||||
RepeatMode = repeatMode;
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioChannelNds
|
||||
{
|
||||
// SOUNDxCNT
|
||||
public uint Volume;
|
||||
public byte VolumeDiv;
|
||||
public bool Hold;
|
||||
public byte Pan;
|
||||
public byte PulseDuty;
|
||||
public byte RepeatMode;
|
||||
public byte Format;
|
||||
public bool Playing;
|
||||
|
||||
public uint SOUNDSAD;
|
||||
public uint SOUNDTMR;
|
||||
public uint SOUNDPNT;
|
||||
public uint SOUNDLEN;
|
||||
|
||||
public uint SamplePos;
|
||||
public uint Timer;
|
||||
|
||||
public uint Interval;
|
||||
public int CurrentValue;
|
||||
public int AdpcmIndex;
|
||||
public int AdpcmLoopValue;
|
||||
public int AdpcmLoopIndex;
|
||||
public uint AdpcmLoopCurrentData;
|
||||
public uint CurrentData;
|
||||
|
||||
public bool DebugEnable = true;
|
||||
public uint DebugAdpcmSaved;
|
||||
public uint DebugAdpcmRestored;
|
||||
|
||||
public long DebugStartTicks;
|
||||
}
|
||||
|
||||
public class NdsAudio
|
||||
{
|
||||
Nds Nds;
|
||||
|
||||
public NdsAudio(Nds nds)
|
||||
{
|
||||
Nds = nds;
|
||||
|
||||
for (uint i = 0; i < 16; i++)
|
||||
{
|
||||
Channels[i] = new AudioChannelNds();
|
||||
}
|
||||
|
||||
Sample(0);
|
||||
}
|
||||
|
||||
public AudioChannelNds[] Channels = new AudioChannelNds[16];
|
||||
|
||||
public const int SampleRate = 32768;
|
||||
|
||||
public bool EnableBlipBufResampling = true;
|
||||
public BlipBuf BlipBuf = new BlipBuf(32, true, 16);
|
||||
|
||||
public bool Record = false;
|
||||
public WavWriter WavWriter = new WavWriter(SampleRate);
|
||||
public WavWriter WavWriterSinc = new WavWriter(SampleRate);
|
||||
|
||||
public int SampleTimer = 0;
|
||||
|
||||
public const uint SampleBufferMax = 256;
|
||||
public short[] SampleBuffer = new short[SampleBufferMax];
|
||||
public uint SampleBufferPos = 0;
|
||||
|
||||
public static sbyte[] IndexTable = { -1, -1, -1, -1, 2, 4, 6, 8 };
|
||||
public static short[] AdpcmTable = {
|
||||
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0010, 0x0011, 0x0013, 0x0015,
|
||||
0x0017, 0x0019, 0x001C, 0x001F, 0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
|
||||
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F, 0x009D, 0x00AD, 0x00BE, 0x00D1,
|
||||
0x00E6, 0x00FD, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
|
||||
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583, 0x0610, 0x06AB, 0x0756, 0x0812,
|
||||
0x08E0, 0x09C3, 0x0ABD, 0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
|
||||
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9, 0x41B2, 0x4844, 0x4F7E,
|
||||
0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF
|
||||
};
|
||||
|
||||
// SOUNDCNT
|
||||
public byte MasterVolume;
|
||||
public uint LeftOutputFrom;
|
||||
public uint RightOutputFrom;
|
||||
public bool Ch1ToMixer;
|
||||
public bool Ch3ToMixer;
|
||||
public bool MasterEnable;
|
||||
|
||||
// SOUNDBIAS
|
||||
public ushort SOUNDBIAS;
|
||||
|
||||
// SNDCAPCNT
|
||||
byte SNDCAP0CNT;
|
||||
byte SNDCAP1CNT;
|
||||
|
||||
public byte ReadHwio8(uint addr)
|
||||
{
|
||||
byte val = 0;
|
||||
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000500: // SOUNDCNT B0
|
||||
val |= (byte)(MasterVolume & 0x7FU);
|
||||
break;
|
||||
case 0x4000501: // SOUNDCNT B1
|
||||
val |= (byte)((LeftOutputFrom & 0b11) << 0);
|
||||
val |= (byte)((RightOutputFrom & 0b11) << 2);
|
||||
if (Ch1ToMixer) val = BitSet(val, 4);
|
||||
if (Ch3ToMixer) val = BitSet(val, 5);
|
||||
if (MasterEnable) val = BitSet(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000504:
|
||||
return (byte)(SOUNDBIAS >> 0);
|
||||
case 0x4000505:
|
||||
return (byte)(SOUNDBIAS >> 8);
|
||||
|
||||
case 0x4000508:
|
||||
return SNDCAP0CNT;
|
||||
case 0x4000509:
|
||||
return SNDCAP1CNT;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x4000500: // SOUNDCNT B0
|
||||
MasterVolume = (byte)(val & 0x7FU);
|
||||
break;
|
||||
case 0x4000501: // SOUNDCNT B1
|
||||
LeftOutputFrom = (byte)((val >> 0) & 0b11);
|
||||
RightOutputFrom = (byte)((val >> 2) & 0b11);
|
||||
Ch1ToMixer = BitTest(val, 4);
|
||||
Ch3ToMixer = BitTest(val, 5);
|
||||
MasterEnable = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4000504:
|
||||
SOUNDBIAS &= 0xFF00;
|
||||
SOUNDBIAS |= (ushort)(val << 0);
|
||||
break;
|
||||
case 0x4000505:
|
||||
SOUNDBIAS &= 0x00FF;
|
||||
SOUNDBIAS |= (ushort)(val << 8);
|
||||
break;
|
||||
|
||||
case 0x4000508:
|
||||
SNDCAP0CNT = val;
|
||||
break;
|
||||
case 0x4000509:
|
||||
SNDCAP1CNT = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public byte ReadHwio8Channels(uint addr)
|
||||
{
|
||||
var c = Channels[(addr >> 4) & 0xF];
|
||||
|
||||
byte val = 0;
|
||||
|
||||
switch (addr & 0xF)
|
||||
{
|
||||
case 0x0:
|
||||
val |= (byte)(c.Volume & 0x7F);
|
||||
break;
|
||||
case 0x1:
|
||||
val |= c.VolumeDiv;
|
||||
if (c.Hold) val = BitSet(val, 7);
|
||||
break;
|
||||
case 0x2:
|
||||
val |= c.Pan;
|
||||
break;
|
||||
case 0x3:
|
||||
val |= c.PulseDuty;
|
||||
val |= (byte)(c.RepeatMode << 3);
|
||||
val |= (byte)(c.Format << 5);
|
||||
if (c.Playing) val = BitSet(val, 7);
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void WriteHwio8Channels(uint addr, byte val)
|
||||
{
|
||||
var c = Channels[(addr >> 4) & 0xF];
|
||||
|
||||
switch (addr & 0xF)
|
||||
{
|
||||
case 0x0:
|
||||
c.Volume = (byte)(val & 0x7F);
|
||||
break;
|
||||
case 0x1:
|
||||
c.VolumeDiv = (byte)(val & 3);
|
||||
c.Hold = BitTest(val, 7);
|
||||
break;
|
||||
case 0x2:
|
||||
c.Pan = (byte)(val & 0x7F);
|
||||
break;
|
||||
case 0x3:
|
||||
c.PulseDuty = (byte)(val & 7);
|
||||
c.RepeatMode = (byte)((val >> 3) & 3);
|
||||
c.Format = (byte)((val >> 5) & 3);
|
||||
if (!c.Playing && BitTest(val, 7))
|
||||
{
|
||||
StartChannel(c);
|
||||
}
|
||||
c.Playing = BitTest(val, 7);
|
||||
break;
|
||||
|
||||
case 0x4:
|
||||
case 0x5:
|
||||
case 0x6:
|
||||
case 0x7:
|
||||
Console.WriteLine(Util.Hex(Nds.Cpu7.GetCurrentInstrAddr(), 8));
|
||||
// Console.WriteLine(Nds.MemoryControl.SharedRamControl);
|
||||
|
||||
c.SOUNDSAD = SetByteIn(c.SOUNDSAD, val, addr & 3) & 0x7FFFFFC;
|
||||
|
||||
if (c.Playing)
|
||||
{
|
||||
StartChannel(c);
|
||||
}
|
||||
break;
|
||||
case 0x8:
|
||||
case 0x9:
|
||||
c.SOUNDTMR = SetByteIn(c.SOUNDTMR, val, addr & 1);
|
||||
c.Interval = 2 * (0x10000 - c.SOUNDTMR);
|
||||
break;
|
||||
case 0xA:
|
||||
case 0xB:
|
||||
c.SOUNDPNT = SetByteIn(c.SOUNDPNT, val, addr & 1);
|
||||
break;
|
||||
case 0xC:
|
||||
case 0xD:
|
||||
case 0xE:
|
||||
case 0xF:
|
||||
c.SOUNDLEN = SetByteIn(c.SOUNDLEN, val, addr & 3) & 0x3FFFFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void StartChannel(AudioChannelNds c)
|
||||
{
|
||||
c.SamplePos = 0;
|
||||
c.Timer = 0;
|
||||
c.CurrentValue = 0;
|
||||
|
||||
c.DebugStartTicks = Nds.Scheduler.CurrentTicks;
|
||||
}
|
||||
|
||||
public void Sample(long cyclesLate)
|
||||
{
|
||||
long left = 0;
|
||||
long right = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var c = Channels[i];
|
||||
|
||||
if (c.Playing)
|
||||
{
|
||||
c.Timer += 1024;
|
||||
while (c.Timer >= c.Interval && c.Interval != 0)
|
||||
{
|
||||
c.Timer -= c.Interval;
|
||||
|
||||
// Advance sample
|
||||
switch (c.Format)
|
||||
{
|
||||
case 0: // PCM8
|
||||
if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 4)
|
||||
{
|
||||
switch (c.RepeatMode)
|
||||
{
|
||||
case 1: // Infinite
|
||||
c.SamplePos = c.SOUNDPNT * 4;
|
||||
break;
|
||||
case 2: // One-shot
|
||||
c.Playing = false;
|
||||
if (!c.Hold)
|
||||
{
|
||||
c.CurrentValue = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((c.SamplePos & 3) == 0)
|
||||
{
|
||||
c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos);
|
||||
}
|
||||
|
||||
c.CurrentValue = (short)((byte)c.CurrentData << 8);
|
||||
c.CurrentData >>= 8;
|
||||
|
||||
c.SamplePos++;
|
||||
break;
|
||||
case 1: // PCM16
|
||||
if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 2)
|
||||
{
|
||||
switch (c.RepeatMode)
|
||||
{
|
||||
case 1: // Infinite
|
||||
c.SamplePos = c.SOUNDPNT * 2;
|
||||
break;
|
||||
case 2: // One-shot
|
||||
c.Playing = false;
|
||||
if (!c.Hold)
|
||||
{
|
||||
c.CurrentValue = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((c.SamplePos & 1) == 0)
|
||||
{
|
||||
c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos * 2);
|
||||
}
|
||||
|
||||
c.CurrentValue = (short)c.CurrentData;
|
||||
c.CurrentData >>= 16;
|
||||
|
||||
c.SamplePos++;
|
||||
break;
|
||||
case 2: // IMA-ADPCM
|
||||
if ((c.SamplePos & 7) == 0)
|
||||
{
|
||||
c.CurrentData = Nds.Mem7.Read32(c.SOUNDSAD + c.SamplePos / 2);
|
||||
// ADPCM header
|
||||
if (c.SamplePos == 0)
|
||||
{
|
||||
c.CurrentValue = (short)c.CurrentData;
|
||||
// Console.WriteLine("header set " + x++);
|
||||
// Console.WriteLine("interval: " + Util.Hex(c.Interval, 8));
|
||||
c.AdpcmIndex = Math.Clamp((int)(c.CurrentData >> 16), 0, 88);
|
||||
}
|
||||
// Console.WriteLine("addr: " + Util.Hex(c.Source, 8));
|
||||
}
|
||||
if (c.SamplePos > 7)
|
||||
{
|
||||
// End of sound, loop or stop
|
||||
if (c.SamplePos >= (c.SOUNDPNT + c.SOUNDLEN) * 8)
|
||||
{
|
||||
switch (c.RepeatMode)
|
||||
{
|
||||
case 1: // Infinite
|
||||
c.SamplePos = c.SOUNDPNT * 8;
|
||||
c.CurrentValue = c.AdpcmLoopValue;
|
||||
c.AdpcmIndex = c.AdpcmLoopIndex;
|
||||
c.CurrentData = c.AdpcmLoopCurrentData;
|
||||
// Console.WriteLine($"Ch{i}: Loaded at " + c.SampleNum);
|
||||
|
||||
c.DebugAdpcmRestored = c.SamplePos;
|
||||
break;
|
||||
case 2: // One-shot
|
||||
c.Playing = false;
|
||||
if (!c.Hold)
|
||||
{
|
||||
c.CurrentValue = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
byte data = (byte)(c.CurrentData & 0xF);
|
||||
|
||||
short tableVal = AdpcmTable[c.AdpcmIndex];
|
||||
int diff = tableVal / 8;
|
||||
if ((data & 1) != 0) diff += tableVal / 4;
|
||||
if ((data & 2) != 0) diff += tableVal / 2;
|
||||
if ((data & 4) != 0) diff += tableVal / 1;
|
||||
|
||||
if ((data & 8) == 8)
|
||||
{
|
||||
c.CurrentValue = Math.Max((int)c.CurrentValue - diff, -0x7FFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
c.CurrentValue = Math.Min((int)c.CurrentValue + diff, 0x7FFF);
|
||||
}
|
||||
c.AdpcmIndex = Math.Clamp(c.AdpcmIndex + IndexTable[data & 7], 0, 88);
|
||||
|
||||
c.CurrentData >>= 4;
|
||||
|
||||
// Save value and ADPCM table index for loop
|
||||
if (c.SamplePos == c.SOUNDPNT * 8)
|
||||
{
|
||||
c.AdpcmLoopValue = c.CurrentValue;
|
||||
c.AdpcmLoopIndex = c.AdpcmIndex;
|
||||
c.AdpcmLoopCurrentData = c.CurrentData;
|
||||
|
||||
c.DebugAdpcmSaved = c.SamplePos;
|
||||
// Console.WriteLine($"Ch{i}: Saved at " + c.SampleNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
c.SamplePos++;
|
||||
break;
|
||||
case 3: // Pulse / Noise
|
||||
if (((c.SamplePos ^ 7) & 7) <= c.PulseDuty)
|
||||
{
|
||||
c.CurrentValue = -0x7FFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
c.CurrentValue = 0x7FFF;
|
||||
}
|
||||
c.SamplePos++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (EnableBlipBufResampling)
|
||||
{
|
||||
long timeTicks = Nds.Scheduler.CurrentTicks - cyclesLate + 1024 - (c.Timer % 1024);
|
||||
double timeSec = (double)timeTicks / 33513982D;
|
||||
double timeSample = (double)timeTicks / (33513982D / (double)SampleRate);
|
||||
|
||||
if (c.DebugEnable)
|
||||
{
|
||||
uint effectiveVol = c.Volume;
|
||||
if (effectiveVol == 127) effectiveVol++;
|
||||
long leftCh = ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * (127 - c.Pan)) >> 10;
|
||||
long rightCh = ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * c.Pan) >> 10;
|
||||
|
||||
BlipBuf.SetValue(i, timeSample, leftCh, rightCh);
|
||||
}
|
||||
else
|
||||
{
|
||||
BlipBuf.SetValue(i, timeSample, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c.DebugEnable)
|
||||
{
|
||||
uint effectiveVol = c.Volume;
|
||||
if (effectiveVol == 127) effectiveVol++;
|
||||
left += ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * (127 - c.Pan)) >> 10;
|
||||
right += ((((long)c.CurrentValue * (16 >> c.VolumeDiv)) * effectiveVol) * c.Pan) >> 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Decimate samples to 32768 hz
|
||||
// Since 33513982 hz / 1024 ≅ 32728.498 hz
|
||||
SampleTimer += SampleRate * 1024;
|
||||
while (SampleTimer >= 33513982)
|
||||
{
|
||||
SampleTimer -= 33513982;
|
||||
|
||||
BlipBuf.ReadOutSample();
|
||||
|
||||
// 28 bits now, after mixing all channels
|
||||
// add master volume to get 35 bits
|
||||
// add
|
||||
// strip 19 to get 16 bits for our short output
|
||||
uint effectiveMasterVol = MasterVolume;
|
||||
if (effectiveMasterVol == 127) effectiveMasterVol++;
|
||||
|
||||
short leftFinalSinc = (short)(((long)BlipBuf.CurrentValL * effectiveMasterVol) >> 16);
|
||||
short rightFinalSinc = (short)(((long)BlipBuf.CurrentValR * effectiveMasterVol) >> 16);
|
||||
|
||||
short leftFinal = (short)((left * effectiveMasterVol) >> 16);
|
||||
short rightFinal = (short)((right * effectiveMasterVol) >> 16);
|
||||
|
||||
if (EnableBlipBufResampling)
|
||||
{
|
||||
SampleBuffer[SampleBufferPos++] = leftFinalSinc;
|
||||
SampleBuffer[SampleBufferPos++] = rightFinalSinc;
|
||||
}
|
||||
else
|
||||
{
|
||||
SampleBuffer[SampleBufferPos++] = leftFinal;
|
||||
SampleBuffer[SampleBufferPos++] = rightFinal;
|
||||
}
|
||||
|
||||
if (Record)
|
||||
{
|
||||
WavWriterSinc.AddSample(leftFinalSinc, rightFinalSinc);
|
||||
WavWriter.AddSample(leftFinal, rightFinal);
|
||||
}
|
||||
|
||||
if (SampleBufferPos >= SampleBufferMax)
|
||||
{
|
||||
SampleBufferPos = 0;
|
||||
|
||||
Nds.Provider.AudioCallback(SampleBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
Nds.Scheduler.AddEventRelative(SchedulerId.ApuSample, 1024 - cyclesLate, Sample);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/emulator/NdsAudio.cs.meta
Normal file
11
Assets/emulator/NdsAudio.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aac09fde41176a14b888ed3e2d5d4676
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
261
Assets/emulator/Ppu.cs
Normal file
261
Assets/emulator/Ppu.cs
Normal file
@ -0,0 +1,261 @@
|
||||
using static OptimeGBA.Bits;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace OptimeGBA
|
||||
{
|
||||
public enum BackgroundMode
|
||||
{
|
||||
Char,
|
||||
Display3D,
|
||||
Affine,
|
||||
Extended,
|
||||
Large,
|
||||
|
||||
// Extended
|
||||
Affine16BitBgMapEntries,
|
||||
Affine256ColorBitmap,
|
||||
AffineFullColorBitmap,
|
||||
}
|
||||
public sealed class Background
|
||||
{
|
||||
bool Nds;
|
||||
|
||||
public Background(bool nds, byte id)
|
||||
{
|
||||
Nds = nds;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
byte[] BGCNTValue = new byte[2];
|
||||
|
||||
// BGCNT
|
||||
public byte Priority = 0;
|
||||
public uint CharBaseBlock = 0;
|
||||
public bool EnableMosaic = false;
|
||||
public bool Use8BitColor = false;
|
||||
public uint MapBaseBlock = 0;
|
||||
public bool OverflowWrap = false;
|
||||
public uint ScreenSize = 0;
|
||||
|
||||
// BGCNT NDS
|
||||
public bool AffineBitmap;
|
||||
public bool AffineBitmapFullColor;
|
||||
|
||||
// BGH/VOFS
|
||||
public uint HorizontalOffset;
|
||||
public uint VerticalOffset;
|
||||
|
||||
public byte Id;
|
||||
|
||||
public uint RefPointX;
|
||||
public uint RefPointY;
|
||||
|
||||
public short AffineA;
|
||||
public short AffineB;
|
||||
public short AffineC;
|
||||
public short AffineD;
|
||||
|
||||
public int AffinePosX;
|
||||
public int AffinePosY;
|
||||
|
||||
// Set by PrepareBackgroundAndWindow() and used by RenderBgModes()
|
||||
public BackgroundMode Mode;
|
||||
|
||||
public byte ReadBGCNT(uint addr)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x00: // BGCNT B0
|
||||
return BGCNTValue[0];
|
||||
case 0x01: // BGCNT B1
|
||||
return BGCNTValue[1];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void WriteBGCNT(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x00: // BGCNT B0
|
||||
Priority = (byte)((val >> 0) & 0b11);
|
||||
|
||||
// These bits overlay other bits on NDS
|
||||
AffineBitmap = BitTest(val, 7);
|
||||
AffineBitmapFullColor = BitTest(val, 2);
|
||||
|
||||
EnableMosaic = BitTest(val, 6);
|
||||
if (!Nds)
|
||||
{
|
||||
CharBaseBlock = (uint)(val >> 2) & 0b11;
|
||||
}
|
||||
else
|
||||
{
|
||||
CharBaseBlock = (uint)(val >> 2) & 0b1111;
|
||||
}
|
||||
EnableMosaic = BitTest(val, 6);
|
||||
Use8BitColor = BitTest(val, 7);
|
||||
|
||||
BGCNTValue[0] = val;
|
||||
break;
|
||||
case 0x01: // BGCNT B1
|
||||
MapBaseBlock = (uint)(val >> 0) & 0b11111;
|
||||
OverflowWrap = BitTest(val, 5);
|
||||
ScreenSize = (uint)(val >> 6) & 0b11;
|
||||
|
||||
BGCNTValue[1] = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteBGOFS(uint addr, byte val)
|
||||
{
|
||||
switch (addr)
|
||||
{
|
||||
case 0x0: // BGHOFS B0
|
||||
HorizontalOffset &= ~0x0FFu;
|
||||
HorizontalOffset |= (uint)((val << 0) & 0x0FFu);
|
||||
break;
|
||||
case 0x1: // BGHOFS B1
|
||||
HorizontalOffset &= ~0x100u;
|
||||
HorizontalOffset |= (uint)((val << 8) & 0x100u);
|
||||
break;
|
||||
|
||||
case 0x2: // BGVOFS B0
|
||||
VerticalOffset &= ~0x0FFu;
|
||||
VerticalOffset |= (uint)((val << 0) & 0x0FFu);
|
||||
break;
|
||||
case 0x3: // BGVOFS B1
|
||||
VerticalOffset &= ~0x100u;
|
||||
VerticalOffset |= (uint)((val << 8) & 0x100u);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteBGXY(uint addr, byte val)
|
||||
{
|
||||
byte offset = (byte)((addr & 3) * 8);
|
||||
switch (addr)
|
||||
{
|
||||
case 0x0: // BGX_L
|
||||
case 0x1: // BGX_L
|
||||
case 0x2: // BGX_H
|
||||
case 0x3: // BGX_H
|
||||
RefPointX &= ~(0xFFu << offset);
|
||||
RefPointX |= (uint)(val << offset);
|
||||
break;
|
||||
|
||||
case 0x4: // BGY_L
|
||||
case 0x5: // BGY_L
|
||||
case 0x6: // BGY_H
|
||||
case 0x7: // BGY_H
|
||||
RefPointY &= ~(0xFFu << offset);
|
||||
RefPointY |= (uint)(val << offset);
|
||||
break;
|
||||
}
|
||||
|
||||
CopyAffineParams();
|
||||
}
|
||||
|
||||
public void CopyAffineParams()
|
||||
{
|
||||
// also sign extend
|
||||
AffinePosX = ((int)RefPointX << 4) >> 4;
|
||||
AffinePosY = ((int)RefPointY << 4) >> 4;
|
||||
}
|
||||
|
||||
public void WriteBGPX(uint addr, byte val)
|
||||
{
|
||||
byte offset = (byte)((addr & 1) * 8);
|
||||
switch (addr)
|
||||
{
|
||||
case 0x0: // BGPA B0
|
||||
case 0x1: // BGPA B1
|
||||
AffineA &= (short)~(0xFFu << offset);
|
||||
AffineA |= (short)(val << offset);
|
||||
break;
|
||||
case 0x2: // BGPB B0
|
||||
case 0x3: // BGPB B1
|
||||
AffineB &= (short)~(0xFFu << offset);
|
||||
AffineB |= (short)(val << offset);
|
||||
break;
|
||||
|
||||
case 0x4: // BGPC B0
|
||||
case 0x5: // BGPC B1
|
||||
AffineC &= (short)~(0xFFu << offset);
|
||||
AffineC |= (short)(val << offset);
|
||||
break;
|
||||
case 0x6: // BGPD B0
|
||||
case 0x7: // BGPD B1
|
||||
AffineD &= (short)~(0xFFu << offset);
|
||||
AffineD |= (short)(val << offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata used for rendering
|
||||
public ushort GetMeta()
|
||||
{
|
||||
return (ushort)((Priority << 8) | (1 << Id));
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 4)]
|
||||
public struct ObjPixel
|
||||
{
|
||||
public ushort Color;
|
||||
public byte PaletteIndex;
|
||||
public byte Priority;
|
||||
public ObjMode Mode;
|
||||
|
||||
public ObjPixel(ushort color, byte paletteIndex, byte priority, ObjMode transparent)
|
||||
{
|
||||
Color = color;
|
||||
PaletteIndex = paletteIndex;
|
||||
Priority = priority;
|
||||
Mode = transparent;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ObjShape
|
||||
{
|
||||
Square = 0,
|
||||
Horizontal = 1,
|
||||
Vertical = 2,
|
||||
}
|
||||
|
||||
public enum ObjMode : byte
|
||||
{
|
||||
Normal = 0,
|
||||
Translucent = 1,
|
||||
ObjWindow = 2,
|
||||
}
|
||||
|
||||
public enum BlendEffect
|
||||
{
|
||||
None = 0,
|
||||
Blend = 1,
|
||||
Lighten = 2,
|
||||
Darken = 3,
|
||||
}
|
||||
|
||||
public enum BlendFlag : byte
|
||||
{
|
||||
Bg0 = 1 << 0,
|
||||
Bg1 = 1 << 1,
|
||||
Bg2 = 1 << 2,
|
||||
Bg3 = 1 << 3,
|
||||
Obj = 1 << 4,
|
||||
Backdrop = 1 << 5,
|
||||
}
|
||||
|
||||
public enum WindowFlag : byte
|
||||
{
|
||||
Bg0 = 1 << 0,
|
||||
Bg1 = 1 << 1,
|
||||
Bg2 = 1 << 2,
|
||||
Bg3 = 1 << 3,
|
||||
Obj = 1 << 4,
|
||||
ColorMath = 1 << 5,
|
||||
}
|
||||
}
|
11
Assets/emulator/Ppu.cs.meta
Normal file
11
Assets/emulator/Ppu.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01c48e13c41f22a45ab867df39d272b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user