Chienomi

文字化けの原理とホラーゲーム表現の話

基礎/リテラシー::knowledge

主にゲームにおけるホラー表現として、

縺薙l縺ッ縲∵律譛ャ隱槭ユ繧ュ繧ケ繝医陦ィ遉コ繧偵ユ繧ケ繝医☆繧九b縺ョ縺ァ縺

のような文字化けを見ることがある。

私は常々思っているのだが、人々はこれを「怖い」と感じるのだろうか?

少なくとも私はそんな表現を見た瞬間にかなり興醒めする。

例えるなら、シリアスなファンタジー作品のシリアスなシーンにおいて「レベル」や「ステータス」のような、ゲーム的表現が出てきたときのようだ。 いや、それがゲーム中世界という設定ならばまだ許せるが、「ファンタジー世界で起こる出来事」なのに「ゲーム上の出来事」としてイメージしてしまっていることを感じてしまうと、非常に興醒めしてしまう。

似たようなことではThe Backroomsにも感じる。 ホラー的な薄気味悪さは現実にも起きるのではないかという境界を曖昧にする部分があってこそ怖いと感じられるものであり、そこで起きるのがゲーム的な出来事になってしまえば、ゲームの中の話でしかないように感じられる。

だが正直個人的な感覚としては、この「ホラー中の文字化け表現」はそれ以上に陳腐で、ホラーの怖さを台無しにするようなものであると思う。 なぜならばこの「文字化け」は恐ろしい出来事ですらなく、「単純な失敗の結果」であるためだ。

さらに言うならば、それが理論上意味のある文字列になっているのであれば「ああ、変換に失敗したのか」と思うだけだが、意味のない文字列になっていた時に至っては「これを作った人は文字列の仕組みさえ知らずに書いているのか」と非常に腹立たしくなる。

本記事では、コンピュータ上の「文字」という概念の取り扱い、文字化けが発生する原理について完全な素人向けに解説する。

なお、本記事によって知識を獲得することによって、ホラー表現に文字化けが出現した際にホラーを楽しめなくなる可能性があることをご了承頂いた上で読み進めて欲しい。 嫌ならGo Back!

コンピュータにおける文字

前提

文字、そして言語は我々は日常的に、特に意識することなく扱っているものだが、実のところ極めて曖昧なものである。 曖昧な取り扱いで不自由なく取り扱えていると思っている普通の人々でさえ、実は言葉が正しく通じているかどうかはかなり怪しい要素であったりする。

「日本語の言葉の発生と解釈」は日本語学の範疇であるが、コンピュータにおいては「文字を厳密に定義する」必要があるという、全く違う問題が発生する。

コンピュータでは曖昧なものは取り扱えず、同じものは必ず同じ結果が得られるようにしなければならない。 これは文字においても同じだ。

コンピュータで文字を扱う上では、「文字集合」と「文字エンコーディング」という2つの概念が登場する。

文字集合

まず、「文字」というものそのものについて定義する必要がある。 「どのような文字が存在するか」が定義されていなければ、文字を表現する以前の問題だからだ。

この、「どういう文字が存在するかの定義」が「文字集合」である。

最も基本的な文字集合は、ASCIIだ。 これは、英語アルファベットやラテン数字など、94の印字可能な文字と、34の印字不可能な文字、計128文字から成る。

いきなり「印字不可能な文字」という表現が出てきて戸惑うかもしれない。 これはコンピュータを制御するために使う特殊文字が多いが、単に表示がないだけのものも存在する。 典型的なのが「スペース」である。 「スペース」は横方向に空間を空けるだけで、それ自体を印刷することはできない。なので、印字不可能な文字だ。 この他にも「改行」や「タブ」なども存在する。

日本語の文字集合で最も基本的なのがJIS X 0201である。 これは基本的には、ASCII128文字にカタカナ(いわゆる半角カナ)を付け加えたようなものであるが、

  • バックスラッシュ \ の代わりに円記号 ¥
  • チルダ ~ の代わりにオーバーライン

という2つの文字の置き換えが行われている。

JIS X 0201はASCIIに追加して93区画を持っているが、うち30区画は未定義であるため、実際に定義されている文字は63文字である。

JIS X 0201だけが定義されていた時代からコンピュータを使っている人は相当なロートルである1ため、人々が慣れ親しんだ日本語文字集合はJIS X 0208である。

ASCIIやJIS X 0201は0から順に番号を振っているだけだが、JIS X 0208の場合は「区点」という概念になっていてちょっと分かりづらい。 ただ、これは呼び方の問題で、基本的に縦横の表、つまり行列になっているものである。

JIS X 0208は94区94点の空間を持ち、8836個の空間を持っている。 このうち、JIS X 0208によって図形文字が定義されているのは6879個である。

基本的に長く日本人が使ってきた文字集合はこのJIS X 0208であるが、当然ながら6879個というのは漢字を網羅するにはかなり少ない。 頻出する、あるいは日常的な漢字を網羅してあると考えられていたのだが、人名や地名の表現には不足であることも多いほか、割と使われるにもかかわらず収録されていない文字も存在する。 ちなみに、6879個中、漢字は6355個である。

JIS X 0208について詳しく解説すると日本語的な難しい話になるので置いておく。

JIS X 0208で不足している漢字を補完する目的で作られたのが、JIS X 0212及びJIS X 0213である。

JIS X 0212及びJIS X 0213はその目的は同一であり、JIS X 0208からさらに収録字数を増やすためのものだ。 これは、0208+0212+0213という構造になっているわけではなく、0212と0213は目的も収録文字も被っている。

0212が存在するにも関わらず0213が登場した経緯は、0212が後述する当時普及していた文字エンコーディングであるShift_JISにおいて表現不可能であるため、Shift_JISの事情を勘案した上で定義されたのが0213ということである。

また、0212は0208+0212の補完関係になっているのに対して、0213は0208を内包しているという違いもある。

そして近年より広く使われているのがUnicodeである。

日本語文字集合は(一部キリル文字など収録されている文字もあるが)日本語を表現するためのものであり、外国語を表現することはできない。 例えば、韓国語は表現できないのだ。

なら他の文字集合と組み合わせてて使えばいいと思うかもしれないが、そもそも文字集合は「存在する文字の定義」であるため、そこにないものは存在しない扱いであり、存在するものを表現するためのものは存在しないものを表現できない。2 異なる文字列として異なる文字集合を並べられるようにアプリを作れば混ぜることはできるが、単一の文字列中に異なる文字集合を混ぜることはできない。

それは、インターネットがそれほど普及していなかった時期なら問題はなかった。 そもそも異なる文字集合を必要とする人々と交流する機会がなく、さらに言えば大抵の文字集合はASCIIを下地にしているため、どうしても他言語圏と交流する必要がある場合は英語によってやりとりすれば良いという方式だった。

が、インターネットが普及して国境の垣根が取り払われた今、言語の垣根をいつまでも残しておくことはできない。 そこで「新時代のASCII」として、世界中の文字を集めたのがUnicodeである。

文字集合に含まれていれば表現できるため、世界中の文字をひとつの文字集合にすれば異なる言語を混ぜることが可能になる。 ChienomiはUnicodeを用いた文字エンコーディングを用いて書かれているため、한국어を混ぜ込むことも可能である。

Unicodeにはバージョンがあり、都度更新されて新しい文字が収録されている。 Unicodeに収録されている文字の中には絵文字♠️やルーン文字ᚡ、ヒエログリフ𐦀なども含まれる。

文字エンコーディング

文字集合が「どのような文字が存在するか」の定義であるのに対し、それを「どのように表現するか」を定めたものが文字エンコーディングである。

多くの人が言葉としては知るように、コンピュータは01しか扱わない。 この01は数値に換算できるものであり、言い換えれば数値しか扱えない。 よって、文字を扱うためには文字と数値の対応関係が必要になる。

コンピュータの基本的な取り扱い単位として「バイト」があり、「1byte = 8bit」である。 8bit256であるため、文字集合で定義されている文字が256文字以下であれば、単純に数を振っていくだけで良い。 実際、ASCIIやJIS X 0201はそのようになっている。

1文字を表現するための単位が1バイトでない場合、コンピュータ的に色々難しい問題が発生する。 これがエンディアンや可変長エンコーディングといった問題なのだが、本記事は素人向けであるためこのあたりを深く解説しない。 が、可変長エンコーディングに関してはShift_JISの話の中で少し触れる。

文字エンコーディングは文字集合をどのように表現するかということであるため、前提となっている文字集合が存在する。 JIS X 0208に対する文字エンコーディングは、大雑把にはJIS (ISO-2022-JP), Shift_JIS, EUC-JPの3種類が存在する。

では

これは日本語です

という文字列をそれぞれの文字エンコーディングで表してみる。

JISの場合は

00011011 00100100 01000010 00100100 00110011 00100100
01101100 00100100 01001111 01000110 01111100 01001011
01011100 00111000 01101100 00100100 01000111 00100100
00111001 00011011 00101000 01000010 00001010

Shift_JISの場合は

10000010 10110001 10000010 11101010 10000010 11001101
10010011 11111010 10010110 01111011 10001100 11101010
10000010 11000101 10000010 10110111 00001010

EUC-JPの場合は

10100100 10110011 10100100 11101100 10100100 11001111
11000110 11111100 11001011 11011100 10111000 11101100
10100100 11000111 10100100 10111001 00001010

となる。

これだと見づらいので、2進数ではなく16進数による表現に変える。 2進数と16進数の関係については少し難しい話になってしまうため、そこは置いておき、とりあえずShift_JISは先程の2進数表現と

82 b1 82 ea 82 cd
93 fa 96 7b 8c ea
82 c5 82 b7 0a

という16進数表現が同一であるという説明にとどめておく。

Unicodeに対する文字エンコーディングとしては、UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BEなどが存在する。

基本的に現代でUnicodeの文字エンコーディングといえばほとんどの場合UTF-8で、特に何も言わなければ現代のコンピュータ上の文字列は文字集合はUnicode、文字エンコーディングはUTF-8であると解釈されることがほとんどである。

ただ、Unicodeが普及する過程ではUTF-16のほうが好まれていた時期が存在する。 このため、Windowsのシステム上ではUTF-16LEが使われていたりする。

先ほどのこれは日本語ですという文字列をUTF-8で表現すると

e3 81 93 e3 82 8c
e3 81 af e6 97 a5
e6 9c ac e8 aa 9e
e3 81 a7 e3 81 99
0a

となる。

Shift_JISについて

本記事の趣旨である文字化けを理解するためには、Shift_JISについてはもう少し深く知っておく必要がある。

古くからJIS文字集合に対して使われてきた文字エンコーディングはJISであった。 JISはISO 2022という国際標準の方式を取っているが、この処理が重かった。また、前の例を見てもらうとJISがShift_JISより長いのが見て取れるかと思うが、「日本語に特化すればより短くできる」という背景もあった。

こうしたこともあって日本語専用に作られたのがShift_JISである。

Shift_JISはJIS X 0201およびJIS X 0208を表現することに特化している。 Shift_JISは企業合同による策定である。

Shift_JISの特徴のひとつに、可変長エンコーディングであるということが挙げられる。 これは、「1バイトで表現できるものは1バイトで、2バイト必要なら2バイトで」という方式で表現されているということだ。 このため、ASCIIの構成文字からなる文字列は、ASCIIとしてもShift_JISとしても解釈できる。

「2バイト文字」という言葉もここに由来している。 これは余談であるため、後に述べる。

WindowsではShift_JISを改変したMSCP932が使われた。 MacでもShift_JISを改変したMacJapaneseが使われた。 両者には微妙に互換性がなく、当時はこのWindowsとMacの交換で微妙に違う文字に変わってしまうという問題を特に文字化けという場合が多かった。

こうしたことから、独自に拡張された、本来の規格に沿っていないものではあるものの、Shift_JISをベースとしたものが非常に広く使われていた。 また、多くの場合これらの独自拡張されたものをShift_JISと呼んでもいた。

Unix界隈では一般的にはEUC-JPが使われていたし、EUC-JPとShift_JIS間での解釈違いが発生すれば当然文字化けするのだが、人々がEUC-JPで書かれたドキュメントに触れる機会はほとんどなかったし、EUC-JP圏の人々は「どうせShift_JISでしょ」と思っていたから問題が起きることは稀であった。

文字化けとその原理

単にデータを与えられたとき、まずそれが文字列かどうかを判定するのも難しいのだが、文字列だと分かったとしても、それがどのような文字エンコーディングかを判定するのはさらに難しい。 そのため、基本的にテキストを扱うときは文字エンコーディングは固定するか、明示するかということが必要になってくる。

ただ、「テキストは日本語である」という前提があれば、「文字エンコーディングを推測する」ということもできなくもない。 が、これは非常に難しいし間違えることもある。

もし間違った文字エンコーディングであると解釈したらどうなるか?

まず一番可能性が高いのは「解釈できない」である。 が、テキストというのは1ビットでも誤りがあったら理解できないというものではなく、たとえ部分的に壊れていたとしても解釈できる部分だけでも読みたいということが多い。 実際にそのように「解釈できない部分は無視する」という挙動を選択する場合が多い。

では、間違った文字エンコーディングを「解釈できないが、解釈できない部分は無視する」という振る舞いで解釈したらどうなるか。 答えは「正しくない文字列であるように見える」だ。

このポリシーを用いて先程のこれは日本語ですという文字列を、正しくはEUC-JPなのにShift_JISであるとして解釈したとすると

、ウ、、マニヒワク、ヌ、ケ

となる。

ちなみに、逆にShift_JISで書かれたものをEUC-JPやJISとして解釈することは、どう頑張ってもできない。

もう察したかもしれないが、日本人が一般的に目にする「文字化け」とは、Shift_JISでないものをShift_JISであると間違って解釈することから発生するである。

基本的に他のものとしての解釈は文字列として解釈不能であることがほとんどだからだ。 例外的には、JISの文字列をUTF-8として解釈することは可能で、

$3$l$OF|K\8l$G$9

という感じになる。

さて、「間違ってShift_JISとして解釈した場合」ということは、UTF-8文字列をShift_JISとして解釈した場合も存在するわけだ。 するとどうなるとかというと

縺薙l縺ッ譌・譛ャ隱槭〒縺

となる。 見慣れた感じになっただろう。

そう、この表示はUTF-8である文字列をShift_JISであると誤って解釈した場合に発生するものである。

ChienomiをShift_JISとみなした場合

これを人々が目にする機会が多いのは単純な理由で、「以前は主流だったShift_JIS」と「現在主流のUTF-8」の関係であるためだ。 UTF-16LEをShift_JISとして解釈すると

S00o0蘰,g條g0Y0

という感じになるが、これを見る機会は少ないだろう。

また、これを目にする機会が多い理由はより具体的な部分にもある。

まずはウェブだ。 ウェブは現代のHTML5においては文字エンコーディングについて何も記載がなければUTF-8であると決められているが、それ以前は決まりがなかった。 その上で、Google ChromeおよびFirefoxの挙動として、サーバーレスポンス、およびドキュメントに文字エンコーディングの規定がない場合

  • HTML5として宣言されていればUTF-8
  • HTML5でなく、ブラウザの言語が日本語ならShift_JIS

として解釈するようになっているため、この理由からウェブ上で特に古い文書に当たるとこの問題が発生しやすい。

もうひとつの理由として、日本語版Windowsが優先的にShift_JISとして解釈しようとするために、Windows上ではUTF-8テキストが正しく解釈できずに文字化けすることが多かった、というのがある。 この背景として、WindowsはUTF-8として解釈するために特殊な条件を要求していたため、この条件を満たさない普通のUTF-8文字列がShift_JISとして解釈される機会が多かったというのがあるが、近年Windowsもこの条件を廃止する方向に変わりつつあるため、これは減っていくものと思われる。

余談: 縺

縺という文字は「もつれ」であるが、UTF-8をShift_JISとして解釈した場合にめちゃくちゃ出てくる。

これは、境界を同一としたUTF-8のひらがなが「縺○」または「繧○」になるためで、日本語テキストのひらがなの比率を考えると必然的に縺の登場頻度が高くなる。

ちなみに、カタカナは「繧○」または「繝○」になる。

ただ、1文字のバイト数の違いから境界が変わってこれ以外になることもある。

余談: 2バイト文字

1バイトで表現できるのはJIS X 0201の範疇であるが、JIS X 0201で定義されているのはカタカナだけである。 しかも、濁点や半濁点は独立した文字として定義されており、この時点ではアルファベットと同じ大きさのボックス上で表現するのが普通だった。

対してJIS X 0208は漢字なども含んでおり、アルファベットと同じボックスで表現するのはかなり難しい。 しかも、JIS X 0208はJIS X 0201とは別にカタカナが定義されており、こちらは濁点/半濁点つきの文字を含む。 このことからJIS X 0208の文字は一般的に横2個分のボックスを使うのが普通であった。

文字が視覚的にどのように表現されるかはフォントの領域であり、データとしての文字列の話とは直接関係がないのだが、通例としてそのようにされる関係から俗称として横1個分のボックスを用いるものを「半角」、横2個分のボックスを用いるものを「全角」と呼ぶようになった。

そして、全く別の理由ではあるが、JIS X 0201の文字は通例として半角で、JIS X 0208の文字は通例として全角で表現される。 そしてShift_JISではJIS X 0201の文字は1バイトで、JIS X 0208の文字は2バイトで表現する。

このことから「半角=1バイト」、「全角=2バイト」と表現されたわけである。

しかし今日に至ってこの表現を用いるのはシンプルに間違いとなる。

というのも、文字の幅というのは文字から確定できない。 例えばJIS X 0208にあるはJIS X 0208の中にある以上、日本語フォントは横2個分の幅として描くのが普通だ。

ところが、という文字自体は別に日本固有のものではないから、アルファベットのような横幅を欲しない文字を用いる国のフォントであっても採用することは普通にありえる話である。 その場合は、「横2倍幅の文字」などというものがほかに存在していないのだから、横1個分で表現するのが普通という感覚になる。

この結果、という文字は横1個分で表現されるべきか、2個分で表現されるべきかが明らかでないという事態が生じる。

こうしたことから、一般的に文字はnarrow, wide, ambiguousで表現される。

それとは別に、JIS X 0201とJIS X 0208ではカタカナと英語アルファベットが重複して収録されており、それぞれ異なる幅で表現されてきた。 その背景を以て文字に名前がついているUnicodeとJIS X 0213では「半角カナ」に “Halfwidth” という名称が使われている。また、UnicodeではJIS X 0208のアルファベット、通称「全角アルファベット」に “Fullwidth” という名称がつけられている。 つまり、年月を経て「半角・全角」は公式化したわけである。

が、これとバイト数は全く違う話である。 というのも、JIS X 0201は1バイト、JIS X 0208は2バイトというのはShift_JISだけの話である。 EUC-JPは漢字は3バイトが使われる可能性があるし、半角カナは2バイトになる。 JISに至っては半角カナは表現すらできない。3

そして今一般的なUTF-8に至っては、結合しない1文字に対してさえ最大で6バイトとなり、結合が含まれる場合はもっと長くなる。 Unicodeの「基本」の漢字で2〜3バイト、Unicodeの追加漢字面の漢字で4バイト、IVS(異体字)を使うと7〜8バイトである。

これは、前述のUTF-8のバイナリ表現がShift JISと比べてかなり長いことからも分かるだろう。

このことから、「2バイト文字」という表現は「古臭い上に理解もしてない」というふうに受け取られてしまう可能性があるため、注意が必要だ。