diff --git a/PDGServer_WPF/App.xaml.cs b/PDGServer_WPF/App.xaml.cs
index 1c8b5ca..2fe763c 100644
--- a/PDGServer_WPF/App.xaml.cs
+++ b/PDGServer_WPF/App.xaml.cs
@@ -20,6 +20,7 @@ namespace RBG_Server
{
public GameServer BoardGameServer {get;}
public CommunicationHandler communicationHandler { get; private set;}
+
protected static readonly SolidColorBrush RED_BRUSH = new(Color.FromRgb(255, 0, 0));
protected static readonly SolidColorBrush BLUE_BRUSH = new(Color.FromRgb(0, 0, 255));
protected static readonly SolidColorBrush GREEN_BRUSH = new(Color.FromRgb(0, 255, 0));
diff --git a/RBG_Server.Core/CommunicationHandler.cs b/RBG_Server.Core/CommunicationHandler.cs
index 92fd9b8..737e4b9 100644
--- a/RBG_Server.Core/CommunicationHandler.cs
+++ b/RBG_Server.Core/CommunicationHandler.cs
@@ -1,7 +1,10 @@
-using System;
+using NATUPNPLib;
+using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
+using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
@@ -10,9 +13,13 @@ namespace RBG_Server
///
/// Contains all communication data, from both the client's and server's perspective (should be able to switch between each mode as necessary)
///
+ ///
public class CommunicationHandler
{
+ UPnPNAT upnpnat = new();
+ IStaticPortMapping portMapping;
+
///
/// Image data is stored in memory in a dictionary collection. Each byte[] represents an image file, compressed according to its file type;
/// which helps save space in memory. Uses a CachedByteArray; essentially a normal byte array but automatically stored on disk if it is larger
@@ -20,5 +27,202 @@ namespace RBG_Server
/// This limit can be changed at any time
///
public ConcurrentDictionary ImageCollection { get; } = new();
+ public List ImageList { get; } = new();
+ public string BoardName { get; set; }
+
+ public IPAddress IpAddress { get; set; }
+ public short Port { get; set; }
+
+ private TcpClient stateRetriever { get; set; }
+ private TcpClient dataRetriever { get; set; }
+
+
+ public int ColumnCount { get;private set; }
+ public int RowCount { get;private set; }
+ public int ColumnZoomStart { get;private set; }
+ public int RowZoomStart { get;private set; }
+ public int ColumnZoomSpan { get;private set; }
+ public int RowZoomSpan { get;private set; }
+ public int StartingColumn { get;private set; }
+ public int StartingRow { get;private set; }
+
+ public void InitialiseServer()
+ {
+ // Find the server's active (reliable) network adapter, by creating a remote connection and retrieving our IP from it:
+ using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
+ {
+ s.Bind(new IPEndPoint(IPAddress.Any, 0));
+ s.Connect("microsoft.com", 0);
+ // The IP is implicitly the one assigned to the interface the OS would use to connect to the remote address
+ IpAddress = (s.LocalEndPoint as IPEndPoint).Address;
+ }
+ // Create the upnp mapping
+ upnpnat.StaticPortMappingCollection.Add(Port, "TCP", Port, IpAddress.ToString(), true, "RBGServer");
+
+ TcpListener listener = new(IPAddress.Any, Port); // Allow local comms
+
+ listener.Start();
+ while (true)
+ {
+ TcpClient client = listener.AcceptTcpClient();
+ // Delegate to another thread
+ _ = Task.Run(() =>
+ {
+ AcceptConnections(client);
+ });
+ }
+ // At this point, the ImageCollection will have already been initialised.
+ // The name of the board will also be set to {0}..
+ // Server logic, such as accepting players and maintaining communication goes in here
+ }
+
+ private void AcceptConnections(TcpClient client)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void InitialiseClient(TcpClient client)
+ {
+ // At this point, no details about the game are loaded; we must load them from the server (to which we have already connected [no data should have been sent]).
+ NetworkStream stateStream = stateRetriever.GetStream();
+ Task stateLoader = new Task(async () =>
+ {
+ // Get game board details
+ byte[] buffer = new byte[] { 1, 0, 0, 0, 1 };
+ stateStream.Write(buffer, 0, buffer.Length);
+ // Response size
+ var stateResponse = await GetResponse(stateStream, 4);
+ // Response data
+ stateResponse = await GetResponse(stateStream, GetInt32(stateResponse)); // Get the full response data
+ // Board state data
+ ColumnCount = GetInt32(stateResponse[..4]);
+ RowCount = GetInt32(stateResponse[4..8]);
+ ColumnZoomStart = GetInt32(stateResponse[8..12]);
+ RowZoomStart = GetInt32(stateResponse[12..16]);
+ ColumnZoomSpan = GetInt32(stateResponse[16..20]);
+ RowZoomSpan = GetInt32(stateResponse[20..24]);
+ StartingColumn = GetInt32(stateResponse[24..28]);
+ StartingRow = GetInt32(stateResponse[28..32]);
+ // Basic board data loaded; fetch players
+ buffer = new byte[] { 1, 0, 0, 0, 2};
+ stateStream.Write(buffer, 0, buffer.Length);
+ stateResponse = await GetResponse(stateStream, 4);
+ stateResponse = await GetResponse(stateStream, GetInt32(stateResponse));
+ // state response contains a player list
+ });
+
+
+
+ NetworkStream dataStream = dataRetriever.GetStream();
+ Task dataLoader = new Task(async () =>
+ {
+ byte[] buffer = new byte[] { 2, 0, 0, 0, 128, 0 }; // Get image collection names
+ dataStream.Write(buffer, 0, buffer.Length);
+ var dataResponse = await GetResponse(stateStream, 4);
+
+ dataResponse = await GetResponse(dataStream, GetInt32(dataResponse));
+ List data = new List();
+ for (int i = 0; i < dataResponse.Length; i++)
+ {
+ if (dataResponse[i] != 0)
+ {
+ data.Add(dataResponse[i]);
+ }
+ else
+ {
+ ImageList.Add(Encoding.UTF8.GetString(data.ToArray()));
+ data.Clear();
+ }
+ }
+
+ // Load all low-resolution images
+ foreach (string item in ImageList)
+ {
+ byte[] strBytes = Encoding.ASCII.GetBytes(item);
+ buffer = new byte[strBytes.Length + 6];
+ byte[] lenBytes = GetBytes(strBytes.Length + 6);
+ lenBytes.CopyTo(buffer, 0);
+ buffer[4] = 129; // Download image
+ buffer[5] = 0; // mip_low
+ strBytes.CopyTo(buffer, 6);
+ dataStream.Write(buffer, 0, buffer.Length);
+ // Read the length, then the data
+ dataResponse = await GetResponse(dataStream);
+ dataResponse = await GetResponse(dataStream, GetInt32(dataResponse));
+ ImageCollection.TryAdd(item + "_mip_low", (CachedByteArray)dataResponse);
+ }
+ // At this point, the minimal amount of work required by the data thread has been done (load all thumbs)
+ // When an asset is needed from here, queue a load
+ });
+
+ dataLoader.Start();
+ stateLoader.Start();
+
+
+ byte[] buffer = new byte[] {0,0,0,1,1};
+ // Writing to the stream is to be considered near constant-time, but reading is non-constant.
+ // This application model must be synchronous, but we execute other commands before expecting our response to have arrived (it can complete at any time in that period)
+ stateStream.Write(buffer, 0, buffer.Length);
+ buffer[0] = 1;
+ buffer[3] = 0;
+ dataStream.Write(buffer, 0, buffer.Length);
+ // A details request is [, ]
+ // The response is about the same format:
+ var stateResponse = GetResponse(stateStream, 4);
+ var dataResponse = GetResponse(dataStream, 4);
+ // First, load the board state (low mip-map, row definitions, column definitions, zoom position etc.)
+ // Retrieval command for the board
+ // Then load the player list, and use the low mip for their sprites
+ // Then check each loaded image and load the med, then large, then full sprite
+ // Then load the low of each unused image (for quick retrieval)
+ }
+
+ ///
+ /// Idea is that each response is prefaced by a 4 byte stream length specifier.
+ /// This requires a busy wait to achieve, if not all recieved at once.
+ /// We retrieve the
+ ///
+ ///
+ ///
+ ///
+ private async Task GetResponse(NetworkStream stream, int targetLength=4)
+ {
+ if (stream.CanRead)
+ {
+ byte[] buffer = new byte[targetLength];
+ StringBuilder myCompleteMessage = new StringBuilder();
+ int numberOfBytesRead = 0;
+
+ // Incoming message may be larger than the buffer size.
+ do
+ {
+ numberOfBytesRead += await stream.ReadAsync(buffer, numberOfBytesRead, targetLength - numberOfBytesRead);
+ }
+ while (numberOfBytesRead < targetLength);
+ return buffer;
+ }
+ return Array.Empty();
+ }
+
+ static internal byte[] GetBytes(int i)
+ {
+ byte[] bytes = BitConverter.GetBytes(i);
+ if (BitConverter.IsLittleEndian)
+ {
+ // We need data in big-endian format; so reverse the array
+ Array.Reverse(bytes);
+ }
+ return bytes;
+ }
+
+ static internal int GetInt32(byte[] bytes)
+ {
+ if (BitConverter.IsLittleEndian)
+ {
+ // We need data in little-endian format; so reverse the array
+ Array.Reverse(bytes);
+ }
+ return BitConverter.ToInt32(bytes, 0);
+ }
}
}
diff --git a/RBG_Server.Core/RBG_Server.csproj b/RBG_Server.Core/RBG_Server.csproj
index 76214ae..7f15cb2 100644
--- a/RBG_Server.Core/RBG_Server.csproj
+++ b/RBG_Server.Core/RBG_Server.csproj
@@ -4,6 +4,18 @@
net5.0-windows10.0.19041.0
+
+
+ tlbimp
+ 0
+ 1
+ 1c565858-f302-471e-b409-f180aa4abec6
+ 0
+ false
+ true
+
+
+