Cores/Nds/NDS.Wifi.cs
namespace Emulotl.Nds;

public sealed class Wifi
{
	private const int W_ID = 0x000;
	private const int W_ModeReset = 0x004;
	private const int W_ModeWEP = 0x006;
	private const int W_TXStatCnt = 0x008;
	private const int W_IF = 0x010;
	private const int W_IE = 0x012;
	private const int W_MACAddr0 = 0x018;
	private const int W_MACAddr1 = 0x01A;
	private const int W_MACAddr2 = 0x01C;
	private const int W_BSSID0 = 0x020;
	private const int W_BSSID1 = 0x022;
	private const int W_BSSID2 = 0x024;
	private const int W_AIDLow = 0x028;
	private const int W_AIDFull = 0x02A;
	private const int W_TXRetryLimit = 0x02C;
	private const int W_RXCnt = 0x030;
	private const int W_WEPCnt = 0x032;
	private const int W_TRXPower = 0x034;
	private const int W_PowerUS = 0x036;
	private const int W_PowerTX = 0x038;
	private const int W_PowerState = 0x03C;
	private const int W_PowerForce = 0x040;
	private const int W_PowerDownCtrl = 0x048;
	private const int W_Random = 0x044;
	private const int W_RXBufBegin = 0x050;
	private const int W_RXBufEnd = 0x052;
	private const int W_RXBufWriteCursor = 0x054;
	private const int W_RXBufWriteAddr = 0x056;
	private const int W_RXBufReadAddr = 0x058;
	private const int W_RXBufReadCursor = 0x05A;
	private const int W_RXBufCount = 0x05C;
	private const int W_RXBufDataRead = 0x060;
	private const int W_RXBufGapAddr = 0x062;
	private const int W_RXBufGapSize = 0x064;
	private const int W_TXBufWriteAddr = 0x068;
	private const int W_TXBufCount = 0x06C;
	private const int W_TXBufDataWrite = 0x070;
	private const int W_TXBufGapAddr = 0x074;
	private const int W_TXBufGapSize = 0x076;
	private const int W_TXSlotBeacon = 0x080;
	private const int W_TXBeaconTIM = 0x084;
	private const int W_ListenCount = 0x088;
	private const int W_BeaconInterval = 0x08C;
	private const int W_ListenInterval = 0x08E;
	private const int W_TXSlotCmd = 0x090;
	private const int W_TXSlotReply1 = 0x094;
	private const int W_TXSlotReply2 = 0x098;
	private const int W_TXSlotLoc1 = 0x0A0;
	private const int W_TXSlotLoc2 = 0x0A4;
	private const int W_TXSlotLoc3 = 0x0A8;
	private const int W_TXReqReset = 0x0AC;
	private const int W_TXReqSet = 0x0AE;
	private const int W_TXReqRead = 0x0B0;
	private const int W_TXSlotReset = 0x0B4;
	private const int W_TXBusy = 0x0B6;
	private const int W_TXStat = 0x0B8;
	private const int W_Preamble = 0x0BC;
	private const int W_CmdTotalTime = 0x0C0;
	private const int W_CmdReplyTime = 0x0C4;
	private const int W_RXFilter = 0x0D0;
	private const int W_RXLenCrop = 0x0DA;
	private const int W_RXFilter2 = 0x0E0;
	private const int W_USCountCnt = 0x0E8;
	private const int W_USCompareCnt = 0x0EA;
	private const int W_CmdCountCnt = 0x0EE;
	private const int W_USCount0 = 0x0F8;
	private const int W_USCount1 = 0x0FA;
	private const int W_USCount2 = 0x0FC;
	private const int W_USCount3 = 0x0FE;
	private const int W_USCompare0 = 0x0F0;
	private const int W_USCompare1 = 0x0F2;
	private const int W_USCompare2 = 0x0F4;
	private const int W_USCompare3 = 0x0F6;
	private const int W_ContentFree = 0x10C;
	private const int W_PreBeacon = 0x110;
	private const int W_CmdCount = 0x118;
	private const int W_BeaconCount1 = 0x11C;
	private const int W_BeaconCount2 = 0x134;
	private const int W_BBCnt = 0x158;
	private const int W_BBWrite = 0x15A;
	private const int W_BBRead = 0x15C;
	private const int W_BBBusy = 0x15E;
	private const int W_BBMode = 0x160;
	private const int W_BBPower = 0x168;
	private const int W_RFData2 = 0x17C;
	private const int W_RFData1 = 0x17E;
	private const int W_RFBusy = 0x180;
	private const int W_RFCnt = 0x184;
	private const int W_TXHeaderCnt = 0x194;
	private const int W_RFPins = 0x19C;
	private const int W_TXErrorCount = 0x1C0;
	private const int W_RXCount = 0x1C4;
	private const int W_CMDStat0 = 0x1D0;
	private const int W_TXSeqNo = 0x210;
	private const int W_RFStatus = 0x214;
	private const int W_IFSet = 0x21C;
	private const int W_RXTXAddr = 0x268;

	private readonly NDS _nds;

	private readonly byte[] RAM = new byte[0x2000];
	private readonly ushort[] IO = new ushort[0x1000 >> 1];

	private static readonly byte[] MPCmdMAC = { 0x03, 0x09, 0xBF, 0x00, 0x00, 0x00 };
	private static readonly byte[] MPReplyMAC = { 0x03, 0x09, 0xBF, 0x00, 0x00, 0x10 };
	private static readonly byte[] MPAckMAC = { 0x03, 0x09, 0xBF, 0x00, 0x00, 0x03 };

	private const int kTimerInterval = 8;
	private const uint kTimeCheckMask = 0xFFFFFFF8;

	private bool Enabled;
	private bool PowerOn;

	private int TimerError;

	private ushort Random;

	private ulong USTimestamp;

	private ulong USCounter;
	private ulong USCompare;
	private bool BlockBeaconIRQ14;

	private uint CmdCounter;

	private readonly byte[] BBRegs = new byte[0x100];
	private readonly byte[] BBRegsRO = new byte[0x100];

	private byte RFVersion;
	private readonly uint[] RFRegs = new uint[0x40];

	private readonly uint[] RFChannelIndex = new uint[2];
	private readonly uint[,] RFChannelData = new uint[14, 2];
	private int CurChannel;

	private struct TXSlot
	{
		public bool Valid;
		public ushort Addr;
		public ushort Length;
		public byte Rate;
		public byte CurPhase;
		public int CurPhaseTime;
		public uint HalfwordTimeMask;
	}

	private readonly TXSlot[] TXSlots = new TXSlot[6];
	private readonly byte[] TXBuffer = new byte[0x2000];

	private readonly byte[] RXBuffer = new byte[2048];
	private uint RXBufferPtr;
	private int RXTime;
	private uint RXHalfwordTimeMask;

	private uint ComStatus;
	private uint TXCurSlot;
	private uint RXCounter;

	private int MPReplyTimer;
	private ushort MPClientMask, MPClientFail;

	private readonly byte[] MPClientReplies = new byte[15 * 1024];

	private ushort MPLastSeqno;

	private int USUntilPowerOn;

	private bool IsMP;
	private bool IsMPClient;
	private ulong NextSync;
	private ulong RXTimestamp;

	public Wifi( NDS nds )
	{
		_nds = nds;
	}

	private ref ushort IOPORT( int x ) => ref IO[x >> 1];

	private static ushort R16( byte[] b, int a ) => (ushort)(b[a] | (b[a + 1] << 8));
	private static void W16( byte[] b, int a, ushort v ) { b[a] = (byte)v; b[a + 1] = (byte)(v >> 8); }
	private static uint R32( byte[] b, int a ) => (uint)(b[a] | (b[a + 1] << 8) | (b[a + 2] << 16) | (b[a + 3] << 24));
	private static void W32( byte[] b, int a, uint v ) { b[a] = (byte)v; b[a + 1] = (byte)(v >> 8); b[a + 2] = (byte)(v >> 16); b[a + 3] = (byte)(v >> 24); }
	private static ulong R64( byte[] b, int a ) => R32( b, a ) | ((ulong)R32( b, a + 4 ) << 32);

	private static bool MACEqual( byte[] a, int ao, byte[] b, int bo )
	{
		for ( int i = 0; i < 6; i++ )
			if ( a[ao + i] != b[bo + i] ) return false;
		return true;
	}

	private byte IOByte( int byteAddr ) => (byte)(IO[byteAddr >> 1] >> ((byteAddr & 1) * 8));

	private bool MACEqualIO( byte[] buf, int off, int ioByteAddr )
	{
		for ( int i = 0; i < 6; i++ )
			if ( buf[off + i] != IOByte( ioByteAddr + i ) ) return false;
		return true;
	}

	public void Reset()
	{
		Array.Clear( RAM, 0, 0x2000 );
		Array.Clear( IO, 0, IO.Length );

		Enabled = false;
		PowerOn = false;

		Random = 1;

		Array.Clear( BBRegs, 0, 0x100 );
		Array.Clear( BBRegsRO, 0, 0x100 );

		void BBRegFixed( int id, byte val ) { BBRegs[id] = val; BBRegsRO[id] = 1; }
		BBRegFixed( 0x00, 0x6D );
		BBRegFixed( 0x0D, 0x00 );
		BBRegFixed( 0x0E, 0x00 );
		BBRegFixed( 0x0F, 0x00 );
		BBRegFixed( 0x10, 0x00 );
		BBRegFixed( 0x11, 0x00 );
		BBRegFixed( 0x12, 0x00 );
		BBRegFixed( 0x16, 0x00 );
		BBRegFixed( 0x17, 0x00 );
		BBRegFixed( 0x18, 0x00 );
		BBRegFixed( 0x19, 0x00 );
		BBRegFixed( 0x1A, 0x00 );
		BBRegFixed( 0x27, 0x00 );
		BBRegFixed( 0x4D, 0x00 );
		BBRegFixed( 0x5D, 0x01 );
		BBRegFixed( 0x5E, 0x00 );
		BBRegFixed( 0x5F, 0x00 );
		BBRegFixed( 0x60, 0x00 );
		BBRegFixed( 0x61, 0x00 );
		BBRegFixed( 0x64, 0xFF );
		BBRegFixed( 0x66, 0x00 );
		for ( int i = 0x69; i < 0x100; i++ )
			BBRegFixed( i, 0x00 );

		byte[] fw = _nds.GetFirmware();
		RFVersion = fw[0x40];
		Array.Clear( RFRegs, 0, RFRegs.Length );

		if ( RFVersion == 3 )
		{
			RFChannelIndex[0] = fw[0x116];
			RFChannelIndex[1] = fw[0x125];
			for ( int i = 0; i < 14; i++ )
			{
				RFChannelData[i, 0] = fw[0x117 + i];
				RFChannelData[i, 1] = fw[0x126 + i];
			}
		}
		else
		{
			int rf56 = 0xF2;
			RFChannelIndex[0] = (uint)(fw[rf56 + 2] >> 2);
			RFChannelIndex[1] = (uint)(fw[rf56 + 5] >> 2);
			for ( int i = 0; i < 14; i++ )
			{
				RFChannelData[i, 0] = (uint)(fw[rf56 + i * 6 + 0] |
					(fw[rf56 + i * 6 + 1] << 8) |
					((fw[rf56 + i * 6 + 2] & 0x03) << 16));
				RFChannelData[i, 1] = (uint)(fw[rf56 + i * 6 + 3] |
					(fw[rf56 + i * 6 + 4] << 8) |
					((fw[rf56 + i * 6 + 5] & 0x03) << 16));
			}
		}

		CurChannel = 0;

		IOPORT( 0x000 ) = 0x1440;

		IO[0x018 >> 1] = 0xFFFF; IO[0x01A >> 1] = 0xFFFF; IO[0x01C >> 1] = 0xFFFF;
		IO[0x020 >> 1] = 0xFFFF; IO[0x022 >> 1] = 0xFFFF; IO[0x024 >> 1] = 0xFFFF;

		IOPORT( W_PowerUS ) = 0x0001;

		USTimestamp = 0;
		USCounter = 0;
		USCompare = 0;
		BlockBeaconIRQ14 = false;

		Array.Clear( TXSlots, 0, TXSlots.Length );
		Array.Clear( TXBuffer, 0, TXBuffer.Length );
		ComStatus = 0;
		TXCurSlot = 0xFFFFFFFF;
		RXCounter = 0;

		Array.Clear( RXBuffer, 0, RXBuffer.Length );
		RXBufferPtr = 0;
		RXTime = 0;
		RXHalfwordTimeMask = 0xFFFFFFFF;

		MPReplyTimer = 0;
		MPClientMask = 0;
		MPClientFail = 0;
		Array.Clear( MPClientReplies, 0, MPClientReplies.Length );

		MPLastSeqno = 0xFFFF;

		CmdCounter = 0;

		USUntilPowerOn = 0;

		IsMP = false;
		IsMPClient = false;
		NextSync = 0;
		RXTimestamp = 0;

		_nds.CancelEvent( SysEvent.Wifi );
	}

	private void ScheduleTimer( bool first )
	{
		if ( first ) TimerError = 0;

		long cycles = 33513982L * kTimerInterval;
		cycles -= TimerError;
		int delay = (int)((cycles + 999999) / 1000000);
		TimerError = (int)((delay * 1000000L) - cycles);

		_nds.ScheduleEvent( SysEvent.Wifi, !first, delay, USTimer );
	}

	private void UpdatePowerOn()
	{
		bool on = Enabled;
		on = on && ((IOPORT( W_PowerUS ) & 0x1) == 0);

		if ( on == PowerOn )
			return;

		PowerOn = on;
		if ( on )
			ScheduleTimer( true );
		else
			_nds.CancelEvent( SysEvent.Wifi );
	}

	public void SetPowerCnt( uint val )
	{
		Enabled = (val & (1 << 1)) != 0;
		UpdatePowerOn();
	}

	private void CheckIRQ( ushort oldflags )
	{
		ushort newflags = (ushort)(IOPORT( W_IF ) & IOPORT( W_IE ));
		if ( (oldflags == 0) && (newflags != 0) )
			_nds.SetIRQ( 1, IRQ.Wifi );
	}

	private void SetIRQ( int irq )
	{
		ushort oldflags = (ushort)(IOPORT( W_IF ) & IOPORT( W_IE ));
		IOPORT( W_IF ) |= (ushort)(1 << irq);
		CheckIRQ( oldflags );
	}

	private void SetIRQ13()
	{
		SetIRQ( 13 );
		if ( (IOPORT( W_ModeWEP ) & 0x7) == 0 )
		{
			if ( (IOPORT( W_PowerTX ) & (1 << 1)) == 0 )
				UpdatePowerStatus( -1 );
		}
	}

	private void SetIRQ14( int source )
	{
		if ( source != 2 )
			IOPORT( W_BeaconCount1 ) = IOPORT( W_BeaconInterval );

		if ( BlockBeaconIRQ14 && source == 1 )
			return;
		if ( (IOPORT( W_USCompareCnt ) & 0x0001) == 0 )
			return;

		SetIRQ( 14 );

		IOPORT( W_BeaconCount2 ) = 0xFFFF;
		IOPORT( W_TXReqRead ) &= 0xFFF2;

		if ( (IOPORT( W_TXSlotBeacon ) & 0x8000) != 0 )
			StartTX_Beacon();

		if ( IOPORT( W_ListenCount ) == 0 )
			IOPORT( W_ListenCount ) = IOPORT( W_ListenInterval );

		IOPORT( W_ListenCount )--;
	}

	private void SetIRQ15()
	{
		SetIRQ( 15 );
		if ( (IOPORT( W_PowerTX ) & (1 << 0)) != 0 )
			UpdatePowerStatus( 1 );
	}

	private void SetStatus( uint status )
	{
		ushort[] rfpins = { 0x04, 0x84, 0, 0x46, 0, 0x84, 0x87, 0, 0x46, 0x04 };
		IOPORT( W_RFStatus ) = (ushort)status;
		IOPORT( W_RFPins ) = rfpins[status];
	}

	private void UpdatePowerStatus( int power )
	{
		int curflags = 0;
		if ( IOPORT( W_TRXPower ) == 1 ) curflags |= 1;
		if ( (IOPORT( W_PowerState ) & (1 << 9)) == 0 ) curflags |= 2;
		int reqflags = curflags;

		if ( (IOPORT( W_PowerForce ) & (1 << 15)) != 0 )
		{
			reqflags = (IOPORT( W_PowerForce ) & (1 << 0)) != 0 ? 0 : 3;
		}
		else if ( (IOPORT( W_ModeReset ) & (1 << 0)) == 0 )
		{
			reqflags = 0;
		}
		else
		{
			if ( power == 0 )
			{
				if ( (IOPORT( W_PowerState ) & 0x0202) == 0x0202 )
					power = 1;
				else if ( (IOPORT( W_PowerState ) & 0x0201) == 0x0001 )
					power = -1;
			}

			if ( (power == -1) && (IOPORT( W_PowerDownCtrl ) & (1 << 0)) != 0 )
				power = 0;

			if ( power == 1 )
				reqflags = 3;
			else if ( power == -1 )
				reqflags = IOPORT( W_PowerDownCtrl ) != 0 ? 3 : 0;
			else if ( (IOPORT( W_PowerDownCtrl ) & (1 << 1)) != 0 )
				reqflags = 3;
		}

		if ( reqflags == curflags )
			return;

		if ( (reqflags & 1) != 0 )
		{
			if ( (curflags & 1) == 0 )
			{
				IOPORT( W_TRXPower ) = 1;
				SetStatus( 1 );
			}
		}
		else
		{
			IOPORT( W_TRXPower ) = 2;
			if ( ComStatus == 0 )
			{
				IOPORT( W_TRXPower ) = 0;
				SetStatus( 9 );
			}
		}

		if ( (reqflags & 2) != 0 )
		{
			IOPORT( W_PowerState ) |= 1 << 8;
			if ( ((curflags & 2) == 0) && (USUntilPowerOn == 0) )
			{
				USUntilPowerOn = -2048;
				SetIRQ( 11 );
			}
		}
		else
		{
			IOPORT( W_PowerState ) &= unchecked((ushort)~(1 << 0));
			IOPORT( W_PowerState ) &= unchecked((ushort)~(1 << 8));
			IOPORT( W_PowerState ) |= 1 << 9;
			USUntilPowerOn = 0;
		}
	}

	private int PreambleLen( int rate )
	{
		if ( rate == 1 ) return 192;
		if ( (IOPORT( W_Preamble ) & 0x0004) != 0 ) return 96;
		return 192;
	}

	private uint NumClients( ushort bitmask )
	{
		uint ret = 0;
		for ( int i = 1; i < 16; i++ )
			if ( (bitmask & (1 << i)) != 0 ) ret++;
		return ret;
	}

	private void IncrementTXCount( ref TXSlot slot )
	{
		byte cnt = RAM[slot.Addr + 0x4];
		if ( cnt < 0xFF ) cnt++;
		W16( RAM, slot.Addr + 0x4, cnt );
	}

	private void ReportMPReplyErrors( ushort clientfail )
	{
		for ( int i = 1; i < 16; i++ )
		{
			if ( (clientfail & (1 << i)) == 0 )
				continue;
			int ba = W_CMDStat0 + i;
			byte v = (byte)(IO[ba >> 1] >> ((ba & 1) * 8));
			v++;
			if ( (ba & 1) != 0 ) IO[ba >> 1] = (ushort)((IO[ba >> 1] & 0x00FF) | (v << 8));
			else IO[ba >> 1] = (ushort)((IO[ba >> 1] & 0xFF00) | v);
		}
	}

	private void TXSendFrame( ref TXSlot slot, int num )
	{
		uint noseqno = 0;

		if ( RAM[slot.Addr + 0x4] != 0 )
			noseqno = 2;
		else if ( num == 1 )
			noseqno = (uint)((IOPORT( W_TXSlotCmd ) & 0x4000) != 0 ? 1 : 0);

		if ( noseqno == 0 )
		{
			if ( (IOPORT( W_TXHeaderCnt ) & (1 << 2)) == 0 )
				W16( RAM, slot.Addr + 0xC + 22, (ushort)(IOPORT( W_TXSeqNo ) << 4) );
			IOPORT( W_TXSeqNo ) = (ushort)((IOPORT( W_TXSeqNo ) + 1) & 0x0FFF);
		}

		ushort framectl = R16( RAM, slot.Addr + 0xC );
		if ( (framectl & (1 << 14)) != 0 )
		{
			if ( (IOPORT( W_WEPCnt ) & (1 << 15)) != 0 )
			{
				int wep_fcs = (slot.Addr + 0xC + slot.Length - 7) & ~0x1;
				W32( RAM, wep_fcs, 0x22334466 );
			}
		}

		int len = slot.Length;
		if ( (slot.Addr + len) > 0x1FF4 )
			len = 0x1FF4 - slot.Addr;

		Array.Copy( RAM, slot.Addr, TXBuffer, 0, 12 + len );

		if ( noseqno == 2 )
			W16( TXBuffer, 0xC, (ushort)(R16( TXBuffer, 0xC ) | (1 << 11)) );

		if ( CurChannel == 0 ) return;
		TXBuffer[9] = (byte)CurChannel;
	}

	private void StartTX_LocN( int nslot, int loc )
	{
		ref TXSlot slot = ref TXSlots[nslot];
		slot.Valid = true;
		slot.Addr = (ushort)((IOPORT( W_TXSlotLoc1 + (loc * 4) ) & 0x0FFF) << 1);
		slot.Length = (ushort)(R16( RAM, slot.Addr + 0xA ) & 0x3FFF);

		byte rate = RAM[slot.Addr + 0x8];
		slot.Rate = (byte)(rate == 0x14 ? 2 : 1);

		slot.CurPhase = 0;
		slot.CurPhaseTime = PreambleLen( slot.Rate );
	}

	private void StartTX_Cmd()
	{
		ref TXSlot slot = ref TXSlots[1];
		slot.Valid = true;
		slot.Addr = (ushort)((IOPORT( W_TXSlotCmd ) & 0x0FFF) << 1);
		slot.Length = (ushort)(R16( RAM, slot.Addr + 0xA ) & 0x3FFF);

		byte rate = RAM[slot.Addr + 0x8];
		slot.Rate = (byte)(rate == 0x14 ? 2 : 1);

		MPClientMask = (ushort)(R16( RAM, slot.Addr + 12 + 24 + 2 ) & MPClientFail);
		MPClientFail &= MPClientMask;

		uint duration = (uint)(PreambleLen( slot.Rate ) + (slot.Length * (slot.Rate == 2 ? 4 : 8)));
		duration += (uint)(112 + ((10 + IOPORT( W_CmdReplyTime )) * NumClients( MPClientMask )));
		duration += (uint)(32 * (slot.Rate == 2 ? 4 : 8));

		if ( CmdCounter > (duration + 100) )
		{
			slot.CurPhase = 0;
			slot.CurPhaseTime = PreambleLen( slot.Rate );
		}
		else
		{
			slot.CurPhase = 13;
			slot.CurPhaseTime = (int)(CmdCounter - 100);
		}

		UpdatePowerStatus( 1 );
	}

	private void StartTX_Beacon()
	{
		ref TXSlot slot = ref TXSlots[4];
		slot.Valid = true;
		slot.Addr = (ushort)((IOPORT( W_TXSlotBeacon ) & 0x0FFF) << 1);
		slot.Length = (ushort)(R16( RAM, slot.Addr + 0xA ) & 0x3FFF);

		byte rate = RAM[slot.Addr + 0x8];
		slot.Rate = (byte)(rate == 0x14 ? 2 : 1);

		slot.CurPhase = 0;
		slot.CurPhaseTime = PreambleLen( slot.Rate );

		IOPORT( W_TXBusy ) |= 0x0010;
	}

	private void FireTX()
	{
		if ( (IOPORT( W_RXCnt ) & 0x8000) == 0 )
			return;

		ushort txbusy = IOPORT( W_TXBusy );

		ushort txreq = IOPORT( W_TXReqRead );
		ushort txstart = 0;
		if ( (IOPORT( W_TXSlotLoc1 ) & 0x8000) != 0 ) txstart |= 0x0001;
		if ( (IOPORT( W_TXSlotCmd ) & 0x8000) != 0 ) txstart |= 0x0002;
		if ( (IOPORT( W_TXSlotLoc2 ) & 0x8000) != 0 ) txstart |= 0x0004;
		if ( (IOPORT( W_TXSlotLoc3 ) & 0x8000) != 0 ) txstart |= 0x0008;

		txstart &= txreq;
		txstart &= (ushort)~txbusy;

		IOPORT( W_TXBusy ) = (ushort)(txbusy | txstart);

		if ( (txstart & 0x0008) != 0 ) { StartTX_LocN( 3, 2 ); return; }
		if ( (txstart & 0x0004) != 0 ) { StartTX_LocN( 2, 1 ); return; }
		if ( (txstart & 0x0002) != 0 ) { MPClientFail = 0xFFFE; StartTX_Cmd(); return; }
		if ( (txstart & 0x0001) != 0 ) { StartTX_LocN( 0, 0 ); return; }
	}

	private void SendMPReply( ushort clienttime, ushort clientmask )
	{
		ref TXSlot slot = ref TXSlots[5];

		if ( (IOPORT( W_TXSlotReply2 ) & 0x8000) != 0 )
			W16( RAM, slot.Addr, 0x0001 );

		slot.Rate = 2;

		IOPORT( W_TXSlotReply2 ) = IOPORT( W_TXSlotReply1 );
		IOPORT( W_TXSlotReply1 ) = 0;

		if ( (IOPORT( W_TXSlotReply2 ) & 0x8000) == 0 )
		{
			slot.Valid = false;
		}
		else
		{
			slot.Valid = true;
			slot.Addr = (ushort)((IOPORT( W_TXSlotReply2 ) & 0x0FFF) << 1);
			slot.Length = (ushort)(R16( RAM, slot.Addr + 0xA ) & 0x3FFF);

			uint duration = (uint)(PreambleLen( slot.Rate ) + (slot.Length * (slot.Rate == 2 ? 4 : 8)));
			if ( duration > clienttime )
				slot.Valid = false;
		}

		if ( slot.Valid )
		{
			slot.CurPhase = 0;
			TXSendFrame( ref slot, 5 );
		}
		else
		{
			slot.CurPhase = 10;
		}

		ushort clientnum = 0;
		for ( int i = 1; i < IOPORT( W_AIDLow ); i++ )
			if ( (clientmask & (1 << i)) != 0 ) clientnum++;

		slot.CurPhaseTime = 16 + ((clienttime + 10) * clientnum) + PreambleLen( slot.Rate );

		IOPORT( W_TXBusy ) |= 0x0080;
	}

	private bool ProcessTX( ref TXSlot slot, int num )
	{
		slot.CurPhaseTime -= kTimerInterval;
		if ( slot.CurPhaseTime > 0 )
		{
			if ( slot.CurPhase == 1 )
			{
				if ( ((uint)slot.CurPhaseTime & slot.HalfwordTimeMask) == 0 )
					IOPORT( W_RXTXAddr )++;
			}
			else if ( slot.CurPhase == 2 )
			{
				MPReplyTimer -= kTimerInterval;
				if ( MPReplyTimer <= 0 && MPClientMask != 0 )
				{
					int nclient = 1;
					while ( (MPClientMask & (1 << nclient)) == 0 ) nclient++;

					uint curclient = 1u << nclient;

					MPReplyTimer += 10 + IOPORT( W_CmdReplyTime );
					MPClientMask &= (ushort)~curclient;
				}
			}

			return false;
		}

		switch ( slot.CurPhase )
		{
			case 0:
				{
					SetIRQ( 7 );
					if ( num == 5 ) SetStatus( 8 ); else SetStatus( 3 );

					uint len = slot.Length;
					if ( slot.Rate == 2 ) { len *= 4; slot.HalfwordTimeMask = 0x7 & kTimeCheckMask; }
					else { len *= 8; slot.HalfwordTimeMask = 0xF & kTimeCheckMask; }

					slot.CurPhase = 1;
					slot.CurPhaseTime = (int)len;

					IOPORT( W_RXTXAddr ) = (ushort)(slot.Addr >> 1);

					if ( num != 5 )
						TXSendFrame( ref slot, num );
				}
				break;

			case 10:
				{
					SetIRQ( 7 );
					SetStatus( 8 );
					slot.CurPhase = 11;
					slot.CurPhaseTime = 28 * 4;
					slot.HalfwordTimeMask = 0xFFFFFFFF;
				}
				break;

			case 1:
				{
					if ( num != 1 && num != 5 )
						W16( RAM, slot.Addr, 0x0001 );
					RAM[slot.Addr + 5] = 0;

					if ( num == 1 )
					{
						if ( (IOPORT( W_TXStatCnt ) & 0x4000) != 0 )
						{
							IOPORT( W_TXStat ) = 0x0800;
							SetIRQ( 1 );
						}
						SetStatus( 5 );

						MPReplyTimer = 16 + PreambleLen( slot.Rate );

						slot.CurPhase = 2;
						slot.CurPhaseTime = (int)(112 + ((10 + IOPORT( W_CmdReplyTime )) * NumClients( MPClientMask )));
						break;
					}
					else if ( num == 5 )
					{
						if ( (IOPORT( W_TXStatCnt ) & 0x1000) != 0 )
						{
							IOPORT( W_TXStat ) = 0x0401;
							SetIRQ( 1 );
						}
						SetStatus( 1 );

						IOPORT( W_TXBusy ) &= unchecked((ushort)~0x80);
						FireTX();
						return true;
					}

					IOPORT( W_TXBusy ) &= (ushort)~(1 << num);

					switch ( num )
					{
						case 0:
						case 2:
						case 3:
							IOPORT( W_TXStat ) = (ushort)(0x0001 | ((num != 0 ? (num - 1) : 0) << 12));
							SetIRQ( 1 );
							IOPORT( W_TXSlotLoc1 + ((num != 0 ? (num - 1) : 0) * 4) ) &= 0x7FFF;
							break;

						case 4:
							if ( (IOPORT( W_TXStatCnt ) & 0x8000) != 0 )
							{
								IOPORT( W_TXStat ) = 0x0301;
								SetIRQ( 1 );
							}
							break;
					}

					SetStatus( 1 );
					FireTX();
				}
				return true;

			case 11:
				{
					IOPORT( W_TXSeqNo ) = (ushort)((IOPORT( W_TXSeqNo ) + 1) & 0x0FFF);
					IOPORT( W_TXBusy ) &= unchecked((ushort)~0x80);
					SetStatus( 1 );
					FireTX();
				}
				return true;

			case 2:
				{
					SetIRQ( 7 );
					SetStatus( 8 );

					IOPORT( W_RXTXAddr ) = 0xFC0;

					if ( slot.Rate == 2 ) slot.CurPhaseTime = 32 * 4;
					else slot.CurPhaseTime = 32 * 8;

					ReportMPReplyErrors( MPClientFail );

					slot.CurPhase = 3;
				}
				break;

			case 3:
				{
					if ( MPClientFail == 0 )
						W16( RAM, slot.Addr, 0x0001 );
					else
						W16( RAM, slot.Addr, 0x0005 );

					W16( RAM, slot.Addr + 0x2, MPClientFail );
					if ( MPClientFail == 0 )
						IncrementTXCount( ref slot );

					IOPORT( W_TXSeqNo ) = (ushort)((IOPORT( W_TXSeqNo ) + 1) & 0x0FFF);

					if ( (IOPORT( W_TXStatCnt ) & 0x2000) != 0 )
					{
						IOPORT( W_TXStat ) = 0x0B01;
						SetIRQ( 1 );
					}

					IOPORT( W_TXBusy ) &= unchecked((ushort)~(1 << 1));
					IOPORT( W_TXSlotCmd ) &= 0x7FFF;

					SetStatus( 1 );
					SetIRQ( 12 );

					FireTX();
				}
				return true;

			case 13:
				{
					IOPORT( W_TXBusy ) &= unchecked((ushort)~(1 << 1));
					IOPORT( W_TXSlotCmd ) &= 0x7FFF;

					W16( RAM, slot.Addr, 0x0005 );

					IOPORT( W_TXSeqNo ) = (ushort)((IOPORT( W_TXSeqNo ) + 1) & 0x0FFF);

					SetStatus( 1 );
					SetIRQ( 12 );

					FireTX();
				}
				return true;
		}

		return false;
	}

	private void IncrementRXAddr( ref ushort addr, ushort inc = 2 )
	{
		for ( uint i = 0; i < inc; i += 2 )
		{
			addr += 2;
			addr &= 0x1FFE;
			if ( addr == (IOPORT( W_RXBufEnd ) & 0x1FFE) )
				addr = (ushort)(IOPORT( W_RXBufBegin ) & 0x1FFE);
		}
	}

	private void StartRX()
	{
		ushort framelen = R16( RXBuffer, 8 );
		RXTime = framelen;

		ushort txrate = R16( RXBuffer, 6 );
		if ( txrate == 0x14 ) { RXTime *= 4; RXHalfwordTimeMask = 0x7 & kTimeCheckMask; }
		else { RXTime *= 8; RXHalfwordTimeMask = 0xF & kTimeCheckMask; }

		ushort addr = (ushort)(IOPORT( W_RXBufWriteCursor ) << 1);
		IncrementRXAddr( ref addr, 12 );
		IOPORT( W_RXTXAddr ) = (ushort)(addr >> 1);

		RXBufferPtr = 12;

		SetIRQ( 6 );
		SetStatus( 6 );
		ComStatus |= 1;
	}

	private void FinishRX()
	{
		ComStatus &= ~0x1u;
		RXCounter = 0;

		if ( ComStatus == 0 )
		{
			if ( (IOPORT( W_PowerState ) & (1 << 9)) != 0 )
			{
				IOPORT( W_TRXPower ) = 0;
				SetStatus( 9 );
			}
			else
				SetStatus( 1 );
		}

		ushort framectl = R16( RXBuffer, 12 );
		ushort seqno = R16( RXBuffer, 12 + 22 );

		byte dstmac0 = RXBuffer[12 + 4];
		if ( (dstmac0 & 0x01) == 0 )
		{
			if ( !MACEqualIO( RXBuffer, 12 + 4, W_MACAddr0 ) )
				return;
		}

		if ( (framectl & (1 << 14)) != 0 )
		{
			if ( (IOPORT( W_WEPCnt ) & (1 << 15)) == 0 )
				return;
		}

		ushort rxflags = 0x0010;
		bool cmd_dupe = false;

		switch ( (framectl >> 2) & 0x3 )
		{
			case 0:
				{
					if ( MACEqualIO( RXBuffer, 12 + 16, W_BSSID0 ) )
						rxflags |= 0x8000;

					int subtype = (framectl >> 4) & 0xF;
					if ( subtype == 0x8 )
					{
						if ( (rxflags & 0x8000) == 0 )
						{
							if ( (IOPORT( W_RXFilter ) & (1 << 0)) == 0 )
								return;
						}
						rxflags |= 0x0001;
					}
					else if ( (subtype <= 0x5) || (subtype >= 0xA && subtype <= 0xC) )
					{
						if ( (rxflags & 0x8000) == 0 )
						{
							if ( (IOPORT( W_RXFilter ) & (3 << 9)) == 0 )
								return;
						}
					}
				}
				break;

			case 1:
				{
					if ( (framectl & 0xF0) == 0xA0 )
					{
						if ( MACEqualIO( RXBuffer, 12 + 4, W_BSSID0 ) )
							rxflags |= 0x8000;

						if ( (rxflags & 0x8000) == 0 )
						{
							if ( (IOPORT( W_RXFilter ) & (1 << 11)) == 0 )
								return;
						}
						rxflags |= 0x0005;
					}
					else
						return;
				}
				break;

			case 2:
				{
					int fromto = (framectl >> 8) & 0x3;
					if ( (IOPORT( W_RXFilter2 ) & (1 << fromto)) != 0 )
						return;

					int[] bssidoffset = { 16, 4, 10, 0 };
					if ( bssidoffset[fromto] != 0 )
					{
						if ( MACEqualIO( RXBuffer, 12 + bssidoffset[fromto], W_BSSID0 ) )
							rxflags |= 0x8000;
					}

					ushort rxfilter = IOPORT( W_RXFilter );

					if ( (rxflags & 0x8000) == 0 )
					{
						if ( (rxfilter & (1 << 11)) == 0 )
							return;
					}

					if ( (framectl & (1 << 11)) != 0 )
					{
						if ( (rxfilter & (1 << 0)) == 0 )
							return;
					}

					if ( MACEqual( RXBuffer, 12 + 16, MPReplyMAC, 0 ) )
					{
						if ( (framectl & 0xF0) == 0x50 ) rxflags |= 0x000F;
						else rxflags |= 0x000E;
					}
					else if ( MACEqual( RXBuffer, 12 + 4, MPCmdMAC, 0 ) )
					{
						if ( seqno == MPLastSeqno ) cmd_dupe = true;
						MPLastSeqno = seqno;
						rxflags |= 0x000C;
					}
					else if ( MACEqual( RXBuffer, 12 + 4, MPAckMAC, 0 ) )
					{
						rxflags |= 0x000D;
					}
					else
					{
						rxflags |= 0x0008;
					}

					switch ( (framectl >> 4) & 0xF )
					{
						case 0x0: break;
						case 0x1:
							if ( (rxflags & 0xF) == 0xD )
							{
								if ( (rxfilter & (1 << 7)) == 0 ) return;
							}
							else if ( (rxflags & 0xF) != 0xE )
							{
								if ( (rxfilter & (1 << 1)) == 0 ) return;
							}
							break;
						case 0x2:
							if ( (rxflags & 0xF) != 0xC )
							{
								if ( (rxfilter & (1 << 2)) == 0 ) return;
							}
							break;
						case 0x3:
							if ( (rxfilter & (1 << 3)) == 0 ) return;
							break;
						case 0x4: break;
						case 0x5:
							if ( (rxflags & 0xF) == 0xF )
							{
								if ( (rxfilter & (1 << 8)) == 0 ) return;
							}
							else
							{
								if ( (rxfilter & (1 << 4)) == 0 ) return;
							}
							break;
						case 0x6:
							if ( (rxfilter & (1 << 5)) == 0 ) return;
							break;
						case 0x7:
							if ( (rxfilter & (1 << 6)) == 0 ) return;
							break;
						default:
							return;
					}
				}
				break;
		}

		if ( !cmd_dupe )
		{
			ushort headeraddr = (ushort)(IOPORT( W_RXBufWriteCursor ) << 1);
			W16( RAM, headeraddr, rxflags );
			IncrementRXAddr( ref headeraddr );
			W16( RAM, headeraddr, 0x0040 );
			IncrementRXAddr( ref headeraddr, 4 );
			W16( RAM, headeraddr, R16( RXBuffer, 6 ) );
			IncrementRXAddr( ref headeraddr );
			W16( RAM, headeraddr, R16( RXBuffer, 8 ) );
			IncrementRXAddr( ref headeraddr );
			W16( RAM, headeraddr, 0x4080 );

			ushort addr = (ushort)(IOPORT( W_RXTXAddr ) << 1);
			if ( (addr & 0x2) != 0 ) IncrementRXAddr( ref addr );
			IOPORT( W_RXBufWriteCursor ) = (ushort)((addr & ~0x3) >> 1);

			SetIRQ( 0 );
		}

		if ( (rxflags & 0x800F) == 0x800C )
		{
			ushort clientmask = R16( RXBuffer, 0xC + 26 );
			if ( IOPORT( W_AIDLow ) != 0 && (clientmask & (1 << IOPORT( W_AIDLow ))) != 0 )
				SendMPReply( R16( RXBuffer, 0xC + 24 ), clientmask );
		}
		else if ( (rxflags & 0x800F) == 0x8001 )
		{
			uint len = R16( RXBuffer, 8 );
			ushort txrate = R16( RXBuffer, 6 );
			len *= (uint)((txrate == 0x14) ? 4 : 8);
			len -= 76;

			ulong timestamp = R64( RXBuffer, 12 + 24 );
			timestamp += len;

			USCounter = timestamp;
		}
	}

	private bool CheckRX( int type )
	{
		return false;
	}

	private void MSTimer()
	{
		if ( IOPORT( W_USCompareCnt ) != 0 )
		{
			if ( (USCounter & ~0x3FFUL) == USCompare )
			{
				BlockBeaconIRQ14 = false;
				SetIRQ14( 0 );
			}
		}

		if ( IOPORT( W_BeaconCount1 ) != 0 )
		{
			IOPORT( W_BeaconCount1 )--;
			if ( IOPORT( W_BeaconCount1 ) == 0 ) SetIRQ14( 1 );
		}
		if ( IOPORT( W_BeaconCount1 ) == 0 )
			IOPORT( W_BeaconCount1 ) = IOPORT( W_BeaconInterval );

		if ( IOPORT( W_BeaconCount2 ) != 0 )
		{
			IOPORT( W_BeaconCount2 )--;
			if ( IOPORT( W_BeaconCount2 ) == 0 ) SetIRQ13();
		}
	}

	private void USTimer( uint param )
	{
		USTimestamp += kTimerInterval;

		if ( IsMPClient && (ComStatus == 0) )
		{
			if ( RXTimestamp != 0 && (USTimestamp >= RXTimestamp) )
			{
				RXTimestamp = 0;
				StartRX();
			}

			if ( USTimestamp >= NextSync )
				CheckRX( 2 );
		}

		if ( USUntilPowerOn < 0 )
		{
			USUntilPowerOn += kTimerInterval;
			if ( USUntilPowerOn >= 0 )
			{
				USUntilPowerOn = 0;
				IOPORT( W_PowerState ) = 0;
				SetStatus( 1 );
				UpdatePowerStatus( 0 );
			}
		}

		if ( IOPORT( W_USCountCnt ) != 0 )
		{
			USCounter += kTimerInterval;
			uint uspart = (uint)(USCounter & 0x3FF);

			if ( IOPORT( W_USCompareCnt ) != 0 )
			{
				uint beaconus = ((uint)IOPORT( W_BeaconCount1 ) << 10) | (0x3FFu - uspart);
				if ( (beaconus & kTimeCheckMask) == (IOPORT( W_PreBeacon ) & kTimeCheckMask) )
					SetIRQ15();
			}

			if ( (uspart & kTimeCheckMask) == 0 )
				MSTimer();
		}

		if ( (IOPORT( W_CmdCountCnt ) & 0x0001) != 0 )
		{
			if ( CmdCounter > 0 )
			{
				if ( CmdCounter < kTimerInterval ) CmdCounter = 0;
				else CmdCounter -= kTimerInterval;
			}
		}

		if ( IOPORT( W_ContentFree ) != 0 )
		{
			if ( IOPORT( W_ContentFree ) < kTimerInterval ) IOPORT( W_ContentFree ) = 0;
			else IOPORT( W_ContentFree ) -= kTimerInterval;
		}

		if ( ComStatus == 0 )
		{
			ushort txbusy = IOPORT( W_TXBusy );
			if ( txbusy != 0 )
			{
				if ( (IOPORT( W_PowerState ) & (1 << 9)) != 0 )
				{
					ComStatus = 0;
					TXCurSlot = 0xFFFFFFFF;
				}
				else
				{
					ComStatus = 0x2;
					if ( (txbusy & 0x0080) != 0 ) TXCurSlot = 5;
					else if ( (txbusy & 0x0010) != 0 ) TXCurSlot = 4;
					else if ( (txbusy & 0x0008) != 0 ) TXCurSlot = 3;
					else if ( (txbusy & 0x0004) != 0 ) TXCurSlot = 2;
					else if ( (txbusy & 0x0002) != 0 ) TXCurSlot = 1;
					else if ( (txbusy & 0x0001) != 0 ) TXCurSlot = 0;
				}
			}
			else
			{
				if ( (!IsMPClient) || (USTimestamp > NextSync) )
				{
					if ( ((RXCounter & 0x1FF & kTimeCheckMask) == 0) && (ComStatus == 0) )
						CheckRX( 0 );
				}

				RXCounter += kTimerInterval;
			}
		}

		if ( (ComStatus & 0x2) != 0 )
		{
			bool finished = ProcessTX( ref TXSlots[TXCurSlot], (int)TXCurSlot );
			if ( finished )
			{
				if ( (IOPORT( W_PowerState ) & (1 << 9)) != 0 )
				{
					IOPORT( W_TXBusy ) = 0;
					IOPORT( W_TRXPower ) = 0;
					SetStatus( 9 );
				}

				ushort txbusy = IOPORT( W_TXBusy );
				if ( (txbusy & 0x0080) != 0 ) TXCurSlot = 5;
				else if ( (txbusy & 0x0010) != 0 ) TXCurSlot = 4;
				else if ( (txbusy & 0x0008) != 0 ) TXCurSlot = 3;
				else if ( (txbusy & 0x0004) != 0 ) TXCurSlot = 2;
				else if ( (txbusy & 0x0002) != 0 ) TXCurSlot = 1;
				else if ( (txbusy & 0x0001) != 0 ) TXCurSlot = 0;
				else
				{
					TXCurSlot = 0xFFFFFFFF;
					ComStatus = 0;
					RXCounter = 0;
				}
			}
		}
		if ( (ComStatus & 0x1) != 0 )
		{
			RXTime -= kTimerInterval;
			if ( ((uint)RXTime & RXHalfwordTimeMask) == 0 )
			{
				ushort addr = (ushort)(IOPORT( W_RXTXAddr ) << 1);
				if ( addr < 0x1FFF ) W16( RAM, addr, R16( RXBuffer, (int)RXBufferPtr ) );

				IncrementRXAddr( ref addr );
				IOPORT( W_RXTXAddr ) = (ushort)(addr >> 1);
				RXBufferPtr += 2;

				if ( RXTime <= 0 )
				{
					FinishRX();
				}
				else if ( addr == (IOPORT( W_RXBufReadCursor ) << 1) )
				{
					RXTime = 0;
					SetStatus( 1 );
					if ( TXCurSlot == 0xFFFFFFFF )
					{
						ComStatus &= ~0x1u;
						RXCounter = 0;
					}
					if ( (ComStatus == 0) && (IOPORT( W_PowerState ) & (1 << 9)) != 0 )
					{
						IOPORT( W_TRXPower ) = 0;
						SetStatus( 9 );
					}
				}
			}
		}

		ScheduleTimer( false );
	}

	private void ChangeChannel()
	{
		uint val1 = RFRegs[RFChannelIndex[0] & 0x3F];
		uint val2 = RFRegs[RFChannelIndex[1] & 0x3F];

		CurChannel = 0;

		for ( int i = 0; i < 14; i++ )
		{
			if ( val1 == RFChannelData[i, 0] && val2 == RFChannelData[i, 1] )
			{
				CurChannel = i + 1;
				break;
			}
		}
	}

	private void RFTransfer_Type2()
	{
		uint id = (uint)((IOPORT( W_RFData2 ) >> 2) & 0x1F);

		if ( (IOPORT( W_RFData2 ) & 0x0080) != 0 )
		{
			uint data = RFRegs[id];
			IOPORT( W_RFData1 ) = (ushort)(data & 0xFFFF);
			IOPORT( W_RFData2 ) = (ushort)((IOPORT( W_RFData2 ) & 0xFFFCu) | ((data >> 16) & 0x3u));
		}
		else
		{
			uint data = (uint)(IOPORT( W_RFData1 ) | ((IOPORT( W_RFData2 ) & 0x0003) << 16));
			RFRegs[id] = data;

			if ( id == (RFChannelIndex[0] & 0x3F) || id == (RFChannelIndex[1] & 0x3F) )
				ChangeChannel();
		}
	}

	private void RFTransfer_Type3()
	{
		uint id = (uint)((IOPORT( W_RFData1 ) >> 8) & 0x3F);

		uint cmd = (uint)(IOPORT( W_RFData2 ) & 0xF);
		if ( cmd == 6 )
		{
			IOPORT( W_RFData1 ) = (ushort)((IOPORT( W_RFData1 ) & 0xFF00u) | (RFRegs[id] & 0xFFu));
		}
		else if ( cmd == 5 )
		{
			uint data = (uint)(IOPORT( W_RFData1 ) & 0xFF);
			RFRegs[id] = data;

			if ( id == (RFChannelIndex[0] & 0x3F) || id == (RFChannelIndex[1] & 0x3F) )
				ChangeChannel();
		}
	}

	public ushort Read( uint addr )
	{
		if ( addr >= 0x04810000 )
			return 0;

		addr &= 0x7FFE;

		if ( addr >= 0x4000 && addr < 0x6000 )
			return R16( RAM, (int)(addr & 0x1FFE) );
		if ( addr >= 0x2000 && addr < 0x4000 )
			return 0xFFFF;

		bool activeread = addr < 0x1000;

		switch ( addr )
		{
			case W_Random:
				Random = (ushort)((Random & 0x1) ^ (((Random & 0x3FF) << 1) | (Random >> 10)));
				return Random;

			case W_Preamble:
				return (ushort)(IOPORT( W_Preamble ) & 0x0003);

			case W_USCount0: return (ushort)(USCounter & 0xFFFF);
			case W_USCount1: return (ushort)((USCounter >> 16) & 0xFFFF);
			case W_USCount2: return (ushort)((USCounter >> 32) & 0xFFFF);
			case W_USCount3: return (ushort)(USCounter >> 48);

			case W_USCompare0: return (ushort)(USCompare & 0xFFFF);
			case W_USCompare1: return (ushort)((USCompare >> 16) & 0xFFFF);
			case W_USCompare2: return (ushort)((USCompare >> 32) & 0xFFFF);
			case W_USCompare3: return (ushort)(USCompare >> 48);

			case W_CmdCount: return (ushort)((CmdCounter + 9) / 10);

			case W_BBRead:
				if ( (IOPORT( W_BBCnt ) & 0xF000) != 0x6000 )
					return 0;
				return BBRegs[IOPORT( W_BBCnt ) & 0xFF];

			case W_BBBusy:
				return 0;
			case W_RFBusy:
				return 0;

			case W_RXBufDataRead:
				if ( activeread )
				{
					uint rdaddr = IOPORT( W_RXBufReadAddr );
					ushort ret = R16( RAM, (int)rdaddr );

					rdaddr += 2;
					if ( rdaddr == (uint)(IOPORT( W_RXBufEnd ) & 0x1FFE) )
						rdaddr = (uint)(IOPORT( W_RXBufBegin ) & 0x1FFE);
					if ( rdaddr == IOPORT( W_RXBufGapAddr ) )
					{
						rdaddr += (uint)(IOPORT( W_RXBufGapSize ) << 1);
						if ( rdaddr >= (uint)(IOPORT( W_RXBufEnd ) & 0x1FFE) )
							rdaddr = rdaddr + (uint)(IOPORT( W_RXBufBegin ) & 0x1FFE) - (uint)(IOPORT( W_RXBufEnd ) & 0x1FFE);

						if ( IOPORT( 0x000 ) == 0xC340 )
							IOPORT( W_RXBufGapSize ) = 0;
					}

					IOPORT( W_RXBufReadAddr ) = (ushort)(rdaddr & 0x1FFE);
					IOPORT( W_RXBufDataRead ) = ret;

					if ( IOPORT( W_RXBufCount ) > 0 )
					{
						IOPORT( W_RXBufCount )--;
						if ( IOPORT( W_RXBufCount ) == 0 )
							SetIRQ( 9 );
					}
				}
				break;

			case W_TXBusy:
				return (ushort)(IOPORT( W_TXBusy ) & 0x001F);

			case W_CMDStat0:
			case W_CMDStat0 + 2:
			case W_CMDStat0 + 4:
			case W_CMDStat0 + 6:
			case W_CMDStat0 + 8:
			case W_CMDStat0 + 10:
			case W_CMDStat0 + 12:
			case W_CMDStat0 + 14:
				{
					ushort ret = IOPORT( (int)(addr & 0xFFF) );
					IOPORT( (int)(addr & 0xFFF) ) = 0;
					return ret;
				}
		}

		return IOPORT( (int)(addr & 0xFFF) );
	}

	public void Write( uint addr, ushort val )
	{
		if ( addr >= 0x04810000 )
			return;

		addr &= 0x7FFE;

		if ( addr >= 0x4000 && addr < 0x6000 )
		{
			W16( RAM, (int)(addr & 0x1FFE), val );
			return;
		}
		if ( addr >= 0x2000 && addr < 0x4000 )
			return;

		switch ( addr )
		{
			case W_ModeReset:
				{
					ushort oldval = IOPORT( W_ModeReset );
					IOPORT( W_ModeReset ) = (ushort)(val & 0x0001);

					if ( (oldval & 0x0001) == 0 && (val & 0x0001) != 0 )
					{
						IOPORT( 0x27C ) = 0x0005;
						UpdatePowerStatus( 0 );
					}
					else if ( (oldval & 0x0001) != 0 && (val & 0x0001) == 0 )
					{
						IOPORT( 0x27C ) = 0x000A;
						UpdatePowerStatus( 0 );
					}

					if ( (val & 0x2000) != 0 )
					{
						IOPORT( W_RXBufWriteAddr ) = 0;
						IOPORT( W_CmdTotalTime ) = 0;
						IOPORT( W_CmdReplyTime ) = 0;
						IOPORT( 0x1A4 ) = 0;
						IOPORT( 0x278 ) = 0x000F;
					}
					if ( (val & 0x4000) != 0 )
					{
						IOPORT( W_ModeWEP ) = 0;
						IOPORT( W_TXStatCnt ) = 0;
						IOPORT( 0x00A ) = 0;
						IOPORT( W_MACAddr0 ) = 0;
						IOPORT( W_MACAddr1 ) = 0;
						IOPORT( W_MACAddr2 ) = 0;
						IOPORT( W_BSSID0 ) = 0;
						IOPORT( W_BSSID1 ) = 0;
						IOPORT( W_BSSID2 ) = 0;
						IOPORT( W_AIDLow ) = 0;
						IOPORT( W_AIDFull ) = 0;
						IOPORT( W_TXRetryLimit ) = 0x0707;
						IOPORT( 0x02E ) = 0;
						IOPORT( W_RXBufBegin ) = 0x4000;
						IOPORT( W_RXBufEnd ) = 0x4800;
						IOPORT( W_TXBeaconTIM ) = 0;
						IOPORT( W_Preamble ) = 0x0001;
						IOPORT( W_RXFilter ) = 0x0401;
						IOPORT( 0x0D4 ) = 0x0001;
						IOPORT( W_RXFilter2 ) = 0x0008;
						IOPORT( 0x0EC ) = 0x3F03;
						IOPORT( W_TXHeaderCnt ) = 0;
						IOPORT( 0x198 ) = 0;
						IOPORT( 0x1A2 ) = 0x0001;
						IOPORT( 0x224 ) = 0x0003;
						IOPORT( 0x230 ) = 0x0047;
					}
				}
				return;

			case W_ModeWEP:
				val &= 0x007F;
				IOPORT( W_ModeWEP ) = val;
				if ( (IOPORT( W_PowerTX ) & (1 << 1)) != 0 )
				{
					if ( (val & 0x7) == 1 )
						IOPORT( W_PowerDownCtrl ) |= 1 << 1;
					else if ( (val & 0x7) == 2 )
						IOPORT( W_PowerDownCtrl ) = 3;

					if ( (val & 0x7) != 3 )
						IOPORT( W_PowerState ) &= 0x0300;

					UpdatePowerStatus( 0 );
				}
				return;

			case W_IE:
				{
					ushort oldflags = (ushort)(IOPORT( W_IF ) & IOPORT( W_IE ));
					IOPORT( W_IE ) = val;
					CheckIRQ( oldflags );
				}
				return;
			case W_IF:
				IOPORT( W_IF ) &= (ushort)~val;
				return;
			case W_IFSet:
				{
					ushort oldflags = (ushort)(IOPORT( W_IF ) & IOPORT( W_IE ));
					IOPORT( W_IF ) |= (ushort)(val & 0xFBFF);
					CheckIRQ( oldflags );
				}
				return;

			case W_AIDLow:
				IOPORT( W_AIDLow ) = (ushort)(val & 0x000F);
				return;
			case W_AIDFull:
				IOPORT( W_AIDFull ) = (ushort)(val & 0x07FF);
				return;

			case W_PowerUS:
				IOPORT( W_PowerUS ) = (ushort)(val & 0x0003);
				UpdatePowerOn();
				return;

			case W_PowerTX:
				IOPORT( W_PowerTX ) = (ushort)(val & 0x0003);
				if ( (val & (1 << 1)) != 0 )
				{
					if ( (IOPORT( W_ModeWEP ) & 0x7) == 1 )
						IOPORT( W_PowerDownCtrl ) |= 1 << 1;
					else if ( (IOPORT( W_ModeWEP ) & 0x7) == 2 )
						IOPORT( W_PowerDownCtrl ) = 3;

					UpdatePowerStatus( 0 );
				}
				return;

			case W_PowerState:
				if ( (IOPORT( W_ModeWEP ) & 0x7) != 3 )
					return;

				val = (ushort)((IOPORT( W_PowerState ) & 0x0300) | (val & 0x0003));
				if ( (val & 0x0300) == 0x0200 )
					val &= unchecked((ushort)~(1 << 0));
				else
					val &= unchecked((ushort)~(1 << 1));

				if ( (val & (1 << 9)) == 0 )
					val &= unchecked((ushort)~(1 << 8));

				IOPORT( W_PowerState ) = val;
				UpdatePowerStatus( 0 );
				return;

			case W_PowerForce:
				val &= 0x8001;
				IOPORT( W_PowerForce ) = val;
				UpdatePowerStatus( 0 );
				return;

			case W_PowerDownCtrl:
				IOPORT( W_PowerDownCtrl ) = (ushort)(val & 0x0003);
				if ( (IOPORT( W_PowerTX ) & (1 << 1)) != 0 )
				{
					if ( (IOPORT( W_ModeWEP ) & 0x7) == 1 )
						IOPORT( W_PowerDownCtrl ) |= 1 << 1;
					else if ( (IOPORT( W_ModeWEP ) & 0x7) == 2 )
						IOPORT( W_PowerDownCtrl ) = 3;
				}
				UpdatePowerStatus( 0 );
				return;

			case W_USCountCnt: val &= 0x0001; break;
			case W_USCompareCnt:
				if ( (val & 0x0002) != 0 ) SetIRQ14( 2 );
				val &= 0x0001;
				break;

			case W_USCount0: USCounter = (USCounter & 0xFFFFFFFFFFFF0000) | val; return;
			case W_USCount1: USCounter = (USCounter & 0xFFFFFFFF0000FFFF) | ((ulong)val << 16); return;
			case W_USCount2: USCounter = (USCounter & 0xFFFF0000FFFFFFFF) | ((ulong)val << 32); return;
			case W_USCount3: USCounter = (USCounter & 0x0000FFFFFFFFFFFF) | ((ulong)val << 48); return;

			case W_USCompare0:
				USCompare = (USCompare & 0xFFFFFFFFFFFF0000) | ((ulong)val & 0xFC00);
				if ( (val & 0x0001) != 0 ) BlockBeaconIRQ14 = true;
				return;
			case W_USCompare1: USCompare = (USCompare & 0xFFFFFFFF0000FFFF) | ((ulong)val << 16); return;
			case W_USCompare2: USCompare = (USCompare & 0xFFFF0000FFFFFFFF) | ((ulong)val << 32); return;
			case W_USCompare3: USCompare = (USCompare & 0x0000FFFFFFFFFFFF) | ((ulong)val << 48); return;

			case W_CmdCount: CmdCounter = (uint)(val * 10); return;

			case W_BBCnt:
				IOPORT( W_BBCnt ) = val;
				if ( (IOPORT( W_BBCnt ) & 0xF000) == 0x5000 )
				{
					int regid = IOPORT( W_BBCnt ) & 0xFF;
					if ( BBRegsRO[regid] == 0 )
						BBRegs[regid] = (byte)(IOPORT( W_BBWrite ) & 0xFF);
				}
				return;

			case W_RFData2:
				IOPORT( W_RFData2 ) = val;
				if ( RFVersion == 3 ) RFTransfer_Type3();
				else RFTransfer_Type2();
				return;
			case W_RFCnt:
				val &= 0x413F;
				break;

			case W_RXCnt:
				if ( (val & 0x0001) != 0 )
					IOPORT( W_RXBufWriteCursor ) = IOPORT( W_RXBufWriteAddr );
				if ( (val & 0x0080) != 0 )
				{
					IOPORT( W_TXSlotReply2 ) = IOPORT( W_TXSlotReply1 );
					IOPORT( W_TXSlotReply1 ) = 0;
				}
				if ( (val & 0x8000) != 0 )
					FireTX();
				val &= 0xFF0E;
				break;

			case W_RXBufDataRead:
				if ( IOPORT( W_RXBufCount ) > 0 )
				{
					IOPORT( W_RXBufCount )--;
					if ( IOPORT( W_RXBufCount ) == 0 )
						SetIRQ( 9 );
				}
				return;

			case W_RXBufReadAddr:
			case W_RXBufGapAddr:
				val &= 0x1FFE;
				break;
			case W_RXBufGapSize:
			case W_RXBufCount:
			case W_RXBufWriteAddr:
			case W_RXBufReadCursor:
				val &= 0x0FFF;
				break;

			case W_TXReqReset:
				IOPORT( W_TXReqRead ) &= (ushort)~val;
				return;
			case W_TXReqSet:
				IOPORT( W_TXReqRead ) |= val;
				FireTX();
				return;

			case W_TXSlotReset:
				if ( (val & 0x0001) != 0 ) IOPORT( W_TXSlotLoc1 ) &= 0x7FFF;
				if ( (val & 0x0002) != 0 ) IOPORT( W_TXSlotCmd ) &= 0x7FFF;
				if ( (val & 0x0004) != 0 ) IOPORT( W_TXSlotLoc2 ) &= 0x7FFF;
				if ( (val & 0x0008) != 0 ) IOPORT( W_TXSlotLoc3 ) &= 0x7FFF;
				if ( (val & 0x0040) != 0 ) IOPORT( W_TXSlotReply2 ) &= 0x7FFF;
				if ( (val & 0x0080) != 0 ) IOPORT( W_TXSlotReply1 ) &= 0x7FFF;
				val = 0;
				break;

			case W_TXBufDataWrite:
				{
					uint wraddr = IOPORT( W_TXBufWriteAddr );
					W16( RAM, (int)wraddr, val );

					wraddr += 2;
					if ( wraddr == IOPORT( W_TXBufGapAddr ) )
						wraddr += (uint)(IOPORT( W_TXBufGapSize ) << 1);

					IOPORT( W_TXBufWriteAddr ) = (ushort)(wraddr & 0x1FFE);

					if ( IOPORT( W_TXBufCount ) > 0 )
					{
						IOPORT( W_TXBufCount )--;
						if ( IOPORT( W_TXBufCount ) == 0 )
							SetIRQ( 8 );
					}
				}
				return;

			case W_TXBufWriteAddr:
			case W_TXBufGapAddr:
				val &= 0x1FFE;
				break;
			case W_TXBufGapSize:
			case W_TXBufCount:
				val &= 0x0FFF;
				break;

			case W_TXSlotBeacon:
				IsMP = (val & 0x8000) != 0;
				break;

			case W_TXSlotCmd:
				if ( CmdCounter == 0 )
					val = (ushort)((val & 0x7FFF) | (IOPORT( W_TXSlotCmd ) & 0x8000));
				IOPORT( (int)(addr & 0xFFF) ) = val;
				FireTX();
				return;
			case W_TXSlotLoc1:
			case W_TXSlotLoc2:
			case W_TXSlotLoc3:
				IOPORT( (int)(addr & 0xFFF) ) = val;
				FireTX();
				return;

			case 0x228:
			case 0x244:
				break;

			case 0x000:
			case 0x034:
			case 0x044:
			case 0x054:
			case 0x098:
			case 0x0B0:
			case 0x0B6:
			case 0x0B8:
			case 0x15C:
			case 0x15E:
			case 0x180:
			case 0x19C:
			case 0x1A8:
			case 0x1AC:
			case 0x1C4:
			case 0x210:
			case 0x214:
			case 0x268:
				return;

			default:
				break;
		}

		IOPORT( (int)(addr & 0xFFF) ) = val;
	}
}