記事一覧

YAYAでBase64エンコード

09/07/27 追記

YAYA/Tc535-2で追加されたFREADENCODEを使うことで簡単にBase64エンコードが可能となりました。
ありがとうございます。

sys.fnc.base64encode
{
  _filename = _argv[0]
  FCHARSET('binary')
  if !FOPEN(_filename, 'rb') {
    return
  }
  _base64 = FREADENCODE(_filename, 0, 'base64')
  FCLOSE(_filename)

  _base64
}

以下で紹介するやり方では容量の大きいデータを扱うことはできないようです。
100kB程度でフリーズします。

追記ここまで

さまざまなアプローチがある

YAYAにはBase64エンコードする関数が既に用意されています。

単純なテキストデータならこれでエンコードできるのですが、画像ファイルなどのバイナリデータとなるとそうもいきません。

上記関数の注意書きにある通り、YAYAではバイト値 0x00を文字列に含むことができないのです。
素直にSAORI等の外部プラグインモジュールを作成するのが良さそうですが、残念なことに私はC/C++がわかりません。

悩んでいたところ、「それ100% Pure YAYAでできるよ」といったアドバイスを受け、YAYAで書いてみることにしました。

バイト値 0x00問題をどう解決したものか思い付きませんでしたが、 @lre 氏に、「FREADBIN の引数charを変えて二回読み込んで差分をとる」というアイディアを頂きました。その発想は無かった。多謝。

ソース

以下のようなコードで動作しました。Base64デコードすると画像ファイルもちゃんと復元できました。
対象となる画像ファイルをあらかじめコピーしておいて2つを同時に読み込んで比較します。冗長なやり方になりますが100% Pure YAYAなのが最大の魅力です。

sys.fnc.base64encode
{
  _filename1 = _argv[0]
  _filename2 = _argv[1]
  _outputfilename = _argv[2]
  _ret = ''
  _bin = ''
  _chr1 = CHR(0x01)
  _chr2 = CHR(0x02)
  _base64table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
  FCHARSET('binary')
  if !FOPEN(_filename1, 'rb') {
    return
  }
  FCHARSET('binary')
  if !FOPEN(_filename2, 'rb') {
    FCLOSE(_filename1)
    return
  }
  _buffer = 1
  while 1 {
    _line1 = FREADBIN(_filename1, _buffer, _chr1)
    _line2 = FREADBIN(_filename2, _buffer, _chr2)
    if (_line1 == -1 || _line1 == '' || _line2 == -1 || _line2 == '') {
      break;
    }
    if _line1 != _line2 {
      _bin += '00000000'
    }
    else {
      _bin += sys.fnc.size8(TOBINSTR(CHRCODE(_line1)))
    }
  }
  FCLOSE(_filename1)
  FCLOSE(_filename2)
  for _i = 0; _i < (STRLEN(_bin) % 6); _i++ {
    _bin += '0'
  }
  for _i = 0; _i < (STRLEN(_bin) / 6); _i++ {
    _str = SUBSTR(_bin, _i * 6, 6)
    _int = BINSTRTOI(_str)
    _chr = SUBSTR(_base64table, _int, 1)
    _ret += _chr
  }
  for _i = 0; _i < (STRLEN(_ret) % 4); _i++ {
    _ret += '='
  }
  // 第三引数が無ければ文字列をそのまま返す
  if _outputfilename == '' {
    _ret
    return
  }
  // 第三引数にファイルパスがあればそれに書き出す
  _filename = _outputfilename
  FCHARSET('UTF-8')
  if !FOPEN(_filename, 'w') {
    return
  }
  for _i = 0; _i <= (STRLEN(_ret) / 76); _i++ {
    _str = SUBSTR(_ret, _i * 76, 76)
    FWRITE(_filename, _str)
  }
  FCLOSE(_filename)
}

sys.fnc.size8
{
  _str = _argv[0]
  _ret = ''
  if STRLEN(_str) > 8 {
    _ret = SUBSTR(_str, -8, 8)
  }
  else {
    _ret = _str
    for _i = 0; _i < (8 - STRLEN(_str)); _i++ {
      _ret = '0' + _ret
    }
  }
  _ret
}

1バイトずつ読み込んでいるのでCHRCODEの戻り値は0x??になると思っていたのですが、何故か0xFF??という0xFF00がくっついたデータも存在しました。何ですかこれは。
関数sys.fnc.size8で、なんとなく0xFF00を取り除いてみたところ正しくエンコードできたのですが、きちんと理解できずに何となく動くからそうする、というのは気持ち悪いですね。
YAYAのソースを覗いてみたところintegerにキャストしてるっぽいところまではわかりましたがどうして0xFF00がくっついてくるのかはわかりません。どなたかわかる方いらっしゃいましたら教えて頂けるとありがたいです。