アジョブジ星通信

進捗が出た頃に更新されるブログ。

主人公の好感度問題 完結編

遡ること 2017 年 4 月、「主人公の好感度問題」と題された、あの一大プロジェクトが、今日完結する……。

「主人公の好感度問題」とは

生まれて初めて買ったエロゲ―こと、ワガママハイスペック(以下ワガハイ)をプレイしていたときのこと。このゲームは、共通ルートでの選択肢の選び方によって、各キャラの好感度パラメータが変化していき、最終的に好感度の高いキャラと恋人になって、あとは一本道のストーリーを見るだけの、ほぼほぼ完全に紙芝居なのですが、その選択肢の選び方について、多くのパターンが未尋ルートに向かうように感じられました。そこで、選択肢の選び方全パターンを実際に試してみて、本当に未尋ばかりなのか、つまり未尋はちょろいのかについて検証してみようというプロジェクトが始動しました。

f:id:azyobuzin:20181028175910p:plain

TL;DR

選択肢の積み重ねでエンディングが分岐するタイプのゲームにおいて各エンディングの出現率を調べるために、Docker上にWineで動かしたゲームを C# からX Window Systemプロトコルで操作して、選択肢を全探索させた話。

https://mstdn.maud.io/@unarist/100976963847747421

続きを読む

カーネルモードドライバーをそこそこデバッグできるようにした話

はじめての仮想HID」では、デバッグ手段が手に入れられなかったという終わり方をしてしまいましたが、それなりにデバッグできるようになったので、その方法をまとめておきます。

1. KdPrintEx でログを出力する

KdPrintEx マクロを使うと、デバッグビルド時(DBG 定数があるとき)に雑に Windows にログを残すことができます。 Microsoft から出ているサンプルでもよく使われているので、これで出力したログを見れる環境を作っていきます。

これを読んで理解できれば終わりですが……。

KdPrintEx の使い方

KdPrintEx はこのように使います。

KdPrintEx((ComponentId, Level, Format, フォーマットのための可変長引数));

マクロの都合上、括弧が二重になっています。

ComponentId には、どこで発生したログかを表す値を入れますが、サードパーティーのドライバーの場合は「DPFLTR_IHVDRIVER_ID」を指定しておくのが無難そうです。

Level はいろいろな指定の仕方がありますが、基本的には次の中から選べば良いと思います。

  • DPFLTR_ERROR_LEVEL
  • DPFLTR_WARNING_LEVEL
  • DPFLTR_TRACE_LEVEL
  • DPFLTR_INFO_LEVEL

Format および、その後に続く可変長引数には、 printf 形式でログの内容を指定することができます。最後に改行を入れておかないと、出力されるログも改行されないので、前後の出力がまとまってしまうことに注意してください。

以上を踏まえて、私はこのようなヘッダーファイルを作っておき、利用することにしました。カーネルモードでのログは「カーネルからのログ」という情報しかないので、ログの文字列の最初にプロジェクト名を入れておくことで、あとでフィルターをかけることができるようにしておきます。

#define TraceEnterFunc() KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "ProjectName: Enter %s, IRQL: %d\n", __FUNCTION__, KeGetCurrentIrql()))
#define TraceErrorStatus(funcName, status) KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ProjectName: %s returned 0x%x in %s\n", funcName, status, __FUNCTION__))

#if DBG

#define TraceInfo(msg, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "ProjectName: " msg "\n", __VA_ARGS__)
#define TraceError(msg, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "ProjectName: " msg "\n", __VA_ARGS__)

#else

#define TraceInfo(msg, ...)
#define TraceError(msg, ...)

#endif

DebugView でログを見る

ログを見るのには、 Sysinternals の DebugView を使用すると便利です。

オプション次第(/v)ですべてのログを見ることができますが、それをやるとログの収集が追い付かず、ものすごく遅延が発生していくので、先に表示するログの設定をしておきます。

  1. レジストリHKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter キーを(なければ)作成します。
  2. ログの ComponentId に対応する DWORD 値を作成します。例えば DPFLTR_IHVDRIVER_ID なら「IHVDRIVER」という名前にします。
  3. その値を、表示させたいレベルのビットマスクにします。例えば、 INFO だけを表示(実際には値が 0 である ERROR も表示される)したいならば、 1 << DPFLTR_INFO_LEVEL(3) = 0x8 を値とします。 ERROR から INFO まですべてを表示させる場合は、すべてのビットを立てるので 0xF にします。(この範囲外の Level を指定した場合はまた話が変わってくるけれど割愛)

f:id:azyobuzin:20180809013052p:plain

準備ができたら、管理者権限で DebugView を起動し、 Capture Kernel を有効にするとログが見れるようになります。 Visual Studio から配置する場合は、 WDKRemoteUser 上で起動しておけば良いと思います。

f:id:azyobuzin:20180809014549p:plain

バグなのかと思いますが、一度 Capture Kernel を有効にして、 DebugView を終了させた後、再度 DebugView を起動すると、アクセス拒否エラーが出ます。再起動しないと直らなくてつらい。。

DebugView はリモートでも使用することができます。デバッグターゲットのマシンでは、管理者権限で「Dbgview /a /k」を起動します。 /a がエージェントモード、 /k がカーネルログの有効化です。ログを見る側のマシンでは、普通に DebugView を起動し、 Computer → Connect を選択して、 IP アドレスを入れると接続できます。こちら側でも Capture Kernel を有効にする操作が必要です。

2. KDNET でリモートカーネルデバッグ環境を構築する

以前の記事(はじめての仮想HID)では、 Visual Studio からプロビジョニングを行いましたが、そのときプロビジョニングは失敗していたはずです。しかし、次へをクリックすると「Status: Configured for driver testing」と表示されていたので成功したものだと思っていましたが、実際にはドライバーの配置のみが行える状態で、デバッグが出来ない状況でした。そこで、さらに調査を続けたところ、 Windows 10 では Visual Studio からのカーネルデバッグができなくなったようです。

This feature is not available in Windows 10, version 1507 and later versions of the WDK.

Setting Up Kernel-Mode Debugging over a Network Cable in Visual Studio | Microsoft Docs

そこで、とりあえず WinDbg 経由でリモートアクセスできる程度には環境を整えてみようということでやってみました。これを行うと Visual Studio からデバッグ実行を行ったとき、 Visual Studio 上から WinDbg を操作することができるようになりますが、 Visual Studio のエディタ上で設定したブレークポイントが反映されるようなことはありません。残念。

参考情報

これを読めば、この記事を読む必要がないリストです。

NIC の確認

リモートカーネルデバッグを行うには、対応している NIC を使用しなければいけません。つまり無線では無理ということっぽいです。

対応している NIC リストは次のページにあります。

今、テスト環境として使用しているマシンの、有線の NIC のプロパティはこのようになっています。

f:id:azyobuzin:20180809021138p:plain

赤丸がベンダーID、青丸がデバイスIDです。ベンダーID 1969、デバイスID 1091 はリストにあるので使えそうです。よかった。

次に、 NIC の場所をメモしておきます。

f:id:azyobuzin:20180809021411p:plain

この場合は、左から順に「3.0.0」と記録しておきます。これが後で使う Bus Parameter になります。

Visual Studio でプロビジョニング

せっかくなので、作業の半分くらいを Visual Studio にやってもらいます。どうせ Visual Studio から配置するわけですし。

Visual Studio で Configure Devices を開き、 Add New Device すると、勝手にポートやキーが設定されていると思います。ここで、 Bus Parameter の欄に先程調べた値を入力してプロビジョニングを行うと、この後の設定が完了した後から、 Visual Studio 上で WinDbg の操作を行うことができるようになります(その必要がないなら KDNET だけ設定しても良いです)。

f:id:azyobuzin:20180809021817p:plain

プロビジョニングには失敗しますが、デバイス一覧で「Status: Configured for driver testing」となっていれば完了です。

有線での設定を行いましたが、追加で、 WinDbg にアクセスできなくていいから、デプロイだけ無線でやりたいという場合は、さらに Add New Device して、プロビジョニングを行わず、無線 NIC 側の IP アドレスだけ指定して保存すれば、無線でデプロイできるようになります。この場合でも DebugView でログを見ながらデバッグできます(私はこの構成でやっています)。

KDNET でリモートカーネルデバッグの設定を行う

  1. C:\Program Files (x86)\Windows Kits\10\Debuggers\x64 から kdnet.exe と VerifiedNICList.xml を取ってきて、ターゲットマシンの C:\KDNET にコピーします。
  2. C:\KDNET 上で管理者としてターミナルを開き、 .\kdnet <ホストマシンのIPアドレス> <プロビジョニングで設定したポート番号> を実行します。
  3. 再起動すれば完了です。

もし、プロビジョニングを行っていない場合は、ポート番号は適当な値を使用し、実行結果として表示されるキーをメモしておいてください(bcdedit /dbgsettings でも確認できます)。

これで設定は完了です。あとは WinDbg でポートとキーを指定すれば、デバッグが開始されます。

f:id:azyobuzin:20180809024545p:plain

ブレークすると本当に Windows が丸ごとフリーズするので面白いです(他に活用できてない……)。ドライバー関連だと、 !devnode 0 1 とやると、デバイスマネージャーみたいな表示が得られます。

まとめ

ひとまず、ログを眺めながらデバッグすることができるようになって一安心です。しかし、 KMDF の内部ログとかを含めて見たい場合は、また別なことをしないといけないっぽく面倒だなぁといったお気持ちです。

MSBuildのバッチ機能とは

みなさん MSBuild プロジェクトの手書き、していますか?プロジェクトの手書きはさまざま苦労はあると思いますが、そんなご時世に、知っているとちょっと楽に書けて、しかもインクリメンタルビルドがしやすくなるバッチ機能について、詳しく見ていきましょう。

バッチの概要と使い方については、公式ドキュメントに書いてありますが、腑に落ちないというか、あまり理解できた感じがしなかったので、 MSBuildソースコードとステップ実行してみた結果を基に、具体的な動作から挙動を説明していきます。

  • 用語について
  • 基本的なバッチの動作
  • タスク
    • 簡単な例
    • 同じ値を持つメタデータ
    • 複数の項目の種類を使う
    • 項目名を指定しないでメタデータを参照する
    • バッチが適用されるパラメータ
  • ItemGroup
  • PropertyGroup
  • ターゲット
    • ターゲットのバッチ実行の基本
    • 悪用
    • プロパティや項目を参照するときの注意
  • バッチ実行計画を作成するプログラム
  • 終わりに
続きを読む

はじめての仮想HID

半年ぶりです。崇高な計画を遂行するために、仮想 HID が欲しくなったので、ドライバーから作ってみようと思い、いじってみた記録を書いておきます。

HID ドライバーのサンプル

Microsoft 公式のドキュメントで HID ドライバーのサンプルが公開されています。

これをパクれば簡単ですね!

……ミニマルサンプルにしてはでかすぎて理解できない。

というわけで、これをすべて理解する前に、もっと簡単そうな方法を見つけたので、それを試してみました。

Virtual HID Framework

Windows 10 から Virtual HID Framework(略して VHF)が登場しました。今までの HID ドライバーでは、 Windows が用意してくれるのは「これは HID ですよ」と宣言してくれる機能くらいで、多数の I/O リクエストがパススルーされてくるので、それに対応する処理をすべて書く必要がありました。対して VHF を使用したドライバーは、それ自身が HID として振舞うのではなく、 HID の作成、削除、 I/O 処理を管理するドライバーとして振舞います。このことから、ドキュメントでは「HID source driver」と呼ばれています。

VHF を使った HID ソースドライバーの作り方の公式ドキュメントはここにあります。

これを読めば満足という方は、このブログを閉じても大丈夫です。

ドキュメントの「Virtual HID device tree」の図を見てもらうと、 HID ソースの働きがわかると思いますが、 HID ソースは、自身が 0 個以上の子 HID を持つデバイスとして振舞います。 Windows のドライバー業界でバスと呼ばれているやつです。子 HID には Windows に組み込まれている VHF 用の仮想 HID ドライバーが使用されます。

バイスマネージャーの「接続別」で表示するとこんな感じになっています。

f:id:azyobuzin:20180725025411p:plain

HID ソース → 仮想 HID と接続され、 HID の種類からキーボードであることを認識してキーボードのドライバが読み込まれています。

続きを読む

MPMアルゴリズムのNSDFの求めかた

結論だけ知りたい人向け

NSDF の式

\displaystyle n'_t(\tau) = \frac{2r'_t(\tau)}{m'_t(\tau)} \quad (0 \leq \tau < W)

m'_t(\tau) について、前から求めると

\displaystyle
m'_t(\tau) = \begin{cases}
    2r'_t(0) & (\tau=0) \\
    m'_t(\tau-1) - x_{t+\tau-1}^2 - x_{t+W-\tau}^2 & (0 < \tau < W)
\end{cases}

後ろから求めると

\displaystyle
m'_t(\tau) = \begin{cases}
    0 & (\tau=W) \\
    m'_t(\tau+1) + x_{t+\tau}^2 + x_{t+W-\tau-1}^2 & (0 \leq \tau < W)
\end{cases}

となるので、好きな方でやってください。

はじめに

ピッチ検出アルゴリズムについて検索すると、まぁ大体このページ(音声の波形からピッチを検出するアルゴリズム - まめめも)に行き着くと思うのですが、 NSDF のインクリメンタルな求め方について特に説明もなく、クワイン用の well compressed なプログラムしか書いていないので、基の論文であるところの『A Smarter Way to Find Pitch』に書いてある式を変形しながら、本当にインクリメンタルに求まるのか検証していきます。

ここでは Normalized Square Difference Function の略として NSDF と言っていますが、 McLeod 氏の 2008 年の論文では specially-normalized autocorrelation function で略して SNAC と呼んでいますね。まぁいいや。

続きを読む

さぁ fixed を捨てて Unsafe だ

f:id:azyobuzin:20170929224302p:plain

早すぎる最適化が好きな人のための C# 7 の有効活用ガイドです。

ある構造体をそのまま byte 配列に突っ込みたくなるとき、ありますよね?構造体ならメンバーに名前がついていて書きやすい、でも相手が byte 配列だから 1 バイトずつ手書きするしかないのか……?そんなときにおすすめの技を紹介します。

達成目標

例を用意しましょう。 X Window Systemプロトコルは C 言語などでクライアントを実装しやすいように、適度にアライメントされたデータをやりとりします。しかもエンディアンもクライアント側が指定することができるので、クライアントはまさに構造体を直接送受信することができます。そこで、クライアントが X サーバに接続して、最初に送信するメッセージを構造体を使って中身を用意し、 byte 配列に書き込むことを目標にしていきましょう。

(この目標設定は、ちょうど X クライアントを C# で書いていたからなのですが、完成したころに公開します。公開しないかもしれません。)
公開してあります → X11Client.cs

続きを読む

それでも僕はWineでMonoを動かしたかった

Wine 上で動く Windows アプリを制御するにはどうしよう?と考えた結果、制御する側も Wine 上で動かせば Windows 上と同じように操作できるじゃないか!と思いやっていった記録。残念ながら、結局 Docker 上で動かすことはできなかったので、僕の意志を引き継いでくれる方、もしくは先行研究がありましたら教えてください。

というわけで、 Docker で動かすのを断念するまでに得た知見をまとめておきたいと思います。

環境

  • Ubuntu 17.04
  • Wine 2.14 win32 prefix
  • Mono 5.2.0 x86

wine-mono?それともスタンドアロンの Mono?

wine-mono は Mono のコアライブラリ + .NET Framework になりすますための mscoree.dll という構成になっています。つまり .NET の exe ファイルを起動しようとすると Mono を使ってくれる、という構成になります。

それに対して、 mono-project.com からダウンロードできる Mono の Windows 版は mono.exe を使ってアセンブリを実行する形をとります。つまり Linux で使っている Mono とまったく同じです。

これらふたつは共存することができるので、間接的に .NET Framework を必要とするアプリには wine-mono でうまくやっておいてもらいつつ、 .NET アセンブリだと最初からわかっているものは mono.exe で起動するという使い方ができるでしょう。

さて、最初は wine-mono で全部なんとかしようと思っていたのですが、厄介な問題にぶち当たりました。 wine-mono はアセンブリの厳密名を検証するようですが、回避するために App.config に <bindingRedirect> を書いても読み込んでくれないようです。というわけで、基本的には mono.exe を使うべきだということがわかりました。

続きを読む