Emulator/GbaWirelessAdapter.cs
using System.Collections.Concurrent;

namespace sGBA;

public interface IWirelessNetwork
{
	void Send( int clientId, byte[] data, int length );
}

public sealed class GbaWirelessAdapter
{
	public enum Command : byte
	{
		Init1 = 0x10,
		Init2 = 0x3D,
		SysConfig = 0x17,
		LinkPower = 0x11,
		SysVersion = 0x12,
		SysStatus = 0x13,
		SlotStatus = 0x14,
		ConfigStatus = 0x15,
		BroadcastData = 0x16,
		HostStart = 0x19,
		HostAccept = 0x1A,
		HostStop = 0x1B,
		BroadcastReadStart = 0x1C,
		BroadcastReadFetch = 0x1D,
		BroadcastReadStop = 0x1E,
		Connect = 0x1F,
		IsConnected = 0x20,
		ConnectComplete = 0x21,
		SendData = 0x24,
		SendDataWait = 0x25,
		ReceiveData = 0x26,
		Wait = 0x27,
		RetransmitWait = 0x37,
		Disconnect = 0x30,

		ResponseTimeout = 0x27,
		ResponseData = 0x28,
		ResponseDisconnect = 0x29,
	}

	public enum ComState
	{
		Reset,
		Handshake,
		WaitCommand,
		WaitData,
		ResponseCommand,
		ResponseData,
		ResponseError,
		ResponseError2,
		WaitEvent,
		WaitResponse,
	}

	public enum WifiState
	{
		Idle,
		Host,
		Connecting,
		Client,
	}

	private enum NetType : uint
	{
		Broadcast = 0x00,
		ConnectRequest = 0x01,
		ConnectAck = 0x02,
		ConnectNack = 0x03,
		Disconnect = 0x04,
		HostSend = 0x05,
		ClientSend = 0x06,
		ClientAck = 0x07,
	}

	private const uint ConnInProgress = 0x01000000;
	private const uint ConnFailed = 0x02000000;
	private const uint ConnCompleteFail = 0x01000000;

	private const uint NetHeaderMagic = 0x52465531; // "RFU1"

	private const int BroadcastAnnounceFrames = 30;
	private const int MaxPeers = 32;
	private const int DefaultTimeoutFrames = 32;
	private const int DefaultRetransmitMax = 4;

	private const int ClientQueueSize = 16;
	private const int HostQueueSize = 16;

	private const int MaxClients = 4;
	private const int HostPacketSize = 16;
	private const int ClientPacketSize = 128;

	private struct TxBuffer
	{
		public uint[] Data; // [23]
		public byte Length;
	}

	private struct HostInboxSlot
	{
		public uint Length;
		public byte[] Data; // [HostPacketSize]
	}

	private struct HostClientSlot
	{
		public ushort ClientId;
		public ushort DeviceId;
		public ushort TimeToLive;
		public HostInboxSlot[] Queue;
	}

	private struct HostState
	{
		public ushort DeviceId;
		public byte BroadcastTtl;
		public uint[] BroadcastData; // [6]
		public HostClientSlot[] Clients; // [MaxClients]
	}

	private struct ClientInboxSlot
	{
		public ushort Length;
		public byte[] Data; // [ClientPacketSize]
	}

	private struct ClientState
	{
		public ushort DeviceId;
		public ushort SlotNumber;
		public ushort HostId;
		public ClientInboxSlot[] Queue; // [ClientQueueSize]
	}

	private struct PeerBroadcast
	{
		public bool Valid;
		public byte Ttl;
		public ushort DeviceId;
		public uint[] Data; // [6]
	}

	public Gba Gba { get; }
	public IWirelessNetwork Network { get; set; }

	public ComState SpiState { get; private set; } = ComState.Reset;
	public WifiState WifiMode { get; private set; } = WifiState.Idle;
	public bool IsActive => SpiState != ComState.Reset;

	private uint _prevWord;
	private uint _bufferIndex;
	private readonly uint[] _buffer = new uint[255];
	private Command _command;
	private byte _payloadLength;
	private uint _timeoutCycles;
	private uint _responseTimeoutCycles;
	private byte _timeoutFrames = DefaultTimeoutFrames;
	private byte _retransmitMax = DefaultRetransmitMax;

	private TxBuffer _txBuffer = new() { Data = new uint[23] };
	private HostState _host;
	private ClientState _client;
	private readonly PeerBroadcast[] _peers = new PeerBroadcast[MaxPeers];

	private readonly ConcurrentQueue<(int FromClientId, byte[] Buffer, int Length)> _inbox = new();
	private readonly ConcurrentQueue<int> _disconnects = new();
	private readonly Random _rng = new( unchecked((int)DateTime.UtcNow.Ticks) );

	public GbaWirelessAdapter( Gba gba )
	{
		Gba = gba;
		InitHost();
		InitClient();
	}

	public void Reset()
	{
		_prevWord = 0;
		_bufferIndex = 0;
		WifiMode = WifiState.Idle;
		SpiState = ComState.Reset;
		_timeoutCycles = 0;
		_responseTimeoutCycles = 0;
		_timeoutFrames = DefaultTimeoutFrames;
		_retransmitMax = DefaultRetransmitMax;
		InitHost();
		Array.Clear( _peers );
	}

	public void EnqueueReceive( int fromClientId, byte[] buffer, int length )
	{
		_inbox.Enqueue( (fromClientId, buffer, length) );
	}

	public void EnqueueDisconnect( int fromClientId )
	{
		_disconnects.Enqueue( fromClientId );
	}

	private void DrainInbox()
	{
		while ( _inbox.TryDequeue( out var pkt ) )
			ReceiveNetworkPacket( pkt.Buffer, pkt.Length, pkt.FromClientId );
	}

	private void DrainDisconnects()
	{
		while ( _disconnects.TryDequeue( out int clientId ) )
		{
			if ( clientId >= 0 && clientId < MaxPeers )
				_peers[clientId].Valid = false;

			if ( WifiMode == WifiState.Host )
			{
				for ( int i = 0; i < MaxClients; i++ )
				{
					if ( _host.Clients[i].DeviceId != 0 && _host.Clients[i].ClientId == clientId )
						ClearClientSlot( i );
				}
			}

			if ( WifiMode == WifiState.Client && _client.HostId == clientId )
			{
				WifiMode = WifiState.Idle;
				InitClient();
			}
		}
	}

	private void InitHost()
	{
		_host = new HostState
		{
			DeviceId = 0,
			BroadcastTtl = 0,
			BroadcastData = new uint[6],
			Clients = new HostClientSlot[MaxClients],
		};
		for ( int i = 0; i < MaxClients; i++ )
			_host.Clients[i].Queue = NewHostQueue();
	}

	private static HostInboxSlot[] NewHostQueue()
	{
		var q = new HostInboxSlot[HostQueueSize];
		for ( int i = 0; i < q.Length; i++ )
			q[i].Data = new byte[HostPacketSize];
		return q;
	}

	private void InitClient()
	{
		_client = new ClientState
		{
			DeviceId = 0,
			SlotNumber = 0,
			HostId = 0,
			Queue = new ClientInboxSlot[ClientQueueSize],
		};
		for ( int i = 0; i < _client.Queue.Length; i++ )
			_client.Queue[i].Data = new byte[ClientPacketSize];
	}

	public uint Transfer( uint sentValue )
	{
		uint reply = 0x80000000;

		switch ( SpiState )
		{
			case ComState.Reset:
				reply = 0;
				if ( (sentValue & 0xFFFF) == 0x494E ) // "NI"
					SpiState = ComState.Handshake;
				break;

			case ComState.Handshake:
				if ( sentValue == 0xB0BB8001 )
					SpiState = ComState.WaitCommand;
				reply = (sentValue << 16) | ((~_prevWord) & 0xFFFF);
				break;

			case ComState.WaitCommand:
				if ( (sentValue >> 16) == 0x9966 )
				{
					_payloadLength = (byte)(sentValue >> 8);
					_command = (Command)(byte)sentValue;
					_bufferIndex = 0;
					if ( _payloadLength == 0 )
						CompleteCommand();
					else
						SpiState = ComState.WaitData;
				}
				break;

			case ComState.WaitData:
				_buffer[_bufferIndex++] = sentValue;
				if ( _bufferIndex == _payloadLength )
				{
					CompleteCommand();
					_bufferIndex = 0;
				}
				break;

			case ComState.ResponseCommand:
				reply = 0x99660080u | (byte)_command | ((uint)_payloadLength << 8);
				if ( _command is Command.Wait or Command.RetransmitWait or Command.SendDataWait )
				{
					SpiState = ComState.WaitEvent;
					_timeoutCycles = (uint)(_timeoutFrames * (16777216 / 60));
					_responseTimeoutCycles = (uint)(_retransmitMax * (16777216 / 60 / 6));
				}
				else
				{
					SpiState = _payloadLength != 0 ? ComState.ResponseData : ComState.WaitCommand;
				}
				break;

			case ComState.ResponseData:
				reply = _buffer[_bufferIndex++];
				if ( _bufferIndex == _payloadLength )
					SpiState = ComState.WaitCommand;
				break;

			case ComState.WaitEvent:
			case ComState.WaitResponse:
				break;

			case ComState.ResponseError:
				reply = 0x996601EE;
				SpiState = ComState.ResponseError2;
				break;

			case ComState.ResponseError2:
				reply = (byte)_command;
				SpiState = ComState.WaitCommand;
				break;
		}

		_prevWord = sentValue;
		return reply;
	}

	private void CompleteCommand()
	{
		int ret = ProcessCommand();
		if ( ret < 0 )
		{
			SpiState = ComState.ResponseError;
			_command = (Command)(byte)(-ret);
			_payloadLength = 1;
		}
		else
		{
			SpiState = ComState.ResponseCommand;
			_payloadLength = (byte)ret;
		}
	}

	private bool HasInboundData()
	{
		if ( WifiMode == WifiState.Client )
			return _client.Queue[0].Length != 0;

		if ( WifiMode == WifiState.Host )
		{
			for ( int i = 0; i < MaxClients; i++ )
				if ( _host.Clients[i].DeviceId != 0 && _host.Clients[i].Queue[0].Length != 0 )
					return true;
		}
		return false;
	}

	private ushort NewDeviceId()
	{
		while ( true )
		{
			ushort n = (ushort)(_rng.Next() ^ DateTime.UtcNow.Ticks);
			if ( n != 0 )
				return n;
		}
	}

	private int ProcessCommand()
	{
		switch ( _command )
		{
			case Command.Init1:
			case Command.Init2:
				return 0;

			case Command.SysConfig:
				_timeoutFrames = (byte)_buffer[0];
				_retransmitMax = (byte)(_buffer[0] >> 8);
				return 0;

			case Command.SysVersion:
				_buffer[0] = 0x00830117;
				return 1;

			case Command.SysStatus:
				_buffer[0] = WifiMode switch
				{
					WifiState.Host => (1u << 24) | _host.DeviceId,
					WifiState.Client => (5u << 24) | (1u << _client.SlotNumber << 16) | _client.DeviceId,
					_ => 0,
				};
				return 1;

			case Command.SlotStatus:
				if ( WifiMode == WifiState.Host )
				{
					uint cnt = 1;
					_buffer[0] = 0;
					for ( int i = 0; i < MaxClients; i++ )
					{
						if ( _host.Clients[i].DeviceId != 0 )
						{
							_buffer[0]++;
							_buffer[cnt++] = (uint)(_host.Clients[i].DeviceId | (i << 16));
						}
					}
					return (int)cnt;
				}
				return 0;

			case Command.LinkPower:
				_buffer[0] = WifiMode switch
				{
					WifiState.Host =>
						(_host.Clients[0].DeviceId != 0 ? 0x000000FFu : 0u) |
						(_host.Clients[1].DeviceId != 0 ? 0x0000FF00u : 0u) |
						(_host.Clients[2].DeviceId != 0 ? 0x00FF0000u : 0u) |
						(_host.Clients[3].DeviceId != 0 ? 0xFF000000u : 0u),
					WifiState.Client => 0xFFFFFFFFu,
					_ => 0u,
				};
				return 1;

			case Command.BroadcastReadStart:
				return 0;

			case Command.BroadcastReadStop:
			case Command.BroadcastReadFetch:
				{
					uint start = (uint)(_rng.Next() % MaxPeers);
					uint cnt = 0;
					for ( uint j = 0; cnt < 4 * 7 && j < MaxPeers; j++ )
					{
						uint entry = (start + j) % MaxPeers;
						if ( _peers[entry].Valid )
						{
							_buffer[cnt++] = _peers[entry].DeviceId;
							for ( int k = 0; k < 6; k++ )
								_buffer[cnt++] = _peers[entry].Data[k];
						}
					}
					return (int)cnt;
				}

			case Command.BroadcastData:
				if ( _payloadLength == 6 )
					Array.Copy( _buffer, 0, _host.BroadcastData, 0, 6 );
				return 0;

			case Command.HostStart:
				if ( WifiMode == WifiState.Client )
					return -1;
				if ( WifiMode == WifiState.Idle )
				{
					_host.DeviceId = NewDeviceId();
					for ( int k = 0; k < MaxClients; k++ )
						ClearClientSlot( k );
					WifiMode = WifiState.Host;
				}
				_host.BroadcastTtl = 0xFF;
				return 0;

			case Command.HostStop:
				if ( WifiMode == WifiState.Idle )
					return -1;
				if ( WifiMode == WifiState.Host )
				{
					for ( int i = 0; i < MaxClients; i++ )
						if ( _host.Clients[i].DeviceId != 0 )
							return 0;
					WifiMode = WifiState.Idle;
				}
				return 0;

			case Command.HostAccept:
				{
					if ( WifiMode == WifiState.Idle )
						return -1;
					uint cnt = 0;
					for ( int i = 0; i < MaxClients; i++ )
						if ( _host.Clients[i].DeviceId != 0 )
							_buffer[cnt++] = (uint)(_host.Clients[i].DeviceId | (i << 16));
					return (int)cnt;
				}

			case Command.Connect:
				{
					if ( WifiMode == WifiState.Host )
						return -1;
					ushort reqId = (ushort)(_buffer[0] & 0xFFFF);
					for ( int i = 0; i < MaxPeers; i++ )
					{
						if ( _peers[i].Valid && _peers[i].DeviceId == reqId )
						{
							SendNetCommand( i, NetType.ConnectRequest, reqId );
							WifiMode = WifiState.Connecting;
							return 0;
						}
					}
					return 0;
				}

			case Command.IsConnected:
				if ( WifiMode == WifiState.Host )
					return -1;
				_buffer[0] = WifiMode switch
				{
					WifiState.Connecting => ConnInProgress,
					WifiState.Idle => ConnFailed,
					_ => (uint)(_client.DeviceId | (_client.SlotNumber << 16)),
				};
				return 1;

			case Command.ConnectComplete:
				if ( WifiMode == WifiState.Host )
					return -1;
				if ( WifiMode == WifiState.Client )
				{
					_buffer[0] = (uint)(_client.DeviceId | (_client.SlotNumber << 16));
				}
				else
				{
					_buffer[0] = ConnCompleteFail;
					WifiMode = WifiState.Idle;
				}
				return 1;

			case Command.SendDataWait:
			case Command.SendData:
				if ( _payloadLength == 0 )
					return 0;
				if ( WifiMode == WifiState.Host )
				{
					_txBuffer.Length = (byte)(_buffer[0] & 0x7F);
					CopyWords( _buffer, 1, _txBuffer.Data, 0, _payloadLength - 1 );
				}
				else if ( WifiMode == WifiState.Client )
				{
					_txBuffer.Length = (byte)((_buffer[0] >> (8 + _client.SlotNumber * 5)) & 0x1F);
					CopyWords( _buffer, 1, _txBuffer.Data, 0, _payloadLength - 1 );
				}
				goto case Command.RetransmitWait;

			case Command.RetransmitWait:
				if ( WifiMode == WifiState.Host )
				{
					if ( _txBuffer.Length <= 90 )
					{
						for ( int i = 0; i < MaxClients; i++ )
							if ( _host.Clients[i].DeviceId != 0 )
								SendNetData( _host.Clients[i].ClientId, NetType.HostSend,
									_txBuffer.Length, _txBuffer.Data, _txBuffer.Length );
					}
				}
				else if ( WifiMode == WifiState.Client )
				{
					if ( _txBuffer.Length <= 16 )
					{
						uint hdr = ((uint)_txBuffer.Length << 24)
							| ((uint)_client.SlotNumber << 16)
							| _client.DeviceId;
						SendNetData( _client.HostId, NetType.ClientSend, hdr, _txBuffer.Data, _txBuffer.Length );
					}
				}
				else
				{
					return -1;
				}
				break;

			case Command.ReceiveData:
				return ReadInbound();

			case Command.Wait:
				return 0;

			case Command.Disconnect:
				if ( WifiMode == WifiState.Client )
				{
					SendNetCommand( _client.HostId, NetType.Disconnect,
						(uint)(_client.DeviceId | (_client.SlotNumber << 16)) );
					WifiMode = WifiState.Idle;
				}
				else if ( WifiMode == WifiState.Host )
				{
					for ( int i = 0; i < MaxClients; i++ )
					{
						if ( (_buffer[0] & (1u << i)) != 0 )
						{
							SendNetCommand( _host.Clients[i].ClientId, NetType.Disconnect,
								(uint)(_host.Clients[i].DeviceId | (i << 16)) );
							ClearClientSlot( i );
						}
					}
				}
				return 0;
		}

		return 0;
	}

	private int ReadInbound()
	{
		if ( WifiMode == WifiState.Host )
		{
			uint cnt = 1;
			uint bufBytes = 0;
			byte[] tmp = new byte[HostPacketSize * MaxClients];
			_buffer[0] = 0;
			for ( int i = 0; i < MaxClients; i++ )
			{
				ref var client = ref _host.Clients[i];
				uint dlen = Math.Min( (uint)HostPacketSize, client.Queue[0].Length );
				if ( client.DeviceId != 0 && dlen != 0 )
				{
					Buffer.BlockCopy( client.Queue[0].Data, 0, tmp, (int)bufBytes, (int)dlen );
					bufBytes += dlen;
					_buffer[0] |= dlen << (8 + i * 5);
					PopHostQueue( ref client );
				}
			}
			for ( int i = 0; i < (bufBytes + 3) / 4; i++ )
				_buffer[cnt++] = ReadLe32( tmp, i * 4 );
			return (int)cnt;
		}

		if ( WifiMode == WifiState.Client )
		{
			uint cnt = 1;
			uint dlen = _client.Queue[0].Length;
			_buffer[0] = dlen;
			for ( uint j = 0; j < (dlen + 3) / 4; j++ )
				_buffer[cnt++] = ReadLe32( _client.Queue[0].Data, (int)(j * 4) );
			PopClientQueue();
			return (int)cnt;
		}

		return 0;
	}

	private static void PopHostQueue( ref HostClientSlot client )
	{
		for ( int q = 0; q < HostQueueSize - 1; q++ )
		{
			client.Queue[q].Length = client.Queue[q + 1].Length;
			Buffer.BlockCopy( client.Queue[q + 1].Data, 0, client.Queue[q].Data, 0, HostPacketSize );
		}
		client.Queue[HostQueueSize - 1].Length = 0;
	}

	private void PopClientQueue()
	{
		for ( int q = 0; q < ClientQueueSize - 1; q++ )
		{
			_client.Queue[q].Length = _client.Queue[q + 1].Length;
			Buffer.BlockCopy( _client.Queue[q + 1].Data, 0, _client.Queue[q].Data, 0, ClientPacketSize );
		}
		_client.Queue[ClientQueueSize - 1].Length = 0;
	}

	private void ClearClientSlot( int slot )
	{
		ref var c = ref _host.Clients[slot];
		c.ClientId = 0;
		c.DeviceId = 0;
		c.TimeToLive = 0;
		for ( int p = 0; p < HostQueueSize; p++ )
		{
			c.Queue[p].Length = 0;
			Array.Clear( c.Queue[p].Data, 0, HostPacketSize );
		}
	}

	public void FrameUpdate()
	{
		if ( SpiState == ComState.Reset )
			return;

		DrainInbox();
		DrainDisconnects();

		for ( int i = 0; i < MaxPeers; i++ )
		{
			if ( _peers[i].Valid && --_peers[i].Ttl == 0 )
				_peers[i].Valid = false;
		}

		if ( WifiMode != WifiState.Host )
			return;

		if ( _host.BroadcastTtl++ >= BroadcastAnnounceFrames )
		{
			_host.BroadcastTtl = 0;
			SendNetBroadcast( NetType.Broadcast, _host.DeviceId, _host.BroadcastData );
		}

		for ( int i = 0; i < MaxClients; i++ )
		{
			if ( _host.Clients[i].DeviceId == 0 )
				continue;
			if ( ++_host.Clients[i].TimeToLive >= 240 )
				ClearClientSlot( i );
		}
	}

	private void ReceiveNetworkPacket( byte[] buf, int len, int clientId )
	{
		if ( len < 12 || ReadBe32( buf, 0 ) != NetHeaderMagic )
			return;

		var ptype = (NetType)ReadBe32( buf, 4 );
		uint hdata = ReadBe32( buf, 8 );
		const int payOff = 12;

		switch ( ptype )
		{
			case NetType.Broadcast:
				if ( clientId < MaxPeers )
				{
					ref var peer = ref _peers[clientId];
					peer.DeviceId = (ushort)hdata;
					peer.Valid = true;
					peer.Ttl = 0xFF;
					peer.Data ??= new uint[6];
					for ( int j = 0; j < 6; j++ )
						peer.Data[j] = ReadBe32( buf, payOff + j * 4 );
				}
				break;

			case NetType.ConnectRequest:
				HandleConnectRequest( clientId );
				break;

			case NetType.ConnectAck:
				if ( WifiMode == WifiState.Connecting )
				{
					InitClient();
					_client.DeviceId = (ushort)(hdata & 0xFFFF);
					_client.SlotNumber = (ushort)(hdata >> 16);
					_client.HostId = (ushort)clientId;
					WifiMode = WifiState.Client;
				}
				break;

			case NetType.ConnectNack:
				if ( WifiMode == WifiState.Connecting )
					WifiMode = WifiState.Idle;
				break;

			case NetType.Disconnect:
				HandleDisconnect( hdata );
				break;

			case NetType.HostSend:
				if ( WifiMode == WifiState.Client )
					HandleHostSend( buf, len, clientId, hdata, payOff );
				break;

			case NetType.ClientSend:
				if ( WifiMode == WifiState.Host )
					HandleClientSend( buf, hdata, payOff );
				goto case NetType.ClientAck;

			case NetType.ClientAck:
				if ( WifiMode == WifiState.Host )
				{
					ushort devId = (ushort)(hdata & 0xFFFF);
					int slot = (int)((hdata >> 16) & 0x3);
					if ( _host.Clients[slot].DeviceId == devId )
						_host.Clients[slot].TimeToLive = 0;
				}
				break;
		}
	}

	private void HandleConnectRequest( int clientId )
	{
		if ( WifiMode != WifiState.Host )
		{
			SendNetCommand( clientId, NetType.ConnectNack, 0 );
			return;
		}

		for ( int i = 0; i < MaxClients; i++ )
			if ( _host.Clients[i].DeviceId != 0 && _host.Clients[i].ClientId == clientId )
				return;

		for ( int i = 0; i < MaxClients; i++ )
		{
			if ( _host.Clients[i].DeviceId == 0 )
			{
				ushort newId = NewDeviceId();
				_host.Clients[i].DeviceId = newId;
				_host.Clients[i].ClientId = (ushort)clientId;
				SendNetCommand( clientId, NetType.ConnectAck, (uint)(newId | (i << 16)) );
				return;
			}
		}

		SendNetCommand( clientId, NetType.ConnectNack, 0 );
	}

	private void HandleDisconnect( uint hdata )
	{
		if ( WifiMode == WifiState.Host )
		{
			int slot = (int)((hdata >> 16) & 0x3);
			ushort devId = (ushort)(hdata & 0xFFFF);
			if ( _host.Clients[slot].DeviceId == devId )
				ClearClientSlot( slot );
		}
		else if ( WifiMode == WifiState.Client )
		{
			InitClient();
			WifiMode = WifiState.Idle;
		}
	}

	private void HandleHostSend( byte[] buf, int len, int clientId, uint hdata, int payOff )
	{
		uint blen = hdata & 0x7F;
		if ( len < blen + 12 )
			return;

		SendNetCommand( clientId, NetType.ClientAck,
			(uint)(_client.DeviceId | (_client.SlotNumber << 16)) );

		for ( int i = 0; i < ClientQueueSize; i++ )
		{
			if ( _client.Queue[i].Length == 0 )
			{
				Buffer.BlockCopy( buf, payOff, _client.Queue[i].Data, 0, (int)blen );
				_client.Queue[i].Length = (ushort)blen;
				return;
			}
		}
	}

	private void HandleClientSend( byte[] buf, uint hdata, int payOff )
	{
		ushort devId = (ushort)(hdata & 0xFFFF);
		int slot = (int)((hdata >> 16) & 0x3);
		uint blen = hdata >> 24;
		if ( _host.Clients[slot].DeviceId != devId )
			return;

		_host.Clients[slot].TimeToLive = 0;
		for ( int i = 0; i < HostQueueSize; i++ )
		{
			if ( _host.Clients[slot].Queue[i].Length == 0 )
			{
				Buffer.BlockCopy( buf, payOff, _host.Clients[slot].Queue[i].Data, 0, (int)blen );
				_host.Clients[slot].Queue[i].Length = blen;
				return;
			}
		}
	}

	public bool Update( uint cycles, ushort sioCnt, out bool wroteSio, out ushort newSioCnt, out ushort newSioDataLo, out ushort newSioDataHi )
	{
		wroteSio = false;
		newSioCnt = sioCnt;
		newSioDataLo = 0;
		newSioDataHi = 0;

		if ( SpiState == ComState.WaitEvent )
		{
			DrainInbox();
			_timeoutCycles -= Math.Min( cycles, _timeoutCycles );
			_responseTimeoutCycles -= Math.Min( cycles, _responseTimeoutCycles );

			if ( (sioCnt & 0x1) == 0 )
				EvaluateWaitEvent();
		}

		if ( SpiState == ComState.WaitResponse )
		{
			if ( (sioCnt & 0xC) == 0 && (sioCnt & 0x80) != 0 )
			{
				uint w = _buffer[_bufferIndex];
				newSioDataLo = (ushort)(w & 0xFFFF);
				newSioDataHi = (ushort)(w >> 16);
				newSioCnt = (ushort)(sioCnt & ~0x80);
				wroteSio = true;
				_bufferIndex++;
				if ( _bufferIndex == _payloadLength )
					SpiState = ComState.WaitCommand;
				return (newSioCnt & 0x4000) != 0;
			}
		}

		return false;
	}

	private void EvaluateWaitEvent()
	{
		if ( WifiMode == WifiState.Idle )
		{
			_buffer[0] = 0x99660000u | (1u << 8) | (byte)Command.ResponseDisconnect;
			_buffer[1] = 0xF;
			_buffer[2] = 0x80000000;
			_bufferIndex = 0;
			_payloadLength = 3;
			SpiState = ComState.WaitResponse;
		}
		else if ( HasInboundData() )
		{
			_buffer[0] = 0x99660000u | (byte)Command.ResponseData;
			_buffer[1] = 0x80000000;
			_bufferIndex = 0;
			_payloadLength = 2;
			SpiState = ComState.WaitResponse;
		}
		else if ( WifiMode == WifiState.Host && _responseTimeoutCycles == 0 )
		{
			_buffer[0] = 0x99660000u | (byte)Command.ResponseData | (1u << 8);
			_buffer[1] = 0x00000F0F;
			_buffer[2] = 0x80000000;
			_bufferIndex = 0;
			_payloadLength = 3;
			SpiState = ComState.WaitResponse;
		}
		else if ( _timeoutCycles == 0 )
		{
			_buffer[0] = 0x99660000u | (byte)Command.ResponseTimeout;
			_buffer[1] = 0x80000000;
			_bufferIndex = 0;
			_payloadLength = 2;
			SpiState = ComState.WaitResponse;
		}
	}

	private void SendNetCommand( int clientId, NetType type, uint hdata )
	{
		if ( Network is null )
			return;
		var pkt = new byte[16];
		WriteBe32( pkt, 0, NetHeaderMagic );
		WriteBe32( pkt, 4, (uint)type );
		WriteBe32( pkt, 8, hdata );
		Network.Send( clientId, pkt, 16 );
	}

	private void SendNetBroadcast( NetType type, uint hdata, uint[] payload )
	{
		if ( Network is null )
			return;
		var pkt = new byte[36];
		WriteBe32( pkt, 0, NetHeaderMagic );
		WriteBe32( pkt, 4, (uint)type );
		WriteBe32( pkt, 8, hdata );
		for ( int i = 0; i < 6; i++ )
			WriteBe32( pkt, 12 + i * 4, payload[i] );
		Network.Send( 0xFFFF, pkt, 36 );
	}

	private void SendNetData( int clientId, NetType type, uint hdata, uint[] payload, uint length )
	{
		if ( Network is null )
			return;
		var pkt = new byte[12 + 92];
		WriteBe32( pkt, 0, NetHeaderMagic );
		WriteBe32( pkt, 4, (uint)type );
		WriteBe32( pkt, 8, hdata );
		for ( int i = 0; i < length; i++ )
			pkt[12 + i] = (byte)(payload[i / 4] >> (8 * (i & 3)));
		Network.Send( clientId, pkt, 12 + 92 );
	}

	private static uint ReadBe32( byte[] b, int o )
		=> (uint)((b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]);

	private static uint ReadLe32( byte[] b, int o )
	{
		uint v = 0;
		int n = Math.Min( 4, b.Length - o );
		for ( int i = 0; i < n; i++ )
			v |= (uint)b[o + i] << (8 * i);
		return v;
	}

	private static void WriteBe32( byte[] b, int o, uint v )
	{
		b[o + 0] = (byte)(v >> 24);
		b[o + 1] = (byte)(v >> 16);
		b[o + 2] = (byte)(v >> 8);
		b[o + 3] = (byte)v;
	}

	private static void CopyWords( uint[] src, int srcOff, uint[] dst, int dstOff, int count )
	{
		if ( count <= 0 )
			return;
		int n = Math.Min( count, Math.Min( src.Length - srcOff, dst.Length - dstOff ) );
		Array.Copy( src, srcOff, dst, dstOff, n );
	}
}