2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

重いマイクラを起動せずに「今誰がいるか」知りたいので、タスクトレイ常駐アプリを作った

2
Posted at

はじめに

Minecraft のサーバー運営者やプレイヤーにとって、「今誰がいるか」はログインのモチベーションに直結します。
しかし、確認のためだけに重量級のゲームクライアントを起動するのは非常に手間です。

そこで、「PC を使っている間、邪魔にならず、必要な情報が瞬時にわかる」というコンセプトの元、タスクトレイ(システムトレイ)で常時監視するための Windows デスクトップアプリ「MineTray」 を開発しました。

MineTray Header

今回は、このアプリの開発経緯や技術的なポイントについて紹介します。

▼ ダウンロードはこちら (v1.0.0)
https://github.com/kihi78/MineTray/releases/tag/v1.0.0

作ったもの

MineTray は、タスクトレイに常駐し、指定した Minecraft サーバーの稼働状況と参加者を監視するアプリです。

主な機能

  • 表示:ログイン中のプレイヤーのスキン(顔)がタスクトレイアイコンとして表示
  • 通知:プレイヤーの参加・退出を検知してデスクトップ通知を表示
  • リアルタイム監視:サーバーリスト Ping (SLP) プロトコルを用いて、人数、メンバー、MOTD などを取得
  • 自動起動:PC 起動時に自動で起動

技術スタック

  • Framework:.NET 8.0 (Windows Forms)
  • Language:C# 12
  • Libraries:
    • System.Net.Sockets:SLP 通信用
    • System.Text.Json:JSON パース
    • DnsClient:SRV レコード解決

技術的なこだわりポイント

1. 独自実装の SLP (Server List Ping)

既存のライブラリを使えば簡単ですが、学習と軽量化のために、Minecraft 独自のプロトコル(SLP)を実装しました。
VarIntという可変長整数の読み書きや、ハンドシェイクパケットの構築など、バイナリレベルでの通信制御を行っています。

実際のコードの一部がこちらです(バッチパケットの構築部分):

// --- バッチパケットの準備 (ハンドシェイク + リクエスト) ---
var batchBuffer = new List<byte>();

// 1. ハンドシェイクパケット
var handshakeInner = new List<byte>();
WriteVarInt(handshakeInner, 0x00);      // パケットID
WriteVarInt(handshakeInner, 763);       // プロトコルバージョン
WriteString(handshakeInner, originalHost); // 元のホスト名を送信
WriteUShort(handshakeInner, (ushort)connectionPort);
WriteVarInt(handshakeInner, 0x01);      // 次のステート (Status)

WriteVarInt(batchBuffer, handshakeInner.Count);
batchBuffer.AddRange(handshakeInner);

// 2. ステータスリクエストパケット
var requestInner = new List<byte>();
WriteVarInt(requestInner, 0x00);        // パケットID (Status Request)

WriteVarInt(batchBuffer, requestInner.Count);
batchBuffer.AddRange(requestInner);

// パケット送信
await networkStream.WriteAsync(batchBuffer.ToArray(), cts.Token);

このように、生パケットを構築して TCP で送信することで、外部ライブラリへの依存を減らしています。

2. アイコンアニメーションとメモリ管理

このアプリのメイン機能である「スキンの表示」ですが、Windows のタスクトレイアイコン (NotifyIcon) を頻繁に書き換える処理は、一歩間違えるとメモリリーク(GDI+ハンドルリーク)の温床になります。

Bitmap から Icon を生成する際に作成されるハンドルは、適切に破棄しないと OS のリソースを食いつぶしてしまいます。
そのため、以下のように DestroyIcon という Win32 API を明示的に呼び出して、確実にハンドルを解放する仕組みを入れました。

[DllImport("user32.dll", CharSet = CharSet.Auto)]
extern static bool DestroyIcon(IntPtr handle);

private void ShowSkinIcon(int index)
{
    // ...
    using var bitmap = new Bitmap(_currentSkins[index], new Size(32, 32));
    IntPtr hIcon = bitmap.GetHicon(); // ここでハンドルが生成される
    using (var tempIcon = Icon.FromHandle(hIcon))
    {
        SetCustomIcon((Icon)tempIcon.Clone());
    }
    DestroyIcon(hIcon); // 重要:使い終わったハンドルは必ず破棄
}

この「後始末」を徹底することで、長期間起動しっぱなしでも安定して動作するようになっています。

3. アイコン画像の自動生成

開発途中で、PNG 画像を Windows 標準の .ico 形式に変換する必要がありました。
これについても、外部ツールに頼るのではなく convert_icon.csx という C#スクリプトを作成して自動化しました。
16px から 256px までのマルチサイズアイコンを一括生成することで、高解像度環境でも鮮明なアイコンを表示できるようにしています。

開発における工夫

配布のしやすさも考慮しました。
.NET アプリはランタイムが必要で配布が面倒なイメージがありますが、Self-Contained (自己完結) オプションと PublishSingleFile を組み合わせることで、「単一の EXE ファイルだけで動作する」 環境を作ることができました。
さらに Inno Setup を使ってインストーラーも用意し、一般的なアプリと同じ感覚で導入できるようにしています。

おわりに

MineTray は、単に自分だけが使うことを想定して開発を始めたものでしたが、せっかくなので自分以外の人にも利用してもらえたらと思い、使いやすさ重視で開発を進めました。
技術的にも、低レイヤーのネットワークプログラミングから GUI 制御、インストーラー作成まで、デスクトップアプリ開発の面白い要素が詰まったプロジェクトになりました。

リポジトリ

ソースコードはこちらで公開しています。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?