diff --git a/DaS-PC-MPChan/Config.vb b/DaS-PC-MPChan/Config.vb
index b3fc51e..63ae251 100644
--- a/DaS-PC-MPChan/Config.vb
+++ b/DaS-PC-MPChan/Config.vb
@@ -1,4 +1,10 @@
Module Config
+ 'How frequently should we update the node list from the central server?
+ Public Const UpdateNetNodesInterval = 120 * 1000
+
+ 'How frequently should we publish our nodes to the central server?
+ Public Const PublishNodesInterval = 60 * 1000
+
'How frequently should we connect to node from IRC?
Public Const IRCNodeConnectInterval = 10 * 1000
diff --git a/DaS-PC-MPChan/DSCM.vbproj b/DaS-PC-MPChan/DSCM.vbproj
index 965cb81..70748c6 100644
--- a/DaS-PC-MPChan/DSCM.vbproj
+++ b/DaS-PC-MPChan/DSCM.vbproj
@@ -81,6 +81,8 @@
+
+
@@ -111,7 +113,7 @@
Form
-
+
True
@@ -192,7 +194,9 @@
-
+
+
+
$(SolutionDir)\packages\CommonMark.NET.0.12.0\tools\cmark.exe "$(SolutionDir)\Readme.md" --out $(ProjectDir)Resources\Readme_compiled.html
diff --git a/DaS-PC-MPChan/DSNode.vb b/DaS-PC-MPChan/DSNode.vb
index 913002b..0c9272d 100644
--- a/DaS-PC-MPChan/DSNode.vb
+++ b/DaS-PC-MPChan/DSNode.vb
@@ -196,4 +196,4 @@ Public Class DSNode
Return (SoulLevel - (50 + SoulLevel * 0.2) < other.SoulLevel And
other.SoulLevel < SoulLevel + (10 + SoulLevel * 0.1))
End Function
-End Class
+End Class
\ No newline at end of file
diff --git a/DaS-PC-MPChan/IRCClient.vb b/DaS-PC-MPChan/IRCClient.vb
deleted file mode 100644
index bea1b60..0000000
--- a/DaS-PC-MPChan/IRCClient.vb
+++ /dev/null
@@ -1,242 +0,0 @@
-Imports System.Threading
-Imports System.IO
-Imports System.Net.Sockets
-Imports System.Collections.Concurrent
-Imports System.Text.RegularExpressions
-
-Class IRCConnectionError
- Inherits System.ApplicationException
-
- Sub New(message As String)
- MyBase.New(message)
- End Sub
-End Class
-
-Public Class IRCClient
- Private mainWindow As MainWindow
-
- Private _thread As Thread
- Private tcpClient As System.Net.Sockets.TcpClient
- Private stream As NetworkStream
- Private _streamWriter As StreamWriter = Nothing
- Private _streamReader As StreamReader = Nothing
-
- Private shouldQuit As Boolean = False
-
- Private localNodesLock As New Object()
- Private localNodes As New List(Of DSNode)
- Private selfNode As DSNode
- Public ircNodes As New ConcurrentDictionary(Of String, Tuple(Of DSNode, Date))
-
- Public Sub New(mainWindow As MainWindow)
- Me.mainWindow = mainWindow
- _thread = New Thread(AddressOf main)
- _thread.IsBackground = True
- _thread.Start()
- End Sub
-
- Public Sub setLocalNodes(self As DSNode, nodes As IEnumerable(Of DSNode))
- SyncLock localNodesLock
- localNodes = nodes.ToList()
- selfNode = self
- End SyncLock
- End Sub
-
- Public Sub Shutdown()
- shouldQuit = True
- End Sub
-
- Private Sub main(args As String())
- Try
- While Not shouldQuit
- Try
- setStatus("Initiating connection ...")
- connectToServer()
- setStatus("Connected.")
- messageLoop()
- Catch ex As IRCConnectionError
- If Not shouldQuit Then
- setStatus("Error: " & ex.Message & " – retrying in 10 seconds")
- Thread.Sleep(10000)
- End If
- End Try
- If tcpClient IsNot Nothing Then
- tcpClient.Close()
- tcpClient = Nothing
- End If
- End While
- setStatus("Disconnected.")
- Catch ex As Exception
- setStatus("DSCMNet crashed with: " & ex.Message)
- End Try
- End Sub
-
- Private Sub connectToServer()
- Dim nick As String = Config.IRCNick & "[" & mainWindow.Version.Replace(".", "-") & "]" & Guid.NewGuid.ToString().Split("-")(0)
-
- tcpClient = New System.Net.Sockets.TcpClient()
- tcpClient.Connect(Config.IRCHost, Config.IRCPort)
- If Not tcpClient.Connected Then
- Throw New IRCConnectionError("Failed to connect")
- End If
- stream = tcpClient.GetStream()
- _streamReader = New StreamReader(stream)
- _streamWriter = New StreamWriter(stream)
- _streamWriter.AutoFlush = True
-
- _streamWriter.Write("USER " & nick & " 0 * :" & Config.IRCOwner & vbCr & vbLf)
- _streamWriter.Write("NICK " & nick & vbCr & vbLf)
- _streamWriter.Write("MODE " & nick & " +B" & vbCr & vbLf)
-
- 'Join channel on start
- While True
- If isConnectionDropped() Then
- Throw New IRCConnectionError("Connection was dropped during init")
- End If
- Dim buf As String = _streamReader.ReadLine()
- If buf IsNot Nothing Then
- If buf.StartsWith("PING ") Then
- _streamWriter.Write(buf.Replace("PING", "PONG") & vbCr & vbLf)
- ElseIf buf.Contains(":MOTD") Then
- _streamWriter.Write("JOIN " & Config.IRCChannel & vbCr & vbLf)
- Exit While
- End If
- End If
- End While
- End Sub
- Private Sub messageLoop()
- Dim lastPublish As Date = DateTime.UtcNow
- While True
- If shouldQuit Then
- _streamWriter.Write("QUIT" & vbCr & vbLf)
- Exit While
- ElseIf isConnectionDropped() Then
- Throw New IRCConnectionError("Connection was dropped")
- ElseIf (DateTime.UtcNow - lastPublish).TotalSeconds >= Config.IRCPublishInterval Then
- lastPublish = DateTime.UtcNow
- publishLocalNodes()
- expireIrcNodes()
- ElseIf stream.DataAvailable Then
- handleIRCLine(_streamReader.ReadLine())
- Else
- Thread.Sleep(50)
- End If
- End While
- End Sub
- Private Function isConnectionDropped() As Boolean
- Return tcpClient.Client.Poll(0, SelectMode.SelectRead) AndAlso tcpClient.Client.Available = 0
- End Function
- Private Sub handleIRCLine(line As String)
- Dim prefix As String = Nothing
- If line.StartsWith(":") Then
- prefix = line.Split({" "c}, 2)(0).Substring(1)
- line = line.Split({" "c}, 2)(1)
- End If
- Dim parts = line.Split({" "c}, 2)
- Dim command As String = parts(0)
- Dim rest As String = parts.ElementAtOrDefault(1)
-
- If command = "PING" Then
- _streamWriter.Write("PONG " & rest & vbCr & vbLf)
- ElseIf command = "PRIVMSG" Then
- Dim msg As String = rest.Split({" "c}, 2)(1).Substring(1)
- If msg.StartsWith("REPORT|") Or msg.StartsWith("REPORTSELF|") Then
- Dim inNode As DSNode
- Try
- inNode = parseNodeReport(msg.Split("|")(1))
- Catch ex As Exception
-#If DEBUG Then
- Dim senderNick As String = Regex.Match(prefix, "^([^!@]+)").Groups.Item(1).Value
- setStatus("Invalid: " & senderNick & " " & msg)
-#End If
- Return
- End Try
- If (ircNodes.ContainsKey(inNode.SteamId) AndAlso
- Not inNode.HasExtendedInfo AndAlso
- ircNodes(inNode.SteamId).Item1.HasExtendedInfo) Then
- inNode.Covenant = ircNodes(inNode.SteamId).Item1.Covenant
- inNode.Indictments = ircNodes(inNode.SteamId).Item1.Indictments
- End If
- ircNodes(inNode.SteamId) = Tuple.Create(inNode, DateTime.UtcNow)
- End If
- End If
- End Sub
-
- Private Function parseNodeReport(text) As DSNode
- Dim node As New DSNode()
- Dim tmpFields()
-
- tmpFields = text.Split(",")
-
- node.CharacterName = tmpFields(0)
- node.SteamId = tmpFields(1)
- node.SoulLevel = tmpFields(2)
- node.PhantomType = tmpFields(3)
- node.MPZone = tmpFields(4)
- node.World = tmpFields(5)
-
- If tmpFields.Count > 6 Then
- node.Covenant = tmpFields(6)
- node.Indictments = tmpFields(7)
- End If
-
- Return node
- End Function
- Private Sub publishLocalNodes()
- 'Report your active node status
- Try
- SyncLock localNodesLock
- For Each node In localNodes
- 'Check if node was already reported in the last 3 minutes
- Dim networkKnowsNode = (
- ircNodes.ContainsKey(node.SteamId) AndAlso
- (DateTime.UtcNow - ircNodes(node.SteamId).Item2).TotalSeconds <= IRCNodePublishInterval)
- If networkKnowsNode Then
- 'Publish anyways if the information in the network is incorrect
- Dim networkInfoIsStale = (
- Not ircNodes(node.SteamId).Item1.BasicEquals(node) AndAlso
- (DateTime.UtcNow - ircNodes(node.SteamId).Item2).TotalSeconds >= IRCNodeUpdateInterval)
- If Not networkInfoIsStale Then Continue For
- End If
-
- Dim ircName As String = node.CharacterName
- ircName = ircName.Replace(",", "")
- ircName = ircName.Replace("|", "")
- Dim reportData As String = (
- ircName & "," & node.SteamId & "," & node.SoulLevel & "," &
- node.PhantomType & "," & node.MPZone & "," & node.World)
- _streamWriter.Write("PRIVMSG #DSCM-Main REPORT|" & reportData & vbCr & vbLf)
- Next
- If selfNode IsNot Nothing Then
- Dim ircName As String = selfNode.CharacterName
- ircName = ircName.Replace(",", "")
- ircName = ircName.Replace("|", "")
- Dim reportData As String = (
- ircName & "," & selfNode.SteamId & "," & selfNode.SoulLevel & "," &
- selfNode.PhantomType & "," & selfNode.MPZone & "," & selfNode.World & "," &
- selfNode.Covenant & "," & selfNode.Indictments)
- _streamWriter.Write("PRIVMSG #DSCM-Main REPORTSELF|" & reportData & vbCr & vbLf)
- Console.WriteLine(reportData)
- End If
- End SyncLock
- Catch ex As Exception
- setStatus("Error publishing local nodes: " & ex.Message)
- End Try
- End Sub
- Private Sub expireIrcNodes()
- Dim now As Date = DateTime.UtcNow
- For Each t In ircNodes.Values.ToList()
- If (now - t.Item2).TotalSeconds >= Config.IRCNodeTTL Then
- ircNodes.TryRemove(t.Item1.SteamId, t)
- End If
- Next
- End Sub
- Private Sub setStatus(status As String)
- If mainWindow.InvokeRequired Then
- mainWindow.Invoke(New setStatusDelegate(AddressOf setStatus), {status})
- Else
- mainWindow.txtIRCDebug.Text = status
- End If
- End Sub
- Private Delegate Sub setStatusDelegate(status As String)
-End Class
\ No newline at end of file
diff --git a/DaS-PC-MPChan/MainWindow.vb b/DaS-PC-MPChan/MainWindow.vb
index 2e41547..9046bef 100644
--- a/DaS-PC-MPChan/MainWindow.vb
+++ b/DaS-PC-MPChan/MainWindow.vb
@@ -9,8 +9,10 @@ Public Class MainWindow
'Timers
Private WithEvents updateActiveNodesTimer As New System.Windows.Forms.Timer()
Private WithEvents updateUITimer As New System.Windows.Forms.Timer()
+ Private WithEvents updateNetNodesTimer As New System.Windows.Forms.Timer()
Private WithEvents updateOnlineStateTimer As New System.Windows.Forms.Timer()
- Private WithEvents ircNodeConnectTimer As New System.Windows.Forms.Timer()
+ Private WithEvents netNodeConnectTimer As New System.Windows.Forms.Timer()
+ Private WithEvents publishNodesTimer As New System.Windows.Forms.Timer()
Private WithEvents dsAttachmentTimer As New System.Windows.Forms.Timer()
Private WithEvents hotkeyTimer As New System.Windows.Forms.Timer()
@@ -25,7 +27,7 @@ Public Class MainWindow
Public Version As String
Private dsProcess As DarkSoulsProcess = Nothing
- Private _ircClient As IRCClient = Nothing
+ Private _netClient As NetClient = Nothing
Private ircDisplayList As New DSNodeBindingList()
Private activeNodesDisplayList As New DSNodeBindingList()
Private connectedNodes As New Dictionary(Of String, ConnectedNode)
@@ -79,7 +81,9 @@ Public Class MainWindow
dsAttachmentTimer.Start()
updateOnlineStateTimer.Interval = Config.OnlineCheckInterval
updateOnlineStateTimer.Start()
- ircNodeConnectTimer.Interval = Config.IRCNodeConnectInterval
+ updateNetNodesTimer.Interval = Config.UpdateNetNodesInterval
+ netNodeConnectTimer.Interval = Config.IRCNodeConnectInterval
+ publishNodesTimer.Interval = Config.PublishNodesInterval
attachDSProcess()
@@ -356,8 +360,8 @@ Public Class MainWindow
Me.Close()
End If
End Sub
- Private Sub connectToIRCNode() Handles ircNodeConnectTimer.Tick
- If (_ircClient Is Nothing OrElse
+ Private Sub connectToIRCNode() Handles netNodeConnectTimer.Tick
+ If (_netClient Is Nothing OrElse
dsProcess Is Nothing OrElse
dsProcess.SelfSteamId = "" OrElse
dsProcess.SelfNode.CharacterName = "" OrElse
@@ -384,8 +388,7 @@ Public Class MainWindow
Next
Dim candidates As New List(Of DSNode)
- For Each t In _ircClient.ircNodes.Values
- Dim node As DSNode = t.Item1
+ For Each node In _netClient.netNodes.Values
If blackSet.Contains(node.SteamId) Then Continue For
candidates.Add(node)
Next
@@ -492,7 +495,7 @@ Public Class MainWindow
Return 2
End Function
Private Sub handleDisconnects()
- If _ircClient Is Nothing Or dsProcess Is Nothing Then Return
+ If _netClient Is Nothing Or dsProcess Is Nothing Then Return
If dsProcess.SelfNode.PhantomType = -1 Then Return
Dim now As Date = Date.UtcNow
@@ -565,14 +568,22 @@ Public Class MainWindow
txtCurrNodes.Text = dsProcess.NodeCount
End If
- If _ircClient IsNot Nothing Then
- ircDisplayList.SyncWithDict(_ircClient.ircNodes, Function(x) x.Item1, dgvDSCMNet)
- End If
If Not tabDSCMNet.Text = "DSCM-Net (" & dgvDSCMNet.Rows.Count & ")" Then
tabDSCMNet.Text = "DSCM-Net (" & dgvDSCMNet.Rows.Count & ")"
End If
End Sub
+ Private Async Sub updateNetNodes() Handles updateNetNodesTimer.Tick
+ If _netClient IsNot Nothing Then
+ Await _netClient.loadNodes()
+ ircDisplayList.SyncWithDict(_netClient.netNodes, dgvDSCMNet)
+ End If
+ End Sub
+ Private Async Sub publishNodes() Handles publishNodesTimer.Tick
+ If _netClient IsNot Nothing AndAlso dsProcess IsNot Nothing AndAlso dsProcess.SelfNode.SteamId IsNot Nothing Then
+ Await _netClient.publishLocalNodes(dsProcess.SelfNode, dsProcess.ConnectedNodes.Values())
+ End If
+ End Sub
Private Shared Sub hotkeyTimer_Tick() Handles hotkeyTimer.Tick
Dim ctrlkey As Boolean
Dim oneKey As Boolean 'Toggle Node Display
@@ -642,10 +653,7 @@ Public Class MainWindow
selfNode = dsProcess.SelfNode.Clone()
Else
connectedNodes.Clear()
- End If
- If _ircClient IsNot Nothing Then
- _ircClient.setLocalNodes(selfNode, connectedNodes.Select(Function(kv, i) kv.Value.node))
End If
Dim activeNodes = connectedNodes.ToDictionary(Function(kv) kv.Key, Function(kv) kv.Value.node)
@@ -884,14 +892,17 @@ Public Class MainWindow
key.SetValue("JoinDSCM-Net", chkDSCMNet.Checked)
If chkDSCMNet.Checked Then
- _ircClient = New IRCClient(Me)
- ircNodeConnectTimer.Start()
+ _netClient = New NetClient(Me)
+ netNodeConnectTimer.Start()
+ updateNetNodesTimer.Start()
+ publishNodesTimer.Start()
+ updateNetNodes()
Else
- If _ircClient IsNot Nothing Then
- ircNodeConnectTimer.Stop()
- _ircClient.Shutdown()
- _ircClient = Nothing
- ircDisplayList.Clear()
+ If _netClient IsNot Nothing Then
+ updateNetNodesTimer.Stop()
+ netNodeConnectTimer.Stop()
+ publishNodesTimer.Stop()
+ _netClient = Nothing
End If
End If
End Sub
diff --git a/DaS-PC-MPChan/NetClient.vb b/DaS-PC-MPChan/NetClient.vb
new file mode 100644
index 0000000..f61f28c
--- /dev/null
+++ b/DaS-PC-MPChan/NetClient.vb
@@ -0,0 +1,111 @@
+Imports System.Threading
+Imports System.IO
+Imports System.Net.Sockets
+Imports System.Text.RegularExpressions
+Imports System.Web.Script.Serialization
+Imports System.Net.Http
+
+Public Class NetClient
+ Private mainWindow As MainWindow
+ Public netNodes As New Dictionary(Of String, DSNode)
+ Private client As HttpClient
+
+ Public Sub New(mainWindow As MainWindow)
+ Dim handler = New HttpClientHandler()
+ handler.AutomaticDecompression = Net.DecompressionMethods.GZip Or Net.DecompressionMethods.Deflate
+ client = New HttpClient(handler)
+ Me.mainWindow = mainWindow
+ End Sub
+ Private Function JSONContent(data As Object) As StringContent
+ Dim serializer As New JavaScriptSerializer()
+ serializer.RegisterConverters({New DSNodeSerializer()})
+ Dim str As String = serializer.Serialize(data)
+ Dim content As New StringContent(str, New System.Text.UTF8Encoding())
+ content.Headers.ContentType = New Headers.MediaTypeHeaderValue("application/json")
+ Return content
+ End Function
+ Public Async Function publishLocalNodes(self As DSNode, nodes As IEnumerable(Of DSNode)) As Task
+ Dim data As New Dictionary(Of String, Object)() From {
+ {"self", self},
+ {"nodes", nodes}
+ }
+ Dim content = JSONContent(data)
+ Try
+ Dim response As HttpResponseMessage = Await client.PostAsync("http://dscm-net.chronial.de:8811/store", content)
+ response.EnsureSuccessStatusCode()
+ Catch ex As Exception
+ setStatus("Error publishing local nodes: " & ex.Message)
+ End Try
+ End Function
+ Public Async Function loadNodes() As Task(Of IEnumerable(Of DSNode))
+ Try
+ Dim response As HttpResponseMessage = Await client.GetAsync("http://dscm-net.chronial.de:8811/list")
+ response.EnsureSuccessStatusCode()
+ Dim content = Await response.Content.ReadAsStringAsync()
+ Dim serializer As New JavaScriptSerializer()
+ Dim dsNodeSer As New DSNodeSerializer()
+ Dim data As IDictionary(Of String, Object) = serializer.Deserialize(Of Dictionary(Of String, Object))(content)
+ Dim nodes As IEnumerable = data("nodes")
+ netNodes = New Dictionary(Of String, DSNode)
+ For Each n As Dictionary(Of String, Object) In nodes
+ Dim node As DSNode = dsNodeSer.NodeFromDict(n)
+ netNodes(node.SteamId) = node
+ Next
+ Catch ex As Exception
+ setStatus("Error loading node list: " & ex.Message)
+ End Try
+ Return Nothing
+ End Function
+ Private Sub setStatus(status As String)
+ If mainWindow.InvokeRequired Then
+ mainWindow.Invoke(New setStatusDelegate(AddressOf setStatus), {status})
+ Else
+ mainWindow.txtIRCDebug.Text = status
+ End If
+ End Sub
+ Private Delegate Sub setStatusDelegate(status As String)
+End Class
+
+
+
+Public Class DSNodeSerializer
+ Inherits JavaScriptConverter
+
+ Public Function NodeFromDict(dict As IDictionary(Of String, Object)) As DSNode
+ Dim node As New DSNode()
+ node.SteamId = dict("steamid")
+ node.CharacterName = dict("name")
+ node.SoulLevel = dict("sl")
+ node.PhantomType = dict("phantom_type")
+ node.MPZone = dict("mp_zone")
+ node.World = dict("world")
+ If dict.ContainsKey("covenant") Then
+ node.Covenant = dict("covenant")
+ node.Indictments = dict("indictments")
+ End If
+ Return node
+ End Function
+ Public Overrides Function Deserialize(dict As IDictionary(Of String, Object), type As Type, serializer As JavaScriptSerializer) As Object
+ Return NodeFromDict(dict)
+ End Function
+ Public Overrides Function Serialize(obj As Object, serializer As JavaScriptSerializer) As IDictionary(Of String, Object)
+ Dim node As DSNode = CType(obj, DSNode)
+ Dim out As New Dictionary(Of String, Object)
+ out("steamid") = node.SteamId
+ out("name") = node.CharacterName
+ out("sl") = node.SoulLevel
+ out("phantom_type") = node.PhantomType
+ out("mp_zone") = node.MPZone
+ out("world") = node.World
+ If node.HasExtendedInfo Then
+ out("covenant") = node.Covenant
+ out("indictments") = node.Indictments
+ End If
+ Return out
+ End Function
+ Public Overrides ReadOnly Property SupportedTypes As IEnumerable(Of Type)
+ Get
+ Return New List(Of Type)({GetType(DSNode)})
+ End Get
+ End Property
+End Class
\ No newline at end of file