今回は昔懐かしの「復活の呪文」と
BASE64の関係についてです

解説動画はこちら



復活の呪文について
さて、復活の呪文を知らない人のために
復活の呪文についてですが

ドラクエ1-2に実装されていた
データの進行状況を保存する方法のことで

ひらがなの羅列を入力し
正しい呪文であったら
対応するステータスが再現される
というものです

発売当時のファミリーコンピューターには
ゲームの保存機構が付いていなかったので
知っていたら40代以上確定という代物

その後のドラクエ 3や
ファイナルファンタジーなどは
セーブ機能があるので
この復活の呪文を知る世代は
少なくなっているかもしれません


ドラクエ1の復活の呪文の仕様

ドラクエ 1と2では復活の呪文の
使用が違いますが
ドラクエ 1は次の様です

使える文字種(64文字種)
あいうえお かきくけこ
さしすせそ たちつてと
なにぬねの はひふへほ
まみむめも らりるれろ
やゆよわ 
がぎぐげご ざじずぜぞ
だぢづでど ばびぶべぼ

呪文の長さ
20文字固定

実データ長
6(bit)×20=120(bit)=15バイト相当

有名な復活の呪文
くわたきよはらしのずかなかはたはらいしい


この
ひらがな+濁音文字で64文字というのが
BASE64の仕様と似ている訳です

ここでBASE64の仕組みを見てみましょう


BASE64
データを64種類の印字可能な
英数字のみを用いてそれ以外の文字を
扱うことの出来ない通信環境にて
マルチバイト文字や
バイナリデータを扱うためのエンコード方式

電子メールなどに利用されているような仕組みです
最近だと画像なんかも有りますね

BASE64の変換の仕組みが
復活の呪文の変換方法と似ているので
BASE64の仕組みを見てみましょう


BASE64の仕組み

まずはBASE64文字対応表を用意します
アルファベットを6ビットで対応させた
辞書データのようなものです

使える文字種は
英大文字(26種):A - Z
英子文字(26種):a - z
数字(10種):0 - 9
記号(2種): + / 

これはPythonだとstringライブラリで
用意できます

あとはこれを6ビットの2進数文字列にします
import string

upper = string.ascii_uppercase
lower = string.ascii_lowercase
digits = string.digits
base64_str = upper + lower + digits + '+/'

dict64 = {}
for i ,s in enumerate(base64_str):
    b = bin(i)
    str64 = '{0:06}'.format(int(str(b)[2:]))
    dict64[str64] = s
    print(s ,'\t', str64)
A 000000
B 000001
・・・
9 111101
+ 111110
/ 111111

こんな感じで64文字分のデータを用意します

次にBASE64に変換する文字列を用意します
今回は「Otupy」

BASE64に変換するには
このアルファベットを文字列から16進数に直し
16進数から2進数文字列に変換して連結します

text = 'Otupy'

out = []
for t in text:
    hex_str = t.encode('utf-8').hex()
    int_num = int('0x' + hex_str , 0)
    bin_num = bin(int_num)
    out_str = '{0:08}'.format(int(str(bin_num)[2:]))
    out.append(out_str)

print(out)

encode_str = ''.join(out)
print(encode_str)
['01001111', '01110100', '01110101', '01110000', '01111001']

0100111101110100011101010111000001111001

連結するとこんな感じの文字列になります

この連結した文字列を
6桁(ビット)で区切って
さっき作った対応表に当ててみると

values = []
for i  in range(0,len(encode_str),6):
    key = encode_str[i:i+6]
    key = key if len(key)==6 else key + '0' * (6-len(key))
    value = dict64[key] if key in dict64 else '='
    values.append(value)
    print(key , value)
010011 T
110111 3
010001 R
110101 1
011100 c
000111 H
100100 k


こんな感じで文字列を変換できました
なお、変換後の文字列の長さは4の倍数分の長さにして
足りないところは=で埋めるようです

v_str = ''.join(values)
encode64_str = v_str if len(v_str)%4==0 else v_str + '=' * ((8-len(v_str))%4)
print(encode64_str)

T3R1cHk=

これがBASE64に変換した際の文字列です

長々と仕組みを見てきましたが
PythonにはBase64ライブラリがあるので
この変換自体は1行で出来ちゃいます

import base64

# T3R1cHk=
text = 'Otupy'
st = base64.b64encode(text.encode())
print(st)
b'T3R1cHk='


道中の、64文字の変換表を用いて
データを変換させる仕組みの部分が
復活の呪文に近い所ですね

と言うわけで
復活の呪文ぽい実装を見てみましょう


復活の呪文っぽい実装


64文字のひらがなを用意して
それに対応するように変換します

import re

charas = '''
あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほ
まみむめもらりるれろやゆよわ
がぎぐげござじずぜぞばびぶべぼぱぴぷぺぽ
'''.replace('\n','')

# 復活の呪文にする
def jyumon_encode(bin_num):
    b = bin_num + '00000'
    b = b[0:-(len(b)%6)]
    encoded = ''.join(map(lambda c: charas[int(c, 2)], re.split('(.{6})', b)[1::2]))
    return encoded

# 復活の呪文からデータにする
def jyumon_decode(encode_text):
    decode = ''.join(map(lambda c: format(charas.find(c), '06b'), encode_text))
    return decode

20文字分のひらがなを入れてみましょう

encode_text = 'くわたきよはらしのずかなかはたはらいしい'

decode_text = jyumon_decode(encode_text)
print(decode_text)

re_encode_text = jyumon_encode(decode_text)
print(re_encode_text)
000111101011001111000110101010011001100011001011011000110011000101010100000101011001001111011001100011000001001011000001 くわたきよはらしのずかなかはたはらいしい


呪文からデータへ
データから呪文を生成します

ゲームではおそらく
このようなデータを使って
ゲームのステータスを再現していたのでは
なかろうかと思います



まとめ

現在ではBASE64への変換などは
ライブラリで一発で出来ちゃいますが
1986年(昭和61年)当時は
BASE64ライブラリとか無いんじゃないかと

と言うわけで文字の変換表などを
実装していただろうと推測します

各桁の数値をデータに対応させて
たんだろうなーと思うと
1文字間違えて地獄を見た日々を思い出しますね


ドラクエ1の容量が512kbit(64KB)らしいので
画像とプログラム含めでこのサイズに
納めるのは凄まじいテクニックが
必要だったんだろうと思います

今では画像だけで1MBとか
余裕で越してしまうので
当時の技術力の高さが
窺い知れますよねー

というわけで
今回は昔懐かしの
「復活の呪文」とBASE64
についてでした


それでは