Mechanizeでソシャゲーにログインしよう

ソシャゲーやってると、機能不足を感じるんですよ。
例えば、持ってるアイテムの一覧とか、レベルアップ可能な一覧とか。
そういう、支援が欲しいんですよ。

今回は、某ソシャゲーに、Mechanizeでログインするところまでやろうと思います。

cookieを使ってログイン

んで、ログイン時の画像認証を回避するために、chromeCookieを使います。

takuya-1st.hatenablog.jp

ただ、この記事にも記載がありますが、chromeCookieは暗号化されるようになりました。

cookieの復号

色々検索してたら、Win32APIのCryptUnprotectData()を使うことで、復号できるそうです。
ってことで、書いてみました。

require 'fiddle/import'
require 'fiddle/types'

module WinAPI
  module Win32
    extend Fiddle::Importer
    dlload 'kernel32.dll', 'crypt32.dll'
    include Fiddle::BasicTypes
    include Fiddle::Win32Types

    extern 'long LocalFree(void*)'
    extern 'BOOL CryptUnprotectData(DATA_BLOB*, LPWSTR*, DATA_BLOB*, PVOID, CRYPTPROTECT_PROMPTSTRUCT*, DWORD, DATA_BLOB*)'

    DATA_BLOB = struct(['DWORD size', 'void* data'])
  end

  NULL = 0x00
  CRYPTPROTECT_UI_FORBIDDEN = 0x01

  def crypt_unprotect_data (val)
    data_in = Win32::DATA_BLOB.malloc
    data_in.size = val.bytesize
    data_in.data = val

    data_out = Win32::DATA_BLOB.malloc
    success = Win32::CryptUnprotectData(data_in, NULL, NULL, NULL, NULL,
                                        CRYPTPROTECT_UI_FORBIDDEN, data_out)
    unless success
      raise Exception('Failed to decrypt data');
    end

    unprotect_data = data_out.data.to_str(data_out.size)
    Win32::LocalFree(data_out.data)

    unprotect_data
  end

  module_function :crypt_unprotect_data
end

それと、拡張

module MechanizeExtension
  refine Mechanize do
    def load_chrome_cookie (cookie_domain, cookie_path='/')
      path_to_chrome_cookies =
        Object::File.expand_path('AppData/Local/Google/Chrome/User Data/Default/Cookies', ENV['USERPROFILE'])

      unless File.exists? path_to_chrome_cookies
        raise "Not found Chrome cookie database file."
      end

      load_cookies = {}
      db = nil

      begin
        require 'sqlite3'
        db = SQLite3::Database.new(path_to_chrome_cookies)
        db.results_as_hash = true

        sql = "select * from cookies" +
              " where host_key like \"#{cookie_domain}\"" + 
              " and path = \"#{cookie_path}\""

        time_from_utc = ->(utc){ Time.at(Time.utc(1601,1,1,0,0,0,0).to_i, utc) }

        db.execute(sql) do |r|
          r['creation_utc'] = time_from_utc.call(r['creation_utc'])
          r['expires_utc']  = time_from_utc.call(r['expires_utc'])

          if r['value'].nil? or r['value'].empty?
            r['value'] = WinAPI::crypt_unprotect_data(r['encrypted_value'])
          end

          load_cookies[r['name']] = r
        end
      rescue => err
        p err
      ensure
        db.close unless db.nil?
      end

      load_cookies.each do |name, param|
        cookie_params = {
          name:       name ,
          value:      param['value'] ,
          domain:     cookie_domain ,
          expires:    param['expires_utc'] ,
          created_at: param['creation_utc'] ,
          path:       cookie_path ,
        }

        cookie = Mechanize::Cookie.new(cookie_params)

        self.cookie_jar.add(cookie)
      end

      self.cookies.map{|e| e.cookie_value + ';'}.join(' ')
    end
  end
end

使い方

こんな風に使えます。

# extensions
using MechanizeExtension

# configuration
m = Mechanize.new.tap{|x|
  x.user_agent = $config['UserAgent']
  x.load_chrome_cookie($config['CookieDomain'])
}

# get html
html = m.get($config['BaseURL'])


良いソシャゲーライフを! (サーバーに負荷をかけちゃ駄目ですよ)