770 lines
29 KiB
C#
770 lines
29 KiB
C#
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);
|
|
}
|
|
}
|
|
} |