カカリアスタジオブログ

Happy Elementsのゲームブランド「カカリアスタジオ」の公式ブログです。

ソーシャルゲームの不正課金。まさかそんな理由で増えるとは

プランナーのアレックスです。
ソーシャルゲームの運営には、ある程度の不正課金はつきものだと考えています。しかし、今年の1月に、通常の何十倍もの金額の不正課金が発生したので調査したところ、不正の方法と、それが1月に集中した以外な理由がわかったのでまとめてみました。

不正課金の方法

一言で言えば、期限切れなどのクレジットカードを使って課金がされています。ほとんどの場合は決済が通らないはずですが、プラットフォームによっては通ってしまう場合があるようです。
あまり詳しいことはわかりませんが、このような流れであるという説明を受けました:

  1. ユーザーがプラットフォーム側で決済を開始
  2. すぐに結果を返すために、プラットフォームが簡単なチェックでカード情報を確認
  3. チェックが通ると、アプリサーバーに通信しアプリ内アイテムを付与
  4. 後ほどプラットフォームがもう一度しっかりカード情報を確認。この時点で決済に失敗
  5. カードの決済はキャンセルされて、支払いは行われない。しかしアイテムは付与済み

という感じで、アプリ側としては損をしてしまいますね。
さらに、このやり口のノウハウを持っている人がユーザーの代わりに不正課金を行うサービスを提供しているようです。中国のECサイトTaobaoなどで検索すると、数々の有名アプリでの代理課金サービスが宣伝されていることがわかります。
ここで肝となるのが機種変更パスワード

機種変更パスワード

ソーシャルアプリでは通常、新しいスマホを買ったユーザーがデータを以降できるように、機種変更パスワードという機能を用意しています。古い機種でパスワードを確認して、新しい機種に打ち込むと同じデータで引き続き遊べる、という仕組みです。
この機種変更パスワードを悪用すると、

  1. 不正課金ができる人と連絡を取る
  2. 機種変更パスワードを不正者に伝える
  3. 不正者が自分の端末にユーザーのデータを引き継いで、不正課金する
  4. ユーザーが機種変更パスワードを再利用して、有料アイテムを獲得したアカウントを取り戻す

バイスのユーザーエージェントやデバイスID、モデル名などをログに残して決済時に通常と違うものに切り替わるアカウントを調査していくと、このような行為を洗い出すことができるかもしれません。

1月に不正課金が増えたわけ

不正の方法がわかったところで、ではなぜ1月にここまで集中して行われたか?
現在弊社のアプリは中国からの利用者もそれなりにいて、中にはかなりやり込んでランキング上位に入ってくれるユーザーも多く、嬉しい限りです。ただ、中国では海外(日本)で使えるクレジットカードを持つ方は少なく、スマホアプリで課金をする場合はギフトカードを店舗で購入して利用することが多いです。
そして1月の中国でのビッグイベントといえば、そう、旧正月です
正月休みに入って、いつもカードを買っているお店が閉まっている。
クレジットカードは海外では使えない。
けど新しいイベントが始まってどうしてもアイテムを買いたい。
なんとかできないか検索してみたら、代わりに課金をしてくれるサービスがあるじゃないか。機種変更パスワードが必要だったりしてちょっと怪しいけど、他に方法が無いし仕方ないからやってみよう。
このような流れで、特に海外からの上位ユーザーに集中して不本意な不正課金が行われた、ということが今回わかりました。

おわり

ユーザー自身には不正課金をしたいという気持ちがなくても他に課金手段がない、という意外な状況があると大変勉強になりました。ユーザーと連絡を取り、おかげ様で状況を理解できましたので、今後も引き続き運営側の体制、及び不正対策なども整えていきたいと考えています。

一緒に働きたい方、絶賛 募集中

京都でスキルアップしたいエンジニアやプランナーの皆さん、ご応募お待ちしています!
社内にはプロジェクターが使えるバースペースがあり、業務後のコミュニケーションの場となっています。

京都でスキルアップしたい学生さん、アルバイトも可能なのでご応募お待ちしています!
オフィスワークでドリンク飲み放題、時給は高く、シフトの自由度も高いです。

大阪、滋賀、神戸から通勤実績あります

新MBP(Mavericks)への開発環境移行について

エンジニアの上田です。
MBAを使っていたのですが、そこからMBPへ開発環境を移行しました。
そこでいくつかつまづいたので解決した経緯をblogに記載しておきます。
同じ状況で困ってる人の参考になればと思います。

移行前PC

Mac Book Air (OSはLion)

移行後PC

Mac Book Pro (OSはMavericks)
購入直後の状態です

移行環境


手順

command line toolsなど他のインストールに影響するものから最初にいれていきます

1. Xcode5インストール

APP Storeからダウンロードしてインストールします
インストール後、一度起動してライセンスに同意しておいてください
同意しておかないとHomebrewインストールで失敗します

2. command line toolsインストール

xcode-select --install

Command Line Developer Toolsをインストールする確認ダイアログが出るので
Installボタンを押してインストールします

他のアプリなどを入れたりした後だとなぜかxcode-select --installを入力しても
Command Line Developer Toolsインストールダイアログが立ち上がりませんでした
Xcode5をインストール直後に実行しておいたほうが良いです

3. Homebrewインストール

ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)

インストール完了後

brew doctor

と入力し

Your system is ready to brew.

と表示されることを確認します

4. apple-gccインストール

Xcode4からコンパイラLLVMになりました
互換性などの問題によりLLVMではCassandraのコンパイル後、起動時にAbort trap: 6というエラーがでます

brew tap homebrew/dupes
brew install apple-gcc42
sudo ln -snf /usr/local/bin/gcc-4.2 /usr/bin/gcc 

と入力してインストールします

5. JDK6インストール

http://qiita.com/m_mysht/items/a5e60d260c7078331d66
このサイトの内容を参考にインストールさせていただきました
JDK6はOracleからは入手できないのでAppleのサイトからインストールします

6. ruby 1.9.3インストール

RVMでインストールします

sudo curl -L https://get.rvm.io | bash -s stable --ruby
sudo rvm install 1.9.3
source .bash_profile
rvm use 1.9.3 —default


7. memcachedインストール

brew install libevent
brew install memcached


8. Cassandraインストール

通常はhttp://cassandra.apache.org/download/からダウンロードしますが、入れようと思っていたバージョンの1.1.6は既にダウンロードページになかったのでhttp://archive.apache.org/dist/cassandra/1.1.6/からダウンロードしました。
apache-cassandra-1.1.6-bin.tar.gzを解凍して自分の分かりやすい場所に置きます。

brew install libevent
brew install memcached


9. MySQL5.5インストール

http://dev.mysql.com/downloads/mysql/
上記サイトから64bitのDMG Archiveをダウンロードしてインストールします

10. Redisインストール

brew install redis


開発環境移行は以上で完了です!!
だいたい3時間くらいで完了できます

開発環境移行前は2年くらい前に買ったMBAを使っていたのですが、Unityなどを使用してアプリ開発をしていると使用メモリも多く
だいぶ効率が落ちてきていると感じました。

そこで会社にお願いして新しいMBPを買ってもらいました。
退職理由は「転職先のモニターのほうが大きい」から?でも書かれていましたが、自分が働いている会社が「エンジニアが一番パフォーマンスを発揮できる環境を整えてくれる会社である」ということはとても重要であると考えます。
そんなエンジニアの気持ちを汲み取ってくれるHappy Elements株式会社で私たちと一緒に世間をあっと言わせるような面白いアプリを作ってみませんか?

Happy Elements株式会社は一緒に働く仲間を募集中です!

詳細な情報は以下のサイトをご覧ください。


Wantedly
https://www.wantedly.com/projects/3743

Happy Elements株式会社 JOBS
http://www.happyelements.co.jp/index.php/category/jobs/kyoto-office

世界平和とAndroid

エンジニアの草苅です。
スマートフォンを扱うエンジニアの皆さんは、日々Android のバグに悩まされているのではないかと思います。弊社も類に漏れず様々な Android のバグと戦っています。

特にあんさんぶるガールズ!ではアニメーションはすべて Canvas を利用していることもあり、AndroidCanvas 絡みのバグに、頭を痛めています。

Androidバッドノウハウは悩んでいる人みんなで共有した方が、世のため人のためになるのではと思い立ったので、世界平和を願っていくつかまとめてみたいと思います。

1. GPUレンダリングの設定によって Canvas で不具合が発生する

Android は OS のバージョンや、WebView のレンダリングエンジンの違いによって、GPUレンダリングOFFの場合に、Canvas が正常に表示できない端末、もしくはGPUレンダリングONの場合に、Canvas が正常に表示できない端末が存在します。


このような問題は、できれば Android OS のバージョンで統一しておいて欲しいのですが、どうも機種依存のようなので、弊社では機種に応じて GPUレンダリングONなのか、OFFなのかを個別で指定しています。

いくつか実例を挙げますと、現在のドコモのツートップ Xperia A と Galaxy S4は GPU レンダリングOFFでなければ WebView 上の Canvas は正常動作しません。それとは逆に、Nexus 7 や Arrows X は GPUレンダリングON でなければ WebView 上の Canvas は正しく表示されません。また、同じ OS のバージョンであっても Galaxy Nexus はGPUレンダリングONだとクラッシュし、Nexus 7GPUレンダリングONでなければ Canvas が正常に表示されないということが発生します。

そのため、あんさんぶるガールズ!では、ONまたはOFFでしか動作しない端末については、次のようなメソッドを使って、アプリ側で強制的にGPUレンダリング設定を変更しています。


protected void setHardwareAccelerationEnabled(boolean enable) {
  if (enable) {
    getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  } else {
    // 下位互換性を保つため、以下のコードをリフレクションで実行
    // webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    try {
      Method setLayerTypeMethod = webView.getClass().getMethod("setLayerType", new Class[] {int.class, Paint.class});
      setLayerTypeMethod.invoke(webView, new Object[] {View.LAYER_TYPE_SOFTWARE, null});
    } catch (NoSuchMethodException e) {
      // Older OS, no HW acceleration anyway
    } catch (IllegalArgumentException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
    } catch (InvocationTargetException e) {
      e.printStackTrace();
    }
  }
}



2. GPUレンダリング設定以外に Canvas 周りのバグがある

Android 4.0.4 には Canvas がブラウザを巻き込んでクラッシュするというバグがあります。このバグは上記のバグと違い、WebView では再現しにくいのですが、ブラウザではかなりの頻度で発生します。


例えば Galaxy S3、Xperia SXXperia GX などの端末が Android 4.0.4 を搭載しています。CreateJS のコミュニティにもこの問題は上がっており、かなりの期間対処法がなかったのですが、その後、Canvas を clearRect する際、縦横に 1px だけ加えたサイズをクリアするようにすることでクラッシュしなくなるという、華麗なテクニックが編み出され、この方法が EaselJS 0.6 系に取り込まれました。

CreateJS のコミュニティの該当エントリー
http://community.createjs.com/discussions/easeljs/220-samsung-galaxy-s3-stock-android-404-browser-freezescrashes-on-stageupdate

修正のコミットログ
https://github.com/CreateJS/EaselJS/commit/7c02f0d4a7e50908b284623d23e6897f15e3bff4


また、Galaxy S4(Android 4.2.2)に関しては WebView 上で表示させたときだけ、Canvas が正しく表示されない不具合があります。
GPUレンダリングONの場合は、Canvas 全体が常に水色表示で何も出ず、OFFの場合はアニメーションが表示されたり、されなかったりがかなり不安定になってしまいます。もろもろ調べていると、日本語で次のようなブログ記事を見つけました。

Galaxy S4のWebviewで、非同期処理の中でのCanvasの描画がバグってる - 車輪を再発明 / koba04の日記
http://d.hatena.ne.jp/koba04/20130629/1372437341

この記事を見ながら「最新端末で動作しないアプリって…」と絶望していたのですが、諦めず調べていると安定してアニメーションを表示できるスピリチュアルな方法を、編み出すことができました。それはアニメーション表示直前に Canvas をクリアすることです。CreateJS であれば、Stage を作って、まずクリアしてからアニメーションを実行すると WebView 上の Canvas でも安定してアニメーションを表示することができました。

var canvas = document.getElementById('canvas');
var stage = new createjs.Stage(canvas);
stage.clear();
// clear した後アニメーションのコードを実行


3. GPUレンダリングOFFで特定のCSSが激重

さて、Canvas を正常に表示するためにGPUレンダリングを強制OFFにしたまでは良かったのですが、それに伴いスクロールが激重になってしまった端末があります。


例えば、今年の初めに発売されてツートップの発売前までドコモが推していた Xperia Z という端末は、WebView 上の Canvas を正常に表示するために GPUレンダリングを強制OFFにしたまでは良かったのですが、ほとんどのページでスクロールが激重になってしまいました。

いろいろ調べていると、shadow 系などアルファブレンディングを行うような cssGPUレンダリングOFF だと重くなってしまうようです。参考までに以下のような記事があります。

[CSS] border-radiusとbox-shadowを組み合わせると、それぞれ単体でのスタイルより5倍重たい!? - YoheiM .NET
http://yoheim.net/blog.php?q=20130713

単純に考えると、GPUレンダリングOFFの端末のみ、これらの css の重いプロパティを使用しない css で上書くというのが良さそうです。

Android には幸いなことに、Java Object を JavaScript Object として WebView に渡せるという、セキュリティ問題がとても発生しやすい、さわやかな機能があるので、これを利用します。そして、css を読み込む際、この WebView がGPUレンダリングOFFであれば、上書きする css を読み込むようにしました(以下は簡易的に書いたサンプルです)。

// this を droid という名前で JavaScript からアクセスできるようにします
webView.addJavascriptInterface(this, "droid");


// GPUレンダリングOFFのときのみ、上書き用のCSSを読み込みます。
if (!droid.webView.isHardwareAccelerated()) {
  var fileref = document.createElement("link");
  fileref.setAttribute("rel", "stylesheet");
  fileref.setAttribute("type", "text/css");
  fileref.setAttribute("href", '<%= path_to_stylesheet 'android_without_hardware_acceleration' %>');
  document.getElementsByTagName("head")[0].appendChild(fileref);
}



4. キーボード入力時に上下に揺れる端末

Android にはテキストボックスに文字を入力しようとすると、文字入力のたびになぜか画面がスクロールして、激しく上下に縦揺れするという恐ろしい端末があります。

例えばこちらに不具合が上がっています。
https://github.com/scottjehl/Device-Bugs/issues/32


あんさんぶるガールズ!では当初、コメントはすべてポップアップ画面でその場で入力できるようにしていましたが、この不具合に対応するため、Android はすべて画面遷移でテキストボックスが1つだけある画面に遷移した後、コメントを入力するように変更しました。さらに、テキスト入力中にスクロールが発生しないように、テキストボックスにフォーカスが当たった際、JavaScript でスクロールをなくす処理を入れています(以下はサンプルコードです)。


.stop-scroll {
  outline: none;
  overflow: hidden;
}


$('input[type=text]').on 'focus', (e)->
    $('body').addClass('stop-scroll')

$('input[type=text]').on 'blur', (e)->
    $('body').removeClass('stop-scroll')



おわりに

最後にうちのチームの優秀なアルバイトエンジニアの心の叫びを掲載して、世界平和を切に願いたいと思います。




Happy Elements 株式会社では世界平和を目指すエンジニア社員、およびアルバイトを募集しています。

Find Job!

Wantedly

Happy Elements株式会社 JOBS

redis-objectsがなくてもプロジェクトは回るが

あるととても便利だと思います

はじめに

エンジニアの@ryooo321です。
よろしくお願いします。
ご存知の方も多いかもしれませんが、今回はRubyからRedisを使う際にとても扱いやすいredis-objectsをご紹介したいと思います。

https://github.com/nateware/redis-objects


特徴

・ORMではない

・Redisのデータ型(counter, value, list, hash, set, sorted-set)をサポート

・ロック機能も利用可能



好きなところ

・モデルの任意のプロパティをRedisに保存するような感覚で利用できる点。

・Redisのキー情報をシームレスに管理できる点。

・ARモデルの一部プロパティをRedisに移したりできるので、データストア単位でなく理想の単位でモデルを定義できる点。


This is not an ORM. People that are wrapping ORM’s around Redis are missing the point.
(readmeより引用)
作者の方はこのように言っており、本当にとても使いやすい形になっております。


目次

1. Counter
2. List
3. Set
4. Hash
5. Sorted Set
6. global
7. Lock
8. redisオブジェクト



1. Counter

数値をアトミックにincrement/decrementする型です。

class User < ActiveRecord::Base
  include Redis::Objects
  counter :friend_count, :start => 0
  # startは省略可
end

@user = User.find(id)

# インクリメント
@user.friend_count.increment

# 渡したブロックで例外発生もしくはnilが帰ったときは、decrementを発行して数値を戻す
@user.friend_count.increment do |new_value|
  raise 'friend count is limited by 20' if 20 < new_value
  true
end

# Userを取得せずにアクセス
friend_count = User.get_counter(:friend_count, user_id)
User.increment_counter(:friend_count, user_id)
Userを取得せずにアクセスできる点、オブジェクト経由で操作する時はuser_idを渡さないでよい点(キーがシームレス)が便利です。



2. List

配列をアトミックに操作できる型です。

class User < ActiveRecord::Base
  include Redis::Objects
  list :friend_ids, :maxlength => 20
  # startは省略可
end

@user = User.find(id)

# rubyの配列のように使えます(Enumerable)
# 追加
@user.friend_ids << 123
@user.friend_ids.push(123)

# 変更
@user.friend_ids[2] = 123

# 削除
@user.friend_ids.delete(123)

# 取得
@user.friend_ids[2]
@user.friend_ids[2..4]
@user.friend_ids.at(2)
@user.friend_ids.first
@user.friend_ids.each do |user_id|
  # hoge
end
@user.friend_ids.values

Userを取得せずに操作するメソッドは現在ありません。

しかし、⬇⬇⬇このようにしてUserオブジェクトなしにアクセスできます。

name = :friend_ids
friend_ids = Redis::List.new(User.redis_field_key(name, user_id), User.redis, User.redis_objects[name])
friend_ids.values



3. Set

重複無効な配列をアトミックに操作できる型です。

class User < ActiveRecord::Base
  include Redis::Objects
  set :friend_ids
end

@user = User.find(id)

# rubyの配列のように使えます(Enumerable)
@user.friend_ids << 123
@user.friend_ids.each do |user_id|
  # hoge
end

# setなので、重複したものは無視
@user.friend_ids << 123
@user.friend_ids << 123 # 2回目は無視
@user.friend_ids.member?(me.id)

# 共通の友達
@user.friend_ids & me.friend_ids

# どちらかの友達
@user.friend_ids | me.friend_ids
@user.friend_ids + me.friend_ids

# 共通でない友達(差分)
@user.friend_ids ^ me.friend_ids
@user.friend_ids - me.friend_ids

# Userを取得せずに操作するメソッドは現在ありませんが、List同様(前述)に取得できます



4. Hash

ハッシュをアトミックに操作できる型です。

class User < ActiveRecord::Base
  include Redis::Objects
  hash_key :item_count_map
end

@user = User.find(id)

# rubyのHashのように使えます(Enumerable)
@user.item_count_map[item_id] = 10
@user.item_count_map.each do |item_id, count|
  # hoge
end

# まとめて操作
@user.item_count_map.bulk_set(:a => 10, :b => 20, :c => 30)
@user.item_count_map.bulk_get(:a, :b)
# => {:a => 10, :b => 20}
@user.item_count_map.bulk_values(:a, :b)
# => [10, 20]

# 個別にインクリメント
@user.item_count_map.incr(:a, 50)
@user.item_count_map[:a]
# => 60

# Userを取得せずに操作するメソッドは現在ありませんが、List同様(前述)に取得できます



5. Sorted Set

ソート済みハッシュをアトミックに操作できる型です。

class User < ActiveRecord::Base
  include Redis::Objects
  sorted_set :article_rate
end

@user = User.find(id)

# articleごとに評価スコアを登録
@user.article_rate[:a] = 30
@user.article_rate[:b] = 50
@user.article_rate[:c] = 10

@user.article_rate.score(:c)
# => 10

# 順位
# 昇順
@user.article_rate.rank(:b)
# => 2

# 降順
@user.article_rate.revrank(:b)
# => 0

# スコアの範囲取得
@user.article_rate.rangebyscore(0, 100, :limit => 1)
# => [:c]
@user.article_rate.members(:with_scores => true)
# => [[:c, 10], [:a, 30], [:b, 50]]

# atomicなインクリメント
@user.article_rate.incr(:c, 100)
@user.article_rate.score(:c)
# => 110

# Userを取得せずに操作するメソッドは現在ありませんが、List同様(前述)に取得できます



6. global

すべてのデータ型クラスでglobalオプションが使えます。

trueを指定すると、クラス単位でredisキーが共通になります。

※ 同モデルのオブジェクトすべてが、同じキャッシュを見るような状態です。

class User < ActiveRecord::Base
  include Redis::Objects
  sorted_set :article_rate
  sorted_set :event_point, :global => true
end

# これは@userごとのランキング
@user.article_rate.rank(:a)

# これは全ユーザーのランキング
@user.event_point.rank(:a)



7. Lock

lockの実装としてはredisにフラグ値をsetし、そのフラグがある場合は処理を待ち合わせる作りです。

同じhoge_lockを使っている箇所とで排他制御にできます。

class User < ActiveRecord::Base
  include Redis::Objects
  lock :hoge, :expiration => 20.second, :timeout => 1.second
  
  # lockのオプション
  # timeout :
  #    指定された時間以上にロック解除を待った場合、例外を投げます。
  # expiration :
  #    万が一ロックが解除されない状態になっても、指定された時間が経つとredis側でロック解除します。
end

@user = User.find(id)

# 処理Aと処理Bは排他的に動く
@user.hoge_lock.lock do
  # 処理A
end
@user.hoge_lock.lock do
  # 処理B
end



8. redisオブジェクト

redisオブジェクトはredis gemのオブジェクトで、redis-objectsで未実装のAPIもredisオブジェクト経由で呼び出せる場合があります。

class User < ActiveRecord::Base
  include Redis::Objects
  value :hoge
end

@user = User.find(id)

# redisオブジェクトへは、下記のようにアクセスできます。
redis = User.redis
redis = @user.redis

redis.pipelined do
  redis.set "foo", "bar"
  redis.incr "baz"
end



おわりに

本稿にお付き合い下さいましてありがとうございました。

この素晴らしいプロダクトを、みなさまが少しでもよいと思っていただければ幸いです。



一緒に働きたい方、絶賛 募集中

京都でスキルアップしたいエンジニアの皆さん、ご応募お待ちしています!
社内にはプロジェクターが使えるバースペースがあり、業務後のコミュニケーションの場となっています。

京都でスキルアップしたい学生さん、アルバイトも可能なのでご応募お待ちしています!
オフィスワークでドリンク飲み放題、時給は高く、シフトの自由度も高いです。

大阪、滋賀、神戸から通勤実績あります
イラストレーターさん、シナリオライターさんも募集中(アルバイト可)です!


© Happy Elements K.K