コード書いてみました。流れるパケットを見て、Live Messengerのメッセージ内容だけ取り出します。Live Messengerの仕様が公開されているわけではないので、パケットを実際に解析してみて書いたものになります。
まずは、パケットキャプチャを開始するメソッド。Socketオブジェクトを作って、設定後、BeginReceiveメソッドを呼びます。引数には、コールバック関数を指定しています。Bindメソッドを呼んでいるところで指定しているIpAddressはPrivate変数です。この後、出てきます。
Const BufferSize As Integer = 4096
Private Socket As Socket
Private Buffer(BufferSize) As Byte
Public Sub BeginReceive()
Socket = New Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP)
Socket.Blocking = False
Socket.Bind(New IPEndPoint(IpAddress, 0))
Socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AcceptConnection, True)
Socket.IOControl(IOControlCode.ReceiveAll, New Byte() {1, 0, 0, 0}, New Byte() {0, 0, 0, 0})
Socket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, New AsyncCallback(AddressOf ReceptionCallback), Nothing)
End Sub
続いてコールバック関数の内容。パケットの中身がPrivate変数のBufferに入っているので、それを解析する自作メソッドParseを呼びます。最後にまたBeginReceiveメソッドを呼んで何度も受信するようにします。
Private Sub ReceptionCallback(ByVal ar As IAsyncResult)
Dim size = Socket.EndReceive(ar)
Parse(Buffer, size)
Socket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, New AsyncCallback(AddressOf ReceptionCallback), Nothing)
End Sub
Live Messengerでメッセージを受信した場合のデータ形式は次のようになります。vbCrLfは\r\nで表現してます。
MSG jz5@xxx MATSUE%20Yusuke 139\r\n
MIME-Version: 1.0\r\n
Content-Type: text/plain; charset=UTF-8\r\n
X-MMS-IM-Format: FN=MS%20UI%20Gothic; EF=; CO=0; CS=80; PF=0\r\n
\r\n
こんにちは
1行目は、MSG、メールアドレス、表示名(URLエンコード形式)、データ長、\n\rとなります。データ長は、その直後の\n\rの次から始まるデータの長さを表しているみたい。表示名はURLエンコードされてますが、UTF-8でもエンコードされてると思う。スペースなどが%20になってるのかな? MSGで始まるデータは、メッセージを入力中を示すメッセージにも使用されています。その場合はContent-Typeがtext/x-msmsgscontrolになります。他にも使用されているかも。
以上を踏まえて、Parseメソッドの内容。BufferはIPヘッダ+データの構成になってます。最低限の部分だけ調べてます。Live Messengerのメッセージのやりとりには通常1863番のポートが使用されているのでそのポートが指定されているか確認します。正しくはデータ内のcharset部分を確認する必要があるでしょうが、一律 UTF-8でエンコードしてます。メッセージ部分は\r\n\r\nの次から最後までとして取得しています。
Private Sub Parse(ByVal buffer() As Byte, ByVal size As Integer)
Const tcpHeaderLength As Integer = 20
Dim ipHeaderLength As Integer = (buffer(0) And &HF) * 4
Dim totalLength As Integer = buffer(2) * 256 + buffer(3)
Dim protocol = buffer(9)
' プロトコル&データ長チェック
Const Tcp As Integer = 6
If protocol <> Tcp OrElse _
totalLength <= ipHeaderLength + tcpHeaderLength Then
Exit Sub
End If
' ポート番号チェック
Const MessengerPort As Integer = 1863
Dim sourcePort = buffer(20) * 256 + buffer(21)
If sourcePort <> MessengerPort Then
Exit Sub
End If
' UTF-8デコード MSGから始まるかチェック
Dim message = System.Text.Encoding.UTF8.GetString(buffer, ipHeaderLength + tcpHeaderLength, totalLength - ipHeaderLength - tcpHeaderLength)
If Not message.StartsWith("MSG ") Then
Exit Sub
End If
' 表示名部分取得
Dim match As RegularExpressions.Match
Dim nick As String = Nothing
match = Regex.Match(message, "^MSG\s(.+?)\s(?<nick>.+?)\s(\d+)\s*$", RegexOptions.Multiline)
If match.Success Then
nick = Uri.UnescapeDataString(match.Groups("nick").Value)
Else
Exit Sub
End If
' Content-Type部分取得、text/plainの場合メッセージ表示
match = Regex.Match(message, "^Content-Type:\s*(?<type>.+?);", RegexOptions.Multiline)
If match.Success AndAlso match.Groups("type").Value = "text/plain" Then
Dim p = message.IndexOf(vbCrLf & vbCrLf)
If p > 0 Then
Console.WriteLine("{0}: {1}", nick, message.Substring(p + 4))
End If
End If
End Sub
最後のパケットを取得するIPアドレスを選択する部分を書きます。コンソールプログラムです。IPアドレスが羅列して、番号を入力するようにしました。
Sub Main()
Dim addressList = Dns.GetHostEntry(Dns.GetHostName).AddressList
For i = 0 To addressList.Count - 1
Console.WriteLine("{0}: {1}", i, addressList(i))
Next
Console.Write("Input no > ")
Dim input = Console.ReadLine
IpAddress = addressList(CInt(input))
BeginReceive()
Console.ReadLine()
End Sub
以上で完成です(?)。動かすと次のような感じ。パケット盗み取っているので、管理者権限で実行する必要があります。シェイクやエモーションはキャプチャしません。
動くんですが、終了処理がないです。きちんとする場合は、コールバックメソッド内で終了条件時には、次の受信をしないようにして終わる必要があります。