艦これのバトルJSONをNetwork Monitorで解析(端歩編)

艦これのバトルJSONを再解析(祥鳳改小破編) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/5517

で解析をした後で、hp を減算すれば ok というのは分かったので、さて他の艦これブラウザを見てみると…なんか、もうこれでいいやってな感じになってしまいました。もともと諜報員はデスクトップアクセサリ的に作りたかったわけで、戦略ゲーム的な内容を調査してというのはちょっと苦手で。ただし、デスクトップアクセサリといえば、

<b>MMDDM</b>MMDモデル用デスクトップマスコットツール?-?[?BowlRoll?]
http://bowlroll.net/up/dl30246

が動いているわけで、これでもいいか(偉そう)ってな感じになってます。そうなると、

  • 艦これブラウザで、GUI をまかなう
  • MMDDM で 榛名/ハルナ のマスコットが動く

という2点を満たせればいいわけで(ワタクシ的には)、となるとここの繋ぎの部分を作ればいいわけですね。業務的にもよくあるパターンです。

艦これブラウザは、

回転母港ページ
http://spinpreach.webcrow.jp/SpinHomePort.html
艦これ 司令部室
http://nozomi.arege.jp/KanHQ/
提督業も忙しい! | grabacr.net
http://grabacr.net/kancolleviewer

を使います。後、ブラウザ上(IE,Firefoxなど)で艦これを動かしている場合も含めます。先の艦これブラウザが fiddler を使っているので、fiddler から fiddler をフックすればいいのですが、ちょっとこれがうまくいかず。更に通常のブラウザの場合は fiddler だけではうまくいかないので(タブブラウザなのでピンポイントで proxy を変えることができるのか不明)、network monitor に立ち返って調べています。

Download Microsoft Network Monitor 3.4 from Official Microsoft Download Center
http://www.microsoft.com/en-us/download/details.aspx?id=4865

ネットワークのフックツールが wincap → network monitor → fiddler となっているので、ひとつ手前の技術なのですが、fiddler と違ってネットワーク全体をフックします(ネットワークドライバー系なのか、ちょっと不明)。なので、PC全体のネットワークを監視できますね。当然、proxy 形式の fiddler もフックできます。
逆に、fiddler にできて、network monitor にできないのが、

  • 通信データを改変する。
  • fiddler は HTTP プロトコルに特化していて、C#/VB.NETから扱いやすい。
  • network monitor をインストールする必要がある(多分)

なところです。下りデータの swf や mp3 を改変して「榛名をハルナにする」ことは network monitor ではできませんが、まあ、夜戦前の状態取得とか、Azure mobile service と連携して push 通信とかならできそうです(当然、fiddler でも可能ですが)。あと、network monitor の場合は、データをモニタ型式(たぶん内部的なコピー)で取ってくるので、PCに対するネットワーク負荷が(たぶん)低いと思われます。まあ、これも艦これに対してだけだし、艦これサーバー自体には fiddler でも network monitor でも負荷が低いのは同じなので(むしろブラウザのほうが負荷が高い)そのあたりは問題ありません。

■network monitor で艦これ通信だけ抽出する

Display Filter で以下を設定すれば OK です。SourceAddress は、艦これサーバーなので鎮守府が違うと別IPになります。URIで絞ったほうがいいも

.ProtocolName == "HTTP" and
.HTTP.Response and
.Ipv4.SourceAddress == XXX.XXX.XXX.167

■ヘルプを見る

実は network monitor には api があります。ヘルプもあります。API は wincap だけかと思い込んでいたので、これは結構使えますね。

C:Program FilesMicrosoft Network Monitor 3HelpNetMon.chm

なところに HTML ヘルプがあります。C++とC#の api も API フォルダに NmApi.h や NetmonAPI.cs というファイルであります。ヘルプには api を使ったサンプルが載っているので、これを手がかりにして作っています。

■api を使ってフックする

まだまだ途中ですが、晒しておきます。google で検索してもリアルタイムにフックする方法がなかなか出てこないので。
ハンドルのクローズとか例外処理とかは省いています。

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // netmon api の初期化
    IntPtr phCaptureEngine = IntPtr.Zero;
    NetmonAPI.NmOpenCaptureEngine(out phCaptureEngine);
    // フィルタを作成
    MyLoadNPL();

    // ネットワークアダプタのチェック(複数差し込み、VMWare Netなど)
    uint ucnt = 0;
    NetmonAPI.NmGetAdapterCount(phCaptureEngine, out ucnt);
    NM_NIC_ADAPTER_INFO adapterInfo = new NM_NIC_ADAPTER_INFO();
    adapterInfo.Size = (ushort)System.Runtime.InteropServices.Marshal.SizeOf(typeof(NM_NIC_ADAPTER_INFO));
    // 4番目のネットワークをフック
    uint ulIndex = 3;
    uint errno = NetmonAPI.NmGetAdapter(phCaptureEngine, ulIndex, ref adapterInfo);
    // モニタの準備
    uint ret = 0;
    IntPtr myTempCap = IntPtr.Zero;
    ret = NetmonAPI.NmConfigAdapter(phCaptureEngine, ulIndex,
        new CaptureCallbackDelegate(FrameIndicationCallback), 
        myTempCap, 
        NmCaptureCallbackExitMode.ReturnRemainFrames);
    // モニタの開始
    ret = NetmonAPI.NmStartCapture(phCaptureEngine, ulIndex, NmCaptureMode.Promiscuous);


}
/// <summary>
/// コールバック
/// </summary>
/// <param name="hCapEng"></param>
/// <param name="ulAdatIdx"></param>
/// <param name="pContext"></param>
/// <param name="hRawFrame"></param>
public void FrameIndicationCallback(IntPtr hCapEng, UInt32 ulAdatIdx, IntPtr pContext, IntPtr hRawFrame)
{

    IntPtr parsedFrame, insertedRawFrame;
    // フレームのパースチェック
    if( NetmonAPI.NmParseFrame(
        _frameParserHandle,
        hRawFrame,
        0,
        NmFrameParsingOption.None ,
        out parsedFrame,
        out insertedRawFrame)  != 0 )
    {
        // NetmonAPI.NmCloseHandle(hRawFrame);
        return;
    }

    // フィルタ
    bool passed = false;
    NetmonAPI.NmEvaluateFilter(parsedFrame, _httpFilterId, out passed);
    if (passed == false)
        return;

    uint ret;
    var name = new char[256];
    // ポインタを直接扱うので unsafe を使う
    unsafe
    {
        fixed (char* value = &name[0])
        {
            ret = NetmonAPI.NmGetFieldValueString(parsedFrame, _uriFeildId, 256, value);
        }
    }
    var bytes = new byte[4000];
    unsafe
    {
        fixed (byte* buf = &bytes[0])
        {
            uint offset, size;
            NetmonAPI.?NmGetFieldOffsetAndSize(parsedFrame, _uriFeildId, out offset, out size);
            Debug.WriteLine("offset:{0} size:{1} ", offset, size);
            // NetmonAPI.NmGetRawFrame(hRawFrame, 4000, buf, out size);
            // Debug.WriteLine("size2 {0}", size);
        }
    }
}

IntPtr _engineHandle, _frameParserHandle, _nplParserHandle, _configParserHandle;
uint _httpFilterId, _uriFeildId;

private void MyParserBuild(IntPtr pCallerContext, uint ulStatusCode, string lpDescription, NmCallbackMsgType ulType)
{
    /*
    // throw new NotImplementedException();
    if (pCallerContext == IntPtr.Zero)
        return;

    GCHandle gch = GCHandle.FromIntPtr(pCallerContext);
    NM_NIC_ADAPTER_INFO adapterInfo = (NM_NIC_ADAPTER_INFO)gch.Target;
        * */
}
/// <summary>
/// フィルタの作成
/// </summary>
void MyLoadNPL()
{
    IntPtr myFrameParser = IntPtr.Zero;
    ulong ret;
    IntPtr myNplParser = IntPtr.Zero;
    IntPtr myFrameParserConfig = IntPtr.Zero;


    // Use NULL to load the default NPL parser set.
    ret = NetmonAPI.NmLoadNplParser(null, NmNplParserLoadingOption.NmAppendRegisteredNplSets, MyParserBuild, IntPtr.Zero, out myNplParser);
    ret = NetmonAPI.NmCreateFrameParserConfiguration(myNplParser, MyParserBuild, IntPtr.Zero, out myFrameParserConfig);
    ret = NetmonAPI.NmConfigConversation(myFrameParserConfig, NmConversationConfigOption.None, true);
    ret = NetmonAPI.NmConfigReassembly(myFrameParserConfig, NmReassemblyConfigOption.None, true);

    uint myHTTPFilterID = 0;
    uint myHTTPFieldID = 0;
    // ret = NetmonAPI.NmAddFilter(myFrameParserConfig, "http.request.command == "GET"", out myHTTPFilterID);
    // ret = NetmonAPI.NmAddField( myFrameParserConfig, "http.request.uri", out myHTTPFieldID);
    // ret = NetmonAPI.NmAddFilter(myFrameParserConfig, "Protocol.HTTP", out _httpFilterId);
    // ret = NetmonAPI.NmAddFilter(myFrameParserConfig, "tcp.Port == 80", out _httpFilterId);
    ret = NetmonAPI.NmAddFilter(myFrameParserConfig, "http.request.command == "GET"", out _httpFilterId);
    ret = NetmonAPI.NmAddField(myFrameParserConfig, "http.request.uri", out _uriFeildId);
    ret = NetmonAPI.NmCreateFrameParser(myFrameParserConfig, out myFrameParser, NmFrameParserOptimizeOption.ParserOptimizeNone);
    _frameParserHandle = myFrameParser;
}

初期化は、
1. NmOpenCaptureEngine で api 初期化
2. NmGetAdapterCount と NmGetAdapter で、フック対象のネットワークアダプタを取得
3. NmConfigAdapter でコールバック関数の登録
4. NmStartCapture でモニタ開始

フィルタ作成は、
1. NmLoadNplParser と NmCreateFrameParserConfiguration で初期化
2. NmAddFilter でフィルタ作成
3. NmAddField でフィールド作成(値を取得するところ)
4. NmCreateFrameParser でパーサの作成
_httpFilterId や _uriFeildId は、グローバルに保存しておいて使いまわしをします。たぶん、コールバックの時に作ってもよいのですが、あらかじめ作ったほうがハンドルとしてベターかと。

コールバック関数には、全ての通信が入って来るのでフィルタで選別します(ここに注意)。
1. 最初に NmParseFrame でパースします。
2. NmEvaluateFilter で、先に作成したフィルタを適用
3. NmGetFieldValueString や NmGetFieldOffsetAndSize などを使って各種フィールドの値を取得

network monitor を調べていくと NPL にぶち当たるのですが、所謂構造体定義です。

Network Monitor Open Source Parsers – Home
http://nmparsers.codeplex.com/

HTTPプロトコルとかICMPプロトコルとか、各種プロトコルを疑似的な構造対で定義します(中身はコードです)。これは、既に network monitor にデフォルトで含まれているので NmLoadNplParser の NmNplParserLoadingOption.NmAppendRegisteredNplSets で読み込ませれば OK です。独自プロトコルを作るときには NPL を作ればいいのでしょう。ここでは HTTP プロトコルしか使わないのでこれで十分です。

netmon api の C# は薄いラッパーなので、NmGetFieldValueString を使った受け渡しはポインタになります。この手のデータを扱うためには unsafe と fixed が必要なのですが…このあたりは C++/CLI で作り直したほうがよさそう。あるいは、string に変換する関数を作るとか。
または HTTP プロトコルしか使わないので、fidder 互換にしてしまうのがよさそうですね。まあ、network monitor 自体が 2010/06 で更新が終わっているので、今更感がありますが。ネットワーク解析的には便利かな。ちなみに、System requirements には Winodws 8 までしか書いてありませんが、Windows 8.1 x64 で動作してます。

カテゴリー: 艦これ パーマリンク