2008年4月30日

 コード書いてみました。流れるパケットを見て、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

 以上で完成です(?)。動かすと次のような感じ。パケット盗み取っているので、管理者権限で実行する必要があります。シェイクやエモーションはキャプチャしません。

実行結果

 動くんですが、終了処理がないです。きちんとする場合は、コールバックメソッド内で終了条件時には、次の受信をしないようにして終わる必要があります。

posted @ 21:25 | Feedback (8)

 ※言いたいことは「MISAOは発表者のために」、「非表示コメント」です。長くなった。

 いっぱいMISAOの話題がー。知らない人にはわけわからない内容だと思いますが、ニコニコメソッド用のツールです。今のところ私がひとりで開発してます。公開もまだきちんとしていなくて、主にわんくま関係者の一部に流れてる感じ。使う使わないについては、当然ながら使ってくれると嬉しい。そこは置いといて、思ってることをつらつらと書いてみます。あと、ツール改良については私に直で言ってくれる方が伝わるよ。

目下のこと

 せっかく話題にあがってるのにダウンロードできないなんて! ってことで公開できるようにしたいなーと。今 私の手元にはコンパイルが通らない最新のMISAOがあります。少し先になります。

MISAOは発表者のために

 USTREAM.TVのチャットに参加したことから作ったものですが、MISAOは発表者用のツールと考えて作ってます。今は表示する(使用する)・表示しない(使用しない)の2操作ぐらいしかありませんが、プレゼンにチャットの内容までも活かす(少し悪く言えば?利用する)感じのためと思ってます。実際にそう使うか・できるかとは別に。

非表示コメント

 発表者のためのものなんで、主導権は発表者ということで、チャット参加者が表示内容の操作できることは少し不本意なわけです。今のMISAOにはチャット参加者主導のメッセージを非表示にする機能が実質的にはあります。一応、NGワード用ってことにはなってますが、その用途で使うには低機能ですね……。私のMISAO思想から外れるので消えてゆく機能です。あと非表示機能で会話されてるってのも発表者には不本意かなと思います。

コマンド

 そんなわけで、チャット参加者用のコマンドもあまり今後実装するつもりが……。USTREAM.TVのチャットにMISAO用のコマンドだらけになることは私として本意でない。ただし、USTREAM.TVには文字色・太字・下線・斜体の文字装飾ができたので、メッセージ表示の際、色はそのまま反映。太字はフォント大きく、下線は下に表示としてます。まあまあ直観的だし、コマンド汚染にもならないので。斜体はニコニコ風のメッセージ表示と繋がらなかったので未実装。

サブプロジェクタ表示・ログ表示

 とある事情からチャットのログをメインスライド以外で表示する機能もできました。でもこれニコニコ的じゃないしなー。これも消えゆく機能です。見なかったメッセージはそれでいいんだけど、発表者しては全部知りたいってのはわかる! 発表者のための機能としては残す。

荒らし(?)対策

 非表示コメントからもわかるように(?)、基本的にはチャット内容なんでも流すってのがスタンス。必要ないものはスルーで。IRCチャットなんでIPアドレス制限もできますが、発表者がプレゼン中にするの? と思うし、単に表示・非表示を今後できるようにするつもり。まあ、必要ないものをスルーが難しいってのはあると思うけど。ニコニコ動画のように動画+動くコメントではないからね。メッセージだけ動いてるし。

会場からの参加

 会場にいない人のためのUSTREAM.TVによる配信。と、その人らチャットから始まったのだけど、こないだの名古屋勉強会はどれぐらい会場から参加していたのかな? 懇親会でもNintendo DSから繋げない? って話しがありました。なるほどー。その場で参加も楽しいよね。

 昨年のLL魂でニコニコメソッド風ではないけどチャット内容をプレゼン中に表示するということをしていた方を思い出しました。LL魂だと数百人以上の規模なので、叫んでも声が届かず質問できません。そういったことも考えるとUSTREAM.TV以外からのメッセージ送信方法があるといいかなと思ってます(もう考えました)。

チャット参加者として

 発表者主導、発表者主導といったけど、チャット参加者のことも考えてます。私は開発者であり、現地参加者であり、チャット参加者なわけだし(あと残るは発表者?)。ご意見待ってますー。まあ、MISAOに限らずニコニコメソッドってのはチャット参加者・会場の参加者がより楽しめる可能性があるプレゼン方法じゃないでしょうか。とりあえず、なにより MISAOのコメントに反応してくれると嬉しい! 

posted @ 21:24 | Feedback (18)