blog.syfm

徒然なるままに考えていることなどを書いていくブログ

 なかったのでグルコスのマイページの情報を取得するライブラリを書いた

概要

BEMANI音ゲーの一つである SDVX や弐寺Jubeat なんかは非公式でもスコアデータを取得するアプリなりライブラリがある。そのどれもが根強い人気を誇っているのは多分音ゲーをやっている/やっていた人ならばわかるはず。
Groove Coaster (グルコス) は BEMANI とかチュウニズムに比べてあんまり人気がない…。(自分は BEMANI より好きです)
当然ノバでも同じで、グルコスで並んだことは恐らく 3 回くらいしかない。
ということはもちろんアプリもライブラリもないので、作るしかないと思ってライブラリを書いた。

github.com

グルコスのマイページの通信を見てる感じだと、XHR でデータを取得している感じで、通常のページ表示に必要な XHR リクエストの数からして、API をふつうに利用する限りではまったく負荷はかからない。
使い方は README.md か GoDoc を見れば大体わかるはず。

サンプル

例えばすべての楽曲のサマリを取得する MusicSummary() を利用すると、

package main

import (
    "fmt"

    "github.com/lycoris0731/go-groovecoaster/groovecoaster"
)

func main() {
    client := groovecoaster.New()

    summary, err := client.MusicSummary()
    if err != nil {
        fmt.Println(err)
        return
    }

    for _, music := range summary {
        fmt.Printf("%#v\n", music)
    }
}

総楽曲数は結構多いので、先頭から幾つかを見ると、

&groovecoaster.MusicSummary{ID:161, Title:"東方不死鳥", PlayCount:40, LastPlayTime:"2016-11-24 21:14:14"}
&groovecoaster.MusicSummary{ID:365, Title:"東方地神律", PlayCount:4, LastPlayTime:"2016-11-24 21:14:14"}
&groovecoaster.MusicSummary{ID:298, Title:"FUJIN Rumble", PlayCount:52, LastPlayTime:"2016-11-24 21:02:02"}
&groovecoaster.MusicSummary{ID:43, Title:"グルーヴ・リボルバー", PlayCount:19, LastPlayTime:"2016-11-24 21:02:02"}
&groovecoaster.MusicSummary{ID:192, Title:"OMAKENO Stroke", PlayCount:17, LastPlayTime:"2016-11-24 21:02:02"}
&groovecoaster.MusicSummary{ID:49, Title:"Got a pain cover?", PlayCount:53, LastPlayTime:"2016-11-24 20:49:04"}
&groovecoaster.MusicSummary{ID:508, Title:"ハレ晴レユカイ", PlayCount:1, LastPlayTime:"2016-11-24 20:49:04"}

こんな感じに取得できる。

所感

しんどい

このライブラリを作る以前にも良くグルコスのマイページのリクエストやソースは見ていたけども、タイトー or 受注している下請けはエンジニアが足りてないんじゃないかと思ってくる… 。
使われる場所はまとまってはいるけど、JSONXML の 2 つが使われていたり、JSON のキーに flag とあったのに、別のリクエストでは flg といった表記をしていたり、真偽値を返すべきところが 01 で表現されていたり、あと個人的には店舗名を tenpo_name とするのはどうしても受け付けがたい…。
あとは、v2.0 で既に廃止されたレベルが JSON に残っていたり。結構挙げるときりがない。
そのあたりのバラバラな要素をできるだけ吸収して統一するようにがんばった。
なんだかんだで Go を書くいい練習になった (気がする)

とにかくグルコスにかぎらずタイトーの Web サイトは色々おかしい部分があるので探してみるのも良いかもしれない。

イラストを Git で管理したかったのでツールをつくった

イラストの管理

自分はたまにイラストを描いたりするのですが、以前からその管理方法に苦労していました。
苦労していた点は主に次の 2 点です。

  • バックアップ
  • 制作過程 Gif をつくるのが面倒くさい

強い人は、短時間でもさらっとイラストを描いてしまいますが、自分は時間をものすごく掛けないとまともなものが描けないので、バックアップは結構頻繁に取ります。
手動でバックアップしようとした場合は、ふつうにファイルを複製する感じになると思います。
ただ、普段からコードを書いていて VCS を利用している身だと、どうしても原始時代かと錯覚してしまいます。
さらに、PhotoShop の psd 形式や CLIP STUDIO PAINT の標準である clip 形式はいろんなデータが詰め込んであるので 1 ファイル当たり平気で 50 MB くらい持って行かれます。これも結構厳しいところです。

VCS を使う場合の不便な点

そこで 1 年くらい前から Git などの VCS で管理できないかなぁと思うようになりました。
Git を使うことでイラストファイルの差分は管理できるようになります。
ただ、コミット時点でのイラストがどんなものだったか確認することはできません(できるけど面倒くさい)。
あくまで Git でバイナリの差分を管理しているだけです。

Clip

そこでこんなものを作りました。 今回は自分がイラストを描くときに使っている CLIP STUDIO PAINT の clip 形式ファイルを管理しようと思ったので、clip 形式に絞っています。

github.com

Git の hook にちょっと設定を追加して、コミットの度にその時点のイラストを自動エクスポートする。
差分を見たいときにはコミットハッシュに紐付いたイラストを表示させる感じ。

仕組み

clip ファイルは何かしらのメタデータ(ちゃんとは確認していない)と SQLite データベースがくっついているので、まず SQLite データベースのマジックナンバーを探して、そこから末端までを取り出しています。
データベースには CanvasPreview なるテーブルがあり、そのうちの ImageData カラムに画像データが blob で格納されているので、取り出すことで画像を生成しています。
最初は Python3 で書いたのですが、あまりにも動作が遅すぎたので、Go で書くことにしました。
Go を書くのは初めてだったのですが、ものすごく書きやすくてすごい(KONAMI)と思いました。
以下が同じ処理の Python3 と Go による比較です。

Python3
f:id:ktr_0731:20161025000126p:plain

Go
f:id:ktr_0731:20161025000135p:plain

Python3 より 132 倍高速になりました。ハンパねぇ…。さすがコンパイラ言語…。

デモ

ちょっと分かりづらいですが、以下のような感じで使えます。
コミットハッシュ値を引数に与えてやることでそれに対応するイラストを表示します。
Demo

思ったこと

.clip 以下にコミット毎の画像が入るので、ImageMagick なんかを使うことでいちいち画像形式でエクスポートしなくても簡単に制作過程 Gif を作ることができます。
ただ、いくつか欲しい機能を実装していないので追々やっていきます〜。
リポジトリの容量については 1 ファイルあたり 1 GB が GitHub での限界だったと思うので 1 リポジトリ 1 イラストを意識していれば特に問題はないかと思います。

追記(2016年10月26日)

思ってもいない反響があって驚いています…。ありがとうございます。
ただ、ちょっと重要なことを忘れていました。
Git は差分を管理するわけではなくて実際にはファイルをまるごと保存するような形で管理しているので、コミットする度に容量は増加します。
とはいえ、 git gc コマンドによって容量の削減ができるはずです。
LFS を利用することでいい感じにできそうなので機能追加を行いたいと思います!ご意見ありがとうございました!

追記(2016年10月27日)

clip ファイルの管理には Git LFS を利用することでリポジトリの肥大化を抑えることができるので、LFS の利用を推奨します。
詳しくはこちら↓

git-lfs.github.com

Aizu CTF #2 に参加してきました

先週土曜日に 会津Webセキュリティ勉強会 a.k.a Aizu CTF #2 に初心者 CTF チーム(仮)の wabisabi のバイナリ担当、暗号担当と共に参加してきました。
見て分かる通り、今回で 2 回目の開催らしいですが自分は初参加でした。

前半は Web セキュリティの基礎知識についての講義、後半は実践ということで Attack & Defense 型の CTF が行われました。
Web 講義については、SQLi や XSSCSRF などの基礎を解説していました。個人的には Time based SQLi や SSRF を初めて知ったので勉強になりました。
CTF については、なんと個人戦でした。(初めてなのに個人戦だったので不安になりました)
各チームにサーバが与えられていて、Web 特化の A&D だったので 80 番以外のポートへ攻撃することはできないという形式です。
それぞれの Web アプリケーションを稼働させながら脆弱な実装を探して攻撃、防御する…という流れでした。

とりあえず攻撃は厳しそうだから…という理由で、まずはソースコードリーディングと防御に回ることにしました。最重要な部分はデータベースと config ファイルだろう、と踏んで、MySQL のパスワードを変更し、config ファイルを修正しました。

#!/bin/usr/env python3


MYSQL_DATABASE_USER = 'root'
MYSQL_DATABASE_DB = 'aizuctf2'
MYSQL_DATABASE_PASSWORD = 'UfndLfo9'
MYSQL_DATABASE_HOST = 'localhost'
SECRET_KEY = 'secret_key'
SESSION_COOKIE_HTTPONLY=False

#APP_HOST = '0.0.0.0'
APP_HOST = '127.0.0.1'
APP_PORT = 8888
DEBUG = True

SALT = 'erutaso'

MySQL のパスワードは変更後ですが、それ以外はこんな感じでした。なので MySQL のパスワードと SECRET_KEY を変更し、SESSION_COOKIE_HTTPONLYTrue にしました。
ついでに salt がえるたそ~だったので、

SALT = 'makisekurisu'

に変更しました。 少しソースを読んでみると、Session にユーザ名とかパスワード、管理者権限なんかをそのまま入れている感じだったので、SECRET_KEY をデフォルトにしているチームのアプリケーションならば、Cookie 改竄してなりすませるのでは…と考えました。
そこで、一旦 SECRET_KEY をデフォルトに戻して自分のアプリケーションに管理者としてログインし、その Cookie を取得しました。
他のチームのアプリケーションに適当なアカウントでログインした後、その Cookie を改竄してみると期待通り管理者と見なされてたので、心で「シャァァッ!!!」と叫びました。
また、少し時間が経つと、SLA のテストとしてユーザ登録や投稿が自動で行われるのですが、その一部に FLAG が紛れていて管理者のみ見れるような状態だったので、この方法でほぼ全員の FLAG を持ってくることができました。 手動はめんどくさいな…と思い、ごく簡単なシェルスクリプトを書いたのですが、一部のチームが XSS に襲われていて、無限ループに陥っているアプリケーションへのアクセスを回避するいい方法が思い浮かばなくて半自動、半手動でスクリプトをうごかしていました。シェルスクリプト力がほしい…。
意外と結構なチームが SECRET_KEY を最後まで変更していなかったので、定期的に FLAG を収集できるような感じでした。

Defense の方は、ソースコードを改修しようと思っていたのですが、その変更によって気づかぬうちにアプリケーションがエラーを吐いて SLA が激減したことが度々合ったので、とりあえず SLA を保持するかということで保守(?)に回りました。
サーバに Git が入ってたので、アプリケーションをリポジトリ化してバックアップみたいに使っていたら結構はかどりました。

途中までは SLA も順調に稼げていていい感じだったのですが、後半くらいになぜか画像が表示されない(というかデータベースからレコードを取得できない)という謎のエラーに悩まされて、解決を試みていましたが結局わからずじまい…。
結果として、SLA を下げてしまい、最終的な順位は6位でした。
f:id:ktr_0731:20161003221346p:plain
攻撃と防御を同時にやろうとすると混乱してどっちも死ぬので、その切替が大変でした。
初めての A&D で、どこから手を付けていいかわからないような感じでしたが、それなりに振る舞えたので良かったです。
運営の皆様、楽しい場所を提供してくださり、ありがとうございました!