【UE4】完全に独立したカスタムクラッシュレポーターを実装する

#はじめに
クラッシュレポーターに関する日本語情報はとても少ない
というか英語でも少ないし、UE4.24前後からは仕様が微妙に変化しているらしくggってもわからない部分が多い
なので「かっこいいクラッシュレポーターを作りたい！」と思ったときにとても苦労するので
自分がわかっている範囲で共有

###完全に独立したカスタムクラッシュレポーターとは?
標準のクラッシュレポーターはこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138731/e16faafd-55c8-6107-3d15-d2aab8a3dca2.png)



見てわかる通り、めちゃくちゃ「UE4！！！！！」という印象だし出てほしくない情報とかも出ている
まぁ標準のクラッシュレポーターじたいもEngineのソースを弄れば変更が可能なのでレイアウトを変更したり見た目を変えることも可能ではあるけれど、色々要件がある場合自前でクラッシュレポーターを1から作成してそちらを使用したいというケースが出てくる
しかしそうなるととたんに情報が少なくなるのでそこらへんの情報をまとめる

###想定しているゴール
* 完全に独立したカスタムクラッシュレポーターを呼び出すことが出来る
* エラー情報を取得することができる


#クラッシュレポーターとは
UE4のエディタやパッケージ化したバイナリがクラッシュした際に出てくるエラー情報などを表示したうえで事前に設定したサーバへエラー内容を送信してくれるクライアント
詳細は[公式ドキュメントを参照](https://docs.unrealengine.com/4.26/ja/TestingAndOptimization/CrashReporter/
)


#開発環境
UE4.26系
Windows10
Visual Studio Community 2017


#前準備
###Githubから持ってきたEngineでビルドする
<font color=red>**EpicgameLancherでDLした通常のUE4ではカスタムのクラッシュレポーターは使用できないので必ず行ってください**</font>

1. GithubからEngineのソースを手に入れる方法についての詳細は[公式ドキュメントへ](https://docs.unrealengine.com/4.26/ja/ProgrammingAndScripting/ProgrammingWithCPP/DownloadingSourceCode/)
2. Engineのソースが用意出来たらuprojectファイルを右クリックでSwitch Unreal Engine version
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138731/153d30ce-f22a-2db3-7d9e-251121552cbe.png)
3. 用意したEngineを指定してslnファイルを再生成
4. VisualStudioでエディタを再ビルドする



###プロジェクト設定
クラッシュレポーターをパッケージに含めるためとエラー情報を取得できるようにするために以下の二つをプロジェクト設定からONにする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138731/48d25471-b477-3370-a658-d329e5c683de.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138731/0da3c424-a74a-a0a6-12fe-0294b163cfee.png)


#独自のクラッシュレポーターを実装する
クラッシュレポーターの実装形式は何でもよいのだが、今回は標準的なWPFアプリケーションで実装

**※ここのコードそのものは重要ではない**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138731/0959c6c3-424b-0b5c-0d96-5f6101073175.png)

``` c#:App.xaml.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.IO;

namespace CrashRepoter
{
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application
    {
        private string windowMessage = "";
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            List<string> argList = new List<string>(e.Args);
            string path = argList.Find(n => n.Contains("UE4CC-") && n.Contains("Crashes"));
            string logText = null, xmlText = null;
            byte[] miniDump = null;
            if (string.IsNullOrEmpty(path))
                return;

            GetErrorInfos(path, out logText, out xmlText, out miniDump);
            windowMessage = xmlText;
        }

        protected override void OnActivated(EventArgs e)
        {
            base.OnActivated(e);

            MainWindow mainWindow = MainWindow as MainWindow;
            mainWindow.SetDescriptionText(windowMessage);
        }

        private void GetErrorInfos(string crashDirPath, out string OutLog, out string OutXml, out byte[] OutMiniDump)
        {
            OutLog = "";
            OutXml = "";
            OutMiniDump = null;
            DirectoryInfo crashDir = new DirectoryInfo(crashDirPath);
            if (crashDir.Exists)
            {
                crashDir.Refresh();

                FileInfo[] fileBuffer = null;

                fileBuffer = crashDir.GetFiles("*.log");
                if (fileBuffer.Length > 0)
                {
                    FileInfo logFile = fileBuffer.First();
                    OutLog = File.ReadAllText(logFile.FullName);
                }

                fileBuffer = crashDir.GetFiles("*.runtime-xml");
                if (fileBuffer.Length > 0)
                {
                    FileInfo logFile = fileBuffer.First();
                    OutXml = File.ReadAllText(logFile.FullName);
                }

                fileBuffer = crashDir.GetFiles("*.dmp");
                if (fileBuffer.Length > 0)
                {
                    FileInfo dumpFile = fileBuffer.First();
                    OutMiniDump = File.ReadAllBytes(dumpFile.FullName);
                }
            }
        }
    }
}


```

##重要な部分のコード解説
###エラー情報の吐き出し先のPathを取得する
引数に起動時のパス情報などが渡されるので文字列検索で取得可能

```c#
List<string> argList = new List<string>(e.Args);
string path = argList.Find(n => n.Contains("UE4CC-") && n.Contains("Crashes"));
```
EventArgs引数が取得できない場合はEnvironmentクラスで取得可能なのでこちらでもOK

```c#
string[] args = Environment.GetCommandLineArgs();
```

####備考
独自のクラッシュレポーターを作成するときにあまりネットに情報ないのがUE4側から吐き出されたエラー情報のファイルがどこにあるのかという点
ネットにある資料（参考リンク）では以下のPathにあるとあるのだが現在は仕様が変わったせいなのかここには存在しない

```
~\AppData\Local\[Game Name]\Saved\Crashes\UE4CC-Windows-#################################
```
現在は以下のパスに出力される

```
~\[Packaged folder]\[project name]\Saved\Crashes\UE4CC-Windows-#################################
```
なお、引数として渡される情報は以下の通りである

| 第1引数 | 第2引数 | 第3引数 | 第4引数 |
|:-:|:-:|:-:|:-:|
| エラーファイルの出力Path  | gameのAppName  |  今回のCrashGUID |  DebugSymbolsのPath | 


#パッケージされた成果物のクラッシュレポーターをカスタムクラッシュレポーターにする
###実行ファイルを置き換えるだけ
UE4のクラッシュレポーターはゲームとは完全に独立していて別プロセスとなっているので置き換えるだけで動作するようにできている
クラッシュレポーターは以下のPathに配置されているので**そのディレクトリのファイルを全て削除してカスタムクラッシュレポーターの成果物へ置き換えよう**
<font color=red>**※この時カスタムクラッシュレポーターのファイル名は全て「CrashReportClient」にしておこう**</font>

```
~\[Packaged folder]\Engine\Binaries\Win64
```

置き換えた後のクラッシュレポーターディレクトリ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/138731/5d37e457-8bec-f55b-adb5-abb3f117a85b.png)


これで全ての作業が完了
ゲームを起動させてコンソールコマンドなどで意図的にクラッシュさせて作成したクラッシュレポーターが起動することを確認しよう


#おまけ
いやー完全独自のカスタムクラッシュレポーターとか要らないから標準のクラッシュレポーターをパパっといじりたいですわ～
という人は以下のbugsplatさま(参考リンク)の記事を見ればどうにかなるので参考にしてほしい


#参考リンク
[teal-gameさま](http://www.teal-game.com/blog/customcrashreporter/)
[bugsplatさま](https://www.bugsplat.com/resources/development/customizing-ue4-crash-dialog/)
[Sentry.IOさま](https://docs.sentry.io/platforms/native/guides/ue4/)