たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

Unityと文字コードでレッツダンスパーティ!

続・僕らの文字コード

なんかVSCodeでMarkdown書くの使いやすくなってますね

文字コードという無限の闇

前回に続いて文字列に関する話題です
xztaityozx.hatenablog.com

bashからの出力を得てUnityに表示したいだけなんだけれども中々うまくいかなかったというお話だったわけですが
今回もなかなかうまくいかなかったお話です

Process process_bash = new Process();
ProcessStartInfo psi_bash = new ProcessStartInfo();
//設定
psi_bash.FileName = @"C:\cygwin64\bin\bash.exe";

psi_bash.RedirectStandardInput = true;
psi_bash.RedirectStandardOutput = true;
psi_bash.RedirectStandardError = true;

psi_bash.CreateNoWindow = false;
psi_bash.UseShellExecute = false;
psi_bash.Arguments = "--login";

//プロセス起動
process_bash = Process.Start(psi_bash);
process_bash.BeginOutputReadLine();
process_bash.BeginErrorReadLine();
process_bash.OutputDataReceived += onBashOutput;
process_bash.ErrorDataReceived += onBashError;

コード見てもらったらわかる通りOutputやErrorが出るたびハンドルを介して出力が得られる非常に簡単な構造
間違えそうなところがない

しかし!
MojiCode1
ご覧の通り文字がばけとります

www.bandai.co.jp

cygwinbashから受け取ったデータをUnityのTextに表示しているだけなんですが何がダメなのかな?
ともかくこのままじゃまともに使えないですね

文字コードをさがせ

文字化けについてggっていたところどうやらUTF-8でも BOM付き ってのと BOM無し ってのがあるみたいで?
site.oukasei.com

詳しいことはおいといて、UnityがBOMないとアカンのかどうかを調べてみると
MojiCode4

どうやら ソースについてはBOM付き である必要があるみたい
UnityのTextがどうかはわからないけどBOM付きUTF-8だろ~な~と思いました。

bashの方の出力がBOM付なのかどうかが問題ですね。
先ずは$LANGechoします

% echo $LANG
ja_JP.UTF-8

次に適当にコマンドをリダイレクトしてBOMついてるか見ます

% date > date_res.txt
% file date_res.txt
date_res.txt: UTF-8 Unicode text

BOM付きの場合は

date_res.txt: UTF-8 Unicode (with BOM) text

と出力されるのでどうやらBOM無しUTF-8のようです

このままではC#に渡したところで正常に出力されまへん
MojiCode5

さしあたってbashの出力にBOMをくっつければ解決するわけですから適当にBOMつけてみました。
MojiCode6

Encoding enc_src = new UTF8Encoding();
Encoding enc_dst = Encoding.UTF8;
byte[] noBOMbytes = enc_src.GetBytes(data);
byte[] BOM = enc_dst.GetPreamble();
byte[] appendedBOMBytes = BOM.Concat(noBOMbytes).ToArray();
string encodedStr = enc_dst.GetString(appendedBOMBytes);

そーれ

・・・
MojiCode1
画像は使いまわしです

あかんやんけ!!!!!!!!!

まぁ想定の範囲内でしたが(嘘)

実はShift_JISに浮気していた

そうこうしているうちにTwitterで心強いリプが

  

ええ!?Shift_JIS!?
んえ!?

いままでUnityのTextがUTF-8であると踏んでいましたがなんかShift_JISのようです・・・?
MojiCode7

こうなったらEncoding.Convertを使ってShift_JISにしてやります。

byte[] convBytes=Encoding.Convert(Encoding.UTF8,Encoding.GetEncoding("Shift_JIS"),data);
string convStr=Encoding.GetEncoding("Shift_JIS").GetString(convBytes);

これで円満解決。ね。

・・・
MojiCode1
画像は使いまわしです

ちょw

stdoutはすごぉい美少女

色々ggったりsrcとdst入れ替えたりProcessStandardOutputEncodingとかもいじってみたんですけどどうにもこうにもダメでした
なんかな~と思ってハンドラをやめてProcessStandardOutputから直接読み取ってみると文字化けしませんでした。

MojiCode8
たぶんstdoutがすごい美少女だから素直に言うこと聞くけど、ハンドラの方はさえない男なのでこういう塩対応なんでしょう

でも便利なハンドラの方使いたいです。さえないってところで親近感もわきますし

base64って

このままじゃ全く前に進まないので死んだ目をしながらggっていたんですがまたTwitterにて心強いリプが

base64ってなんやねん

Base64は、データを64種類の印字可能な英数字のみを用いて、それ以外の文字を扱うことの出来ない通信環境にてマルチバイト文字やバイナリデータを扱うためのエンコード方式である。MIMEによって規定されていて、7ビットのデータしか扱うことの出来ない電子メールにて広く利用されている。具体的には、A–Z, a–z, 0–9 までの62文字と、記号2つ (+, /)、さらにパディング(余った部分を詰める)のための記号として = が用いられる。この変換によって、データ量は4/3(約133%)になる[1]。また、MIMEの基準では76文字ごとに改行コードが入るため、この分の2バイトを計算に入れるとデータ量は約137%となる[2]。 Wikipedia -Base64

なるほど
どうやら変換用のbase64コマンドもCoreutilsに入っているようなのでだいたいの環境で使えそうです
適当にbase64に通して結果を見てみましょう

$ date | base64
MjAxN+W5tCAy5pyIIDE05pelIOeBq+abnOaXpSAxNTowMDoxOSBKU1QK

こいつを間に挟み込みましょう
bashのstdinに書き込むときに2>&1 | base64を付け加えておきます
MojiCode9
MojiCode10

デコードにはSystem.Convert.FromBase64String(String)メソッドをつかいます。

private void outputCommand(string o) {
    byte[] converted = System.Convert.FromBase64String(o);
    Encoding enc = new UTF8Encoding();
    string encoded = enc.GetString(converted);
    output = o + "\n" + encoded + "\n";
}
private void onBashOutput(object sender, DataReceivedEventArgs e) {
    outputCommand(e.Data);
}

それ~

MojiCode11
で、
でた~~~~~~~~~

いやー助かりました。

まだまだ終わらないぞ~~~~!

終わらないです。
この調子で行くかと思ったら違いました。不思議よね

まあ適当にコマンドの実験をしているとなんか変な結果が
MojiCode12

んw

このコマンドはないですという旨の出力を期待してたんですがなんかへんです
ちなみに本来なら

$ da 2>&1 | base64
YmFzaDogZGE6IOOCs+ODnuODs+ODieOBjOimi+OBpOOBi+OCiuOBvuOBm+OCkwo=

という文字列が得られるはずなんですが、gZvjgpMKしかとれてないしどことも部分一致しないんです・・・!

わーいwおもしろーいw

まぁとりあえず落ち着けよ
まずはstdoutとstderrを分けるんだ

da 2>&1 > /dev/null | base64 >&2;da 2> /dev/null | base64 >&1

そうそう・・・こうやって・・・
MojiCode12
画像は使いまわしです

まぁこういう問題じゃぁないことぐらいわかってるよHAHAHA

じゃあなんなのよ!

stderrの反乱

いままで構ってこなかったからかすっかりご機嫌ナナメのstderrがついに暴走しました
stderrはかわいい少年のイメージです
MojiCode15

まずはstdoutかstderrかもわからんのでTextのリッチテキスト形式を利用して赤くします

private void outputCommand(string o,bool isError=false) {
    byte[] converted = System.Convert.FromBase64String(o);
    Encoding enc = new UTF8Encoding();
    string encoded = enc.GetString(converted);
    if (isError) {
        encoded = "<color=red>" + encoded + "</color>";
    }
    output = o + "\n" + encoded + "\n";
}

private void onBashOutput(object sender, DataReceivedEventArgs e) {
    outputCommand(e.Data);
}

private void onBashError(object sender, DataReceivedEventArgs e) {
    outputCommand(e.Data,true);
}

うんうん

そしてとりあえずstderrをbase64に通さず素直に出力しましょうね

private void onBashError(object sender, DataReceivedEventArgs e) {
    UnityEngine.Debug.Log("Debug : "+e.Data);
    outputCommand(e.Data,true);
}

MojiCode13

まぁ化けるわな

ここでなんとなく同じ文字列をTextに出してみました。

private void onBashError(object sender, DataReceivedEventArgs e) {
    byte[] errorBytes = Encoding.GetEncoding("Shift_JIS").GetBytes(e.Data);
    string errorStr_base64 = System.Convert.ToBase64String(errorBytes);
    outputCommand(errorStr_base64,true);
}

こんなふうにC#エンコードデコードをどっちもやります
MojiCode14

!?

ええ!?Shift_JISでいいの!?
なんか一部化けてるけどほぼ正常に出てる!?なんだこれ! (ちなみにハンドラから直接e.Data渡したら全部化けました、もしかしてここか?)

え?ハンドラからでてくるstdoutは何かわからない文字コード(たぶんUTF-8)だったのにstderrの方はShift_JISなの?
なんだこれ?
MojiCode16

Unityの方のコンソールは化けたままなのでUTF-8・・・だとおもいます・・・
Textの方は文字コードを適当に合わせてくれるようです・・・?
stdoutの方が化けてしまったのはTextがUTF-8Shift_JISだと誤解してしまった、というかどこかで誤解させてしまったのか
もしかしたらもともとShift_JISだったのか
で、でもbashの$LANGはUTF-8だし・・・

ともあれ何とか日本語を表示できるようになったのでこの件はいいですがもう少しちゃんと調べる必要がありそうです。

まとめ

今回はかなり振り回されました。base64Shift_JISのヒントをもらったりしてなんとか漕ぎつけましたが・・・
無意識にこのクラスにクソコードを紛れ込ませてしまっているのかわかりませんがコメントをちゃんと残しつつ、整理したいと思います。

名前 文字コードだと思うもの
bash stdout UTF-8? Shift_JIS?
bash stderr Shift_JIS?
bash $LANG UTF-8
Unity Text たぶん合わせてくれる
Unity コンソール UTF-8だとおもう

そ、それでは良い文字コードライフを・・・