2016年1月23日土曜日

UnityのTerrainで作った世界にNavMeshを設定してうろつく


まずは、Terrainを使って土地を作ってみます。
これが完成図。

GameObject3D ObjectからTerrainを設置します。

Inspectorの歯車のマークから色々と設定を変更できます。

とりあえず今回マップは狭くていいので、下のほうのResolutionからサイズを変更します。

サイズの設定が終わったら、早速土地の形状を作成していきます。

歯車マーク並びの左端から三つめまでが形状を変化させるツールです。
左から、引き上げor引き下げ、高さ指定で引き上げor引き下げ、起伏を均すという三つのツールです。

どーん。

これだけだと石膏みたいなので、それっぽいテクスチャとかを用意したいところですが極めて面倒です。

そこで、標準で用意されているAssetがあるのでそれを使います。

AssetsImport PackageからEnvironmentを追加します。
※古いバージョンのUnityではTerrain Assetだった模様。

少し時間がかかりますが、インポートが終わったら筆マークを選択し、Edit Texturesからテクスチャを選択して貼っていきます。

何種類かのテクスチャを貼っていくと、絵心ゼロでもとりあえずそれっぽくなります。

次に筆マークの右側の木のマークから、木を生やします。

最後にもう一つ右側の花のようなマークから、草を生やします。

せっかく島っぽくしたので、海も作ってみましょう。

先ほどインポートしたStandard Assetsフォルダの中のEnvironmentフォルダにWaterフォルダがいくつかあるので、その中のPrefabsフォルダから適当に追加します。

追加された板のようなものが水面になるので、サイズと位置を適当に調節します。

ここまでやると、意外とそれっぽくなります。


次に、せっかくなのでAssetストアからUnityちゃんをインポートして歩かせてみます。

経路探索のアルゴリズムとかを一から実装するのはダルすぎるので、NavMeshの設定をします。

まず、TerrainをStaticにします。(デフォルトでなってました)

Inspectorの横当たりにNavigationというタブがあるので開きます。
無ければメニューのWindowからNavigationを選択すると開きます。

Navigationタブを開くと一番下にBakeというボタンがあるので押します。

歩ける部分が青く表示されます。

次にUnityちゃんのほうに、Nav Mesh Agentを追加します。

NavMeshAgentSetDestination()に位置を渡すと移動できるので、左クリックした場所に移動するようにスクリプトを追加します。

UnityちゃんのデフォルトのAnimator Controllerでは、速度によって歩くアニメーションとアイドルが切り替わるようになっていたのですが、面倒なのでbool値のパラメータwalkを追加しました。

RaycastHit hit;
Ray ray;

NavMeshAgent agent;
Animator animator;

void Start () {
 agent = GetComponent<NavMeshAgent>();
 animator = GetComponent<Animator>();
}

void Update () {
 if (Input.GetMouseButton(0)) {
  ray = Camera.main.ScreenPointToRay (Input.mousePosition);
  if (Physics.Raycast (ray, out hit, 1000)) {
   agent.SetDestination (hit.point);
   animator.SetBool ("walk", true);
  }
 }
 if (Vector3.Distance(hit.point, transform.position) < 1.0f) {
  animator.SetBool ("walk", false);
 }
}

ちゃんと障害物を迂回して歩いてくれました。

Read More...

2016年1月10日日曜日

Blender v2.76でアニメーションを作成しUnityへインポートする



前回、Unityへインポート可能なオブジェクトとRigを作ったので、今回はアニメーションを作成してみます。

まず、Blenderで作業します。
Rigを選択したらViewをDope Sheet、ModeをAction Editorに切り替え、Newをクリックします。

適当に名前をつけたら、横にあるFをクリックします。

これで準備は出来たので、3D ViewのPose Modeで走るアニメーションを作っていきます。

関節の角度から作成していくFKモードは少しめんどくさそうですし、自然な動きにならなそうなので、手足などの末端の位置に腕などが追従してくれるIKモードを利用します。

まず足首あたりにくっついているfoot.ik.Lを選択し、Rig Main PropertiesのFK / IKのところを1.0にします。
反対の足も同じように設定します。

Current Frameを1にして1フレーム目から作成していきます。
右側の赤い丸はAutomatic keyframeのオンオフで、オンにすると動かしたものは自動的に記録してくれるのでオンします。


まずは右横からの視点で、両足と腹部にあるtorsoの位置を調節しながら何フレームか作っていきます。
接地する部分はつま先の角度をいい感じに調節します。

ポーズはCtrl+Cでコピーし、Ctrl+Shift+Vで反転して貼り付けることが出来ます。
歩く、走るといった左右の繰り返しのようなアニメーションを作成する場合、一連のサイクルの半分を作成したら、後は前半部分を反転して貼り付けていくと楽です。

そういうアドオンもあるみたいです。

一連の動きが出来たら、次に正面からの視点に切り替えて、膝、足の位置や角度を調節します。
膝の角度は膝の先に浮かんでいるボールの方向を向くようです。

手についても同じように、手首の上あたりにあるhand.ik.Lを選択し、Rig Main PropertiesのFK / IKのところを1.0にします。

反対の手も同じように設定し、足の動きに合わせてポーズを作成していきます。
肘の角度は膝と同じように後ろに浮いているボールで調節します。

出来上がったらfbxでエクスポートします。
Animationを選択してエクスポートすれば動きました。

Unity側にインポートする際は、モデルをインポートしたときと同じく、Unityのインポート設定の部分で、RigのDEFボーンを駆逐する作業が必要です。

Rigの設定が終わったら、Animationのタブから、アニメーション名、開始終了フレームの設定や、Loop有無の設定などを行います。

モデルに反映してみたところ、ぎこちないですが動きました。



Read More...

2016年1月4日月曜日

Blender v2.76で作成したモデルをMecanim用に設定してUnityへインポートする


Blenderのバージョン違いなどにより、出回っているマニュアルっぽいのが役に立たなかったりするので、ハマったところを中心にメモ的に。


まずは適当にモデルデータを作成します。


Rigifyアドオンを有効にした上で、Shift + AからArmatureHuman(Meta-Rig)を選択し、Armatureを追加します。

オブジェクトモードでオブジェクトの位置、サイズをRigに大まかに合わせます。

Locationが0, 0, 0になるように、オブジェクトの原点をShift + Ctrl + Alt + Cで中心に置いた3Dカーソルに合わせます。
Scaleが1, 1, 1になるようにCtrl + AでScaleをリセットします。

ボーンを選択してEditモードに入り、Amature OptionsからX-Axis Mirrorをオンにしたうえでチマチマと位置合わせします。

骨がすべてうまいこと肉に収まったら、Rigify ButtonsGenerateからRigを生成します。


Objectモードに戻ってObjectを選択したあとにRigを追加で選択し、Ctrl + PWith Automatic Weightsを選択します。
この時点でモデルが消えたり、頭だけ残して消えたりするようであれば、User PreferencesFileタブにあるAuto Run Python Scriptsにチェックが入っていないと思われるので、チェックを入れた上で実行します。


これでモデルとRigが連動して変形するようになります。

WGTから始まるボーンが生成されますが、これは不要なようなのですべて削除します。

で、完成したと思ってfbxファイルにエクスポートしてUnityに取り込むと、かなりカオスな感じになります。

そのままだとUnityのMecanimで利用するには適切ではないボーンに紐づいているようで、Mecanim用に修正する必要があるようです。

公式っぽいところを見ると、「肩をDEF-shoulder.RDEF-shoulder.Lにスキニングして、親がORG-ribsであることを確認します。」と出てくるのですが、ORG-ribsなんてのは見つかりません。

どうやら、Blenderのバージョンが新しいとボーンの命名規則が変わっているようで、上記の手順は通用しないようです。

基本的にはDEFから始まるボーンではなく、ORGから始まるボーンを利用するということのようです。
確かに見てみるとDEFから始まるボーンは関節が人間の倍ぐらいあります。ぐにゃぐにゃするわけですね。

で、いくつかやり方はありそうですが、この動画を参考にやってみたところうまくいったので残しておきます。

流れとしてはDEFボーンをすべてORGボーンの子にしてしまう感じです。


まず、Rigを選択して、一番右下と右から3番目のレイヤーを表示します。

EditモードでDEF-hipsを選択し、Shiftを押しながらもう一度選択するとORG-hipsが選択されます。
重なっているので両レイヤーが有効になっていると区別出来なそうです。

それぞれのボーンを選択したら、Ctrl+PKeep Offsetを選択します。
これで後から選択したボーンが親になります。

以下5組のボーンに対して同じ作業で親子関係を正します。

DEF-hips ORG-hips
DEF-spine ORG-spine
DEF-chest ORG-chest
DEF-neck ORG-neck
DEF-head ORG-head


腕、脚、指にもORGボーン一本につきDEFのボーンが二本あります。

DEFボーン二つを選択した後にORGボーンを選択し、同じくKeep Offsetで親子にします。
複数選択した場合は、すべてが最後に選択したボーンの子になるようです。

以下腕4組、脚4組、指10組と結構あります。

DEF-upper_arm.01.R DEF-upper_arm.02.R ORG-upper_arm.R
DEF-forearm.01.R DEF-forearm.02.R ORG-forearm.R

DEF-upper_arm.01.L DEF-upper_arm.02.L ORG-upper_arm.L
DEF-forearm.01.L DEF-forearm.02.L ORG-forearm.L

DEF-thigh.01.R DEF-thigh.02.R ORG-thigh.R
DEF-shin.01.R DEF-shin.02.R ORG-shin.R

DEF-thigh.01.L DEF-thigh.02.L ORG-thigh.L
DEF-shin.01.L DEF-shin.02.L ORG-shin.L

DEF-f_pinky.01.R.01 DEF-f_pinky.01.R.02 ORG-f_pinky.01.R
DEF-f_ring.01.R.01 DEF-f_ring.01.R.02 ORG-f_ring.01.R
DEF-f_middle.01.R.01 DEF-f_middle.01.R.02 ORG-f_middle.01.R
DEF-f_index.01.R.01 DEF-f_index.01.R.02 ORG-f_index.01.R
DEF-thumb.01.R.01 DEF-thumb.01.R.02 ORG-thumb.01.R

DEF-f_pinky.01.L.01 DEF-f_pinky.01.L.02 ORG-f_pinky.01.L
DEF-f_ring.01.L.01 DEF-f_ring.01.L.02 ORG-f_ring.01.L
DEF-f_middle.01.L.01 DEF-f_middle.01.L.02 ORG-f_middle.01.L
DEF-f_index.01.L.01 DEF-f_index.01.L.02 ORG-f_index.01.L
DEF-thumb.01.L.01 DEF-thumb.01.L.02 ORG-thumb.01.L


これでUnityのMecanim用のボーンの設定は完了です。
FBXでエクスポートして動作を確認してみます。

エクスポートする際は、オプションでSelected Objectsをチェックし、Forward-Z ForwardからZ Forwardに変更します。
BlenderとUnityで座標系が異なるので、ここで合わせておくそうです。

ArmatMeshをオンにしてエクスポートします。

吐き出したfbxファイルをUnityに読み込ませ、RigタブのAnimation TypeHumanoidに設定し、Configureをクリックします。

オールグリーンになりますが、あと一歩です。

BodyにあるToesの二箇所が、DEF-toe.LDEF-toe.RでDEFになっているのでORGに変更します。

HeadにあるHeadが、DEF-headになっているのでORG-headに変更します。

Left HandRight HandそれぞれにあるDistalが、すべてDEFになっているのでORGに変更します。
ちなみに、DistalのDEFになっているボーンはすべて02になって重複していたので、03を選択します。

これでDEFの駆逐が完全に完了し、設定終了です。

最後にMecanim Locomotion Starter Kitを使って動かしてみます。

元のモデルがアレなんで微妙ですが、多関節のクリーチャーみたいなのからは脱却出来ました。



Read More...

2015年12月31日木曜日

Swapbotを利用してSpells of Genesisのレアカードを販売してみる


Spells of Genesisのカードを結構集めたのですが、このリスキーかつ流動性の低い資産が、ポートフォリオの結構な比率を占めていることに気づき、若干危機感を感じてきました。

まぁBTCが上げてきてるので、結果的に関連サービスへの投資がBTCのショートポジションに見えてきてしまい、少しBTCに戻しておきたいという意味合いもあります。

CounterpartyのDEXでいくらか捌こうかとも思ったのですが、DEXでの出来高が少ないのと、直接BTCで販売出来ないという問題があり、Swapbotについて調べてみました。
(単にSoGが盛り上がってないだけという説もありますが。)

ということで、今回はSpells of Genesisのカード購入でよく利用するSwapbotで、レアカードの自動販売機を設置してみます。

SwapbotにアクセスしてSwapbot Administrationを選択します。

利用にはTokenlyアカウントが必要なので、Login or Sign Up Nowからログインページを開きます。

Registerから登録ページを開きます。
画面右上にもあります。

Name、Username、Email address、Passwordを入力します。
例にSatoshi Nakamotoが出てくるあたり、Bitcoin業界でのSatoshiのネームバリューの大きさを感じます。

Tokenlyアカウントの登録が完了すると、Swapbotがユーザー情報へのアクセスを求めてくるので許可します。

ダッシュボードが表示されるので、Create a new Swapbotを選択します。

Create a New Swapbotの画面が表示されるので、まずはLook and Feelの部分でBot Name、Bot URL Slug、Bot Descriptionを決めます。
URL SlugはURLの最後の文字列です。

Custom Background Imageなどは、今回はすっ飛ばします。

次にSwaps Selling Tokensの部分で販売方法や価格を決めます。
Swap TypeをSwaps By Rate、Swaps By Fixed Amounts、Swaps By USD Amount paid in BTC or Tokensの3つあります。

Swaps By Rateだと1.2トークン購入など半端な金額が設定出来てしまうので、今回はSwaps By Fixed Amountsを使います。

Swaps Purchasing Tokensは購入用、Advanced Swap Rulesは値引きなどの設定用なので今回は無視します。

なお、一か所で複数のトークンの販売をしようとした場合、受取りの通貨が同じものだと、おすすめしないとワーニングが出ます。

同一トークンの販売で複数通貨の受け入れか、複数トークン販売でそれぞれ受け入れ通貨が別ならOKなようです。

最後にIncome Forwardingの部分で、一定量たまると自動で送金するよう設定出来るので、一応設定しておきます。
GOX怖い。

Other SettingsはConfirmationsに関するものと、ブラックリスト、ホワイトリストに関するものなので、これも無視してSave Botで設定完了です。

Please send a monthly paymentと出てきました。
monthly paymentってなんすか?

なんとなく勝手に、売買が成立した時に手数料徴収するシステムかと思っていましたが、月額利用料を支払ってレンタルするイメージのようです。

でもいくら払えばいいのかどこにも書いてないんすけど。。

で、問い合わせてみたところ一つのbotを借りるのに約7ドル/月とのこと。

ついでに詳細なマニュアルのありかを教えてくれました。
https://docs.google.com/document/d/19jvcg5VPa9BoVo8Y2_2kbmX9QsBEOh6PgyZeeMuxe8k



まぁわざわざ教えてもらっといてアレですが、とりあえずPublic Bot Addressにアクセスするといつもの画面が表示されたので、アクティブにしないまま満足してそっと閉じましたとさ。


Read More...

2015年12月13日日曜日

カスタムROMとGoogleパスワード変更に潜む孔明の罠


Chromaに乗り換えたのでメモ。
ついでにXposedも入れてみた。

Chroma 12-10-2015
http://forum.xda-developers.com/nexus-6/development/rom-chroma-01-11-2015-t3000003

hellsCore b12-M
http://hc-kernel.blogspot.jp/2015/12/n6-b12-m.html

SuperSU 2.62
http://forum.xda-developers.com/showpost.php?p=64161125

Xposed
http://repo.xposed.info/module/de.robv.android.xposed.installer


という記事を書くはずだった。


が、何をやってもwi-fiが繋がらない。
wi-fiは一旦無視してログインしようとしたところ、なぜか認証時にブロックされる。

そして不正なログイン見つけたよ的なメールが届いて、強制的にパスワードを変更させられる。

諦めてROMをカタクリに変更してみたところ、wi-fiは接続成功。
そして相変わらずgoogleの認証が通らない。

Googleに問い合わせてみたところ、「君のアカウントは生きてるよ。良かったね。」みたいな自動返信が返ってくるだけ。

途方に暮れる。

ログイン失敗時に「この端末の所有者のアカウントを利用してログインしてください」と出るので、端末側かもしれないと思い完全に初期化。

さよならすべてのデータ達。
あっ。。バックアップを母艦に移してない。

。。。


「この端末の所有者のアカウントを利用してログインしてください」
「この端末の所有者のアカウントを利用してログインしてください」
「この端末の所有者のアカウントを利用してログインしてください」

あああああああああああああああああああああああああああああああ!!!!!!!!!!!(ブリブリブリブリュリュリュリュリュリュ!!!!!!ブツチチブブブチチチチブリリイリブブブブゥゥゥゥッッッ!!!!!!! )


で、色々調べたところパスワード変更後72時間は端末にログイン出来ない仕様とのこと。

これ何?常識?常識なの?

カスROMインストール⇒ログイン⇒ブロック⇒パスワード強制変更⇒72時間ブロック
いや、なんとも巧妙な罠ですわ。

とりあえず古いバックアップから復元したところ起動は出来た。
が、やはりモバイルからはGoogle系サービスが使えない。

ということで3日間のGoogle断ち生活(モバイルのみ)が始まる。



一応まともに動いたほう(はず)のカタクリの構成もメモしとく。



俺、この3日間の喪が明けたら、カタクリ入れるんだ。。

Read More...

2015年10月16日金曜日

EtereumのGUI Walletを起動してみた



EthereumのGUI WalletをWindowsで起動してみたのでメモ。


以下ページの最下部にダウンロードリンクがあります。

https://github.com/ethereum/mist/releases/tag/0.3.1-beta.1


Ethereum-Wallet-win64-0-3-1.zipをダウンロードして任意のフォルダに解凍。

Ethereum-Wallet.exeといういかにもな実行ファイルがあるので、これだ!と実行。

すると、1からブロックを同期し始めました。。


もともとコマンドラインでgethを使って同期済だったのですが、--datadirオプションでデータディレクトリの場所を変更していたせいか、自動でそっちは読んでくれませんでした。

設定ファイルなどオプションの指定方法を探したのですが見つからず。


先にコマンドラインからgethを起動した状態で再度walletを起動すると、既存のものを自動で検出してくれたものの、立ち上がった画面上では同期出来ていない状態。

色々試した結果、WALLET_FOLDER/resources/nodes/geth配下にあるgeth--datadirオプションを指定して起動し、そのうえでwalletを起動すると、同期済みのものを読みにいってくれました。


アカウントが検出されないときに何度か触ったせいでアカウントが無駄に複数作成されていましたが。。


また、その後もともとのgethを起動してみたり、再度walletに同梱されていたgethを起動してみたりしているうちに、なぜかまた1から再同期し始めました。

coinbaseはあっているので大丈夫なんだと思いますが、なんだか挙動がよく分かりません。


Betaだからなのか、自分がよくわかってないだけなのか。。。

取り合えず使えたのでよしとします。


残高ゼロですし。


Read More...

2015年8月18日火曜日

Counterpartyのトランザクションを解読してみる(Send)


今回もCounterpartyの勉強のために、Counterpartyのトランザクションを読んでみます。

題材は、前回と同じSpells of Genesis関連のトランザクションの中から選択した、トークンの送金を行うSendというトランザクションタイプです。

エンコード方法も前回と同じくmultisigを使ったものを選択しました。

https://www.blockscan.com/txInfo/11635040

  • データの準備
まずはvoutの一つ目のscriptPubKeyから以下を抜き出します。

OP_1
033f2f85f4c4c200d2a6ae3c300b4a1976e86eba833cdc6b96aa653426850723d9
0296a3f26419b9e6cbc794c67c874ec3d0dca008da66427fdbc9fbd45bf24b0249
02076aee3ebdd93955b70ee616de7867aa451a990ad53e0eaad0893626f231721d
OP_3
OP_CHECKMULTISIG

前回と同じくデコードしていきます。

前半2つのダミー公開鍵を、それぞれ前後1byteずつ削って結合させます。
3f2f85f4c4c200d2a6ae3c300b4a1976e86eba833cdc6b96aa65342685072396a3f26419b9e6cbc794c67c874ec3d0dca008da66427fdbc9fbd45bf24b02

これは暗号化されているので、複合化するためにvinの最初のtxidを探してきます。
810a4dd8e0756a715c6248a78c2d29a8c0985841243eb436318a234145847c29

見つけてきたtxidをパスワードとして、ARC4でデコードします。
1c434e5452505254590000000000000000039ff05d000000000bebc200000000000000000000000000000000000000000000000000000000000000000000

  • 不要なデータ?
はじめのbyteは無視します。
1c

  • CNTRPRTY
次の8byteは「CNTRPRTY」という文字列を16進コード文字列に変換したもので、Counterpartyのトランザクションには必ずある決まり文句です。
434e545250525459

  • トランザクションタイプ
次の4byteがトランザクションタイプです。
00000000

0はSENDを指します。

  • Asset_ID
次の8byteはAsset_IDです。
00000000039ff05d

10進数に直すと60813405で、前回と同じように変換します。

60813405 / 26 = 2338977 余り 3 D
2338977 / 26 = 89960 余り 17 R
89960 / 26 = 3460 余り 0 A
3460 / 26 = 133 余り 2 C
133 / 26 = 5 余り 3 D
5 / 26 = 0 余り 5 F

出来上がったDRACDFを反転したものがAsset名になります。

FDCARD

  • Quantity
次の8byteはQuantityです。
000000000bebc200

10進数に直すと200000000で単位がsatoshiなので、2FDCARDになります。


意外とシンプルな構造になっているようです。


Read More...

Counterpartyのトランザクションを解読してみる(Issuance)


Counterpartyの勉強のために、Counterpartyのトランザクションを読んでみます。

今回の題材はSpells of Genesis関連のトランザクションの中から選択した、独自トークンの発行を行うIssuanceというトランザクションタイプです。

エンコード方法は何パターンかあるようなのですが、今回は以下のmultisigを使ったものを選択しました。

https://www.blockscan.com/txInfo/11603601

  • データの準備
まずはvoutの一つ目のscriptPubKeyから以下を抜き出します。

OP_1
02a8d23b56ec9dfad3ffd52f83ac86bef27fc6b11775fec2f682a7d16e31c0272e
03a5998b49075640bd89ff6f0e849b693d99aafbf1145eea9bb2e1f2753fa4e3da
02076aee3ebdd93955b70ee616de7867aa451a990ad53e0eaad0893626f231721d
OP_3
OP_CHECKMULTISIG


2行目から4行目に公開鍵の様なものが3つありますが、この内、最初の2つはダミーで、Counterpartyで利用するデータが格納されています。

2つのダミー公開鍵を、それぞれ前後1byteずつ削って結合させます。
a8d23b56ec9dfad3ffd52f83ac86bef27fc6b11775fec2f682a7d16e31c027a5998b49075640bd89ff6f0e849b693d99aafbf1145eea9bb2e1f2753fa4e3

これは暗号化されているので、複合化するためにvinの最初のtxidを探してきます。
24b10dd846507572b21e15a78c990cf7f993f13ec8623ca738cf9fe1f12ddf99

見つけてきたtxidをパスワードとして、ARC4でデコードします。
これがCounterparty用の生データです。
3d434e5452505254590000001400000000039ff05d00000006fc23ac0001000000000000000000265370656c6c73206f662047656e657369732f4d6f6f6e

  • 不要なデータ?
はじめのbyteは無視します。
3d

  • CNTRPRTY
次の8byteは「CNTRPRTY」という文字列を16進コード文字列に変換したもので、Counterpartyのトランザクションには必ずある決まり文句です。
434e545250525459

  • トランザクションタイプ
次の4byteがトランザクションタイプです。
00000014

0x14=20はISSUANCEを指します。

  • Asset_ID
次の8byteはAsset_IDです。
00000000039ff05d

10進数に直すと60813405で、これが1ならXCPで、1ではない場合は以下のように変換します。

まず、AからZまでが入った配列を用意し、ここからindexが0ならA、1ならBという具合にアルファベットを取り出します。

IDを26で割った余りがindex、余りを切り捨てた整数値が次の計算用データとなり、次の計算用データが0になるまで繰り返します。

60813405 / 26 = 2338977 余り 3 D
2338977 / 26 = 89960 余り 17 R
89960 / 26 = 3460 余り 0 A
3460 / 26 = 133 余り 2 C
133 / 26 = 5 余り 3 D
5 / 26 = 0 余り 5 F

出来上がったDRACDFを反転したものがAsset名になります。

FDCARD

  • Quantity
次の8byteはQuantityです。
00000006fc23ac00

10進数に直すと30000000000で単位がsatoshiなので、300FDCARDになります。

  • Description
Quantityの後ろには簡単な説明文的なものを埋め込めるようです。
01000000000000000000265370656c6c73206f662047656e657369732f4d6f6f6e

内、頭の11byteは文字数などが入っているようなので、それ以降を文字列に変換します。
Spells of Genesis/Moon

なお、文字数が足りないときはvoutを2つ持たせて、追加のDescriptionデータに使えるようです。

OP_1 038dd23b56ec9dfad3ffb24ea3dbe7cc965fa3419508b88eb2c75bf2c231c127a6 02a5998b49075640bdafac1f6be8f71a1df6ccdbb671308fe8db92dd3850cb8d4f 02076aee3ebdd93955b70ee616de7867aa451a990ad53e0eaad0893626f231721d OP_3 OP_CHECKMULTISIG

18434e5452505254596761206361726420666f7220464c444300000000000000000000000000000000000000000000000000000000000000000000000000

2つ目のデータを同じ様にデコードし、本来トランザクションタイプが入る10byte以降の部分を文字列に変換すると以下になります。

ga card for FLDC


一つ目のDescriptionと連結するとDescriptionが完成します。

Spells of Genesis/Moonga card for FLDC


以下ブロックエクスプローラのAsset Infoを確認すると、Descriptionが正しくデコードされていることが分かります。
https://www.blockscan.com/assetInfo/FDCARD


これで基本的なCounterpartyのトランザクションデータの作りが分かりました。
multisig以外のエンコード方法や、LOCKのトランザクションについては、また別の機会に検証してみたいと思います。


Read More...

2015年8月10日月曜日

Monacoinでマルチシグネチャアドレスを作成する


Counterpartyについて調べていたところ、マルチシグネチャという単語が出てきました。

また、Spells of Genesisでも資金持ち逃げ、使い込みなどの防止に、マルチシグネチャを利用しているようです。

ということで、今後、暗号通貨界隈の常識になりそうなマルチシグネチャについて、Monacoinでも動くのかを試してみます。

具体的には、2-of-3マルチシグネチャアドレスを作成し、送金してみます。
3つの署名のうち、2つあれば出金できるというヤツですね。

  • アドレスの作成
getnewaddressで署名するためのアドレスを3つ作成します。

後で確認しやすいようにアカウントを設定しておきます。
$ monacoind getnewaddress  "monamultisig"
MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm

$ monacoind getnewaddress  "monamultisig"
MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83

$ monacoind getnewaddress  "monamultisig"
MVe4ucgqSPRkR1yescuuX17D7t5vcikznd

getaddressesbyaccountでちゃんと作成できているか確認します。
$ monacoind getaddressesbyaccount "monamultisig"
[
    "MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83",
    "MVe4ucgqSPRkR1yescuuX17D7t5vcikznd",
    "MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm"
]

  • 公開鍵の確認
validateaddressで各アドレスの公開鍵を確認します。
monacoind validateaddress MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83
{
    "isvalid" : true,
    "address" : "MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83",
    "ismine" : true,
    "isscript" : false,
    "pubkey" : "020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a",
    "iscompressed" : true,
    "account" : "monamultisig"
}

monacoind validateaddress MVe4ucgqSPRkR1yescuuX17D7t5vcikznd
{
    "isvalid" : true,
    "address" : "MVe4ucgqSPRkR1yescuuX17D7t5vcikznd",
    "ismine" : true,
    "isscript" : false,
    "pubkey" : "03e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca",
    "iscompressed" : true,
    "account" : "monamultisig"
}

monacoind validateaddress MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm
{
    "isvalid" : true,
    "address" : "MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm",
    "ismine" : true,
    "isscript" : false,
    "pubkey" : "035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb438",
    "iscompressed" : true,
    "account" : "monamultisig"
}

ちなみに他人のアドレスに対してvalidateaddressを実行しても、結果には公開鍵は含まれず、有効であることだけ分かります。
monacoind validateaddress MR2j3u5oNWZzAAxeHtSinHUEVNiSbkn8zJ
{
    "isvalid" : true,
    "address" : "MR2j3u5oNWZzAAxeHtSinHUEVNiSbkn8zJ",
    "ismine" : false
}

  • マルチシグネチャアドレスの作成
createmultisigを使ってマルチシグネチャアドレスを作成します。

先ほど作成したアドレスの公開鍵を利用して作成し、一番目の引数が出金時に必要な署名の数です。
$ monacoind createmultisig 2 '["020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a", "03e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca", "035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb438"]'
{
    "address" : "3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM",
    "redeemScript" : "5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"
}

redeemScriptは後で使います。

ちなみに公開鍵ではなく、アドレスからも作れるようで、以下のように同じ結果になります。
$ monacoind createmultisig 2 '["MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83", "MVe4ucgqSPRkR1yescuuX17D7t5vcikznd", "MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm"]'
{
    "address" : "3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM",
    "redeemScript" : "5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"
}

  • マルチシグネチャアドレスへの送金
先ほど作成したアドレス宛に、sendtoaddressで送金してみます。
$ monacoind sendtoaddress 3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM 0.1
64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac


ブロックエクスプローラを見ると、しっかりと着金しています。

  • マルチシグネチャアドレス宛送金トランザクションの確認
getrawtransactionでトランザクションの中身を確認します。
オプションに1をつけるとrawではなくデコード済みが返ってきます。
$ monacoind getrawtransaction 64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac 1
{
    "hex" : "0100000002ad378b21681f50fbc3cf3cc1dc5967b003733437bd0008a7bc52e44acd2ec3ce00000000fdfe0000493046022100fcdca98debaa9413cba58f2c6ef22277797e6448ee1d391d840a295cc8d65614022100ccc415a475c0a0e4d88f41e4b53f59692a7847951d171cd723f7bb803d4748f301473044022030dbe7bb6fa1271bb2dea33341497847786a74d96f8d21286f5f279ade8b9e6302207be36c17cb83e2dc441ff37ea68ce0272c5b271635de2d82c1c812c762e762b6014c6952210336e6fc63f48676949b646456adf96b1183f296bd0e700bd894174f524839798a2102ea9fa819e3e9c05580671a095ce73aee766ce39c55e084ae5ad50355a5f1d4ef2103fe8b5f8df85b57ee876c3cb4ea23dfe7063ad6fb3500c7527971c80d1c931def53aeffffffff0221976a0390f96d87a81a7941c6279fb233625f44d17dc040f594f4fd6f99d2010000006c493046022100856d3d4813fc636873ab43d13e8251ccd3e4c8e29d284d7812d2537ecf0550dd022100af0a2139aa266e52619a1ed716ebdba6239c6f4537fe3a78e4d9db6f1a1883820121020c61b05ce77a8b0d36c813d882f7e730158296c9e5ef4184ce2f30f4acab5cfcffffffff026f0a1700000000001976a9141391189829986705d972aa1b0c26de994582afb588ac809698000000000017a9146b52e73d9d62dd84f9c974c0989ebc186d90531f8700000000",
    "txid" : "64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac",
    "version" : 1,
    "locktime" : 0,
    "vin" : [
        {
            "txid" : "cec32ecd4ae452bca70800bd37347303b06759dcc13ccfc3fb501f68218b37ad",
            "vout" : 0,
            "scriptSig" : {
                "asm" : "0 3046022100fcdca98debaa9413cba58f2c6ef22277797e6448ee1d391d840a295cc8d65614022100ccc415a475c0a0e4d88f41e4b53f59692a7847951d171cd723f7bb803d4748f301 3044022030dbe7bb6fa1271bb2dea33341497847786a74d96f8d21286f5f279ade8b9e6302207be36c17cb83e2dc441ff37ea68ce0272c5b271635de2d82c1c812c762e762b601 52210336e6fc63f48676949b646456adf96b1183f296bd0e700bd894174f524839798a2102ea9fa819e3e9c05580671a095ce73aee766ce39c55e084ae5ad50355a5f1d4ef2103fe8b5f8df85b57ee876c3cb4ea23dfe7063ad6fb3500c7527971c80d1c931def53ae",
                "hex" : "00493046022100fcdca98debaa9413cba58f2c6ef22277797e6448ee1d391d840a295cc8d65614022100ccc415a475c0a0e4d88f41e4b53f59692a7847951d171cd723f7bb803d4748f301473044022030dbe7bb6fa1271bb2dea33341497847786a74d96f8d21286f5f279ade8b9e6302207be36c17cb83e2dc441ff37ea68ce0272c5b271635de2d82c1c812c762e762b6014c6952210336e6fc63f48676949b646456adf96b1183f296bd0e700bd894174f524839798a2102ea9fa819e3e9c05580671a095ce73aee766ce39c55e084ae5ad50355a5f1d4ef2103fe8b5f8df85b57ee876c3cb4ea23dfe7063ad6fb3500c7527971c80d1c931def53ae"
            },
            "sequence" : 4294967295
        },
        {
            "txid" : "d2996ffdf494f540c07dd1445f6233b29f27c641791aa8876df990036a972102",
            "vout" : 1,
            "scriptSig" : {
                "asm" : "3046022100856d3d4813fc636873ab43d13e8251ccd3e4c8e29d284d7812d2537ecf0550dd022100af0a2139aa266e52619a1ed716ebdba6239c6f4537fe3a78e4d9db6f1a18838201 020c61b05ce77a8b0d36c813d882f7e730158296c9e5ef4184ce2f30f4acab5cfc",
                "hex" : "493046022100856d3d4813fc636873ab43d13e8251ccd3e4c8e29d284d7812d2537ecf0550dd022100af0a2139aa266e52619a1ed716ebdba6239c6f4537fe3a78e4d9db6f1a1883820121020c61b05ce77a8b0d36c813d882f7e730158296c9e5ef4184ce2f30f4acab5cfc"
            },
            "sequence" : 4294967295
        }
    ],
    "vout" : [
        {
            "value" : 0.01509999,
            "n" : 0,
            "scriptPubKey" : {
                "asm" : "OP_DUP OP_HASH160 1391189829986705d972aa1b0c26de994582afb5 OP_EQUALVERIFY OP_CHECKSIG",
                "hex" : "76a9141391189829986705d972aa1b0c26de994582afb588ac",
                "reqSigs" : 1,
                "type" : "pubkeyhash",
                "addresses" : [
                    "M9gctXWKs8hSrRbdJMRR4t9s6Yc8yHKd2s"
                ]
            }
        },
        {
            "value" : 0.10000000,
            "n" : 1,
            "scriptPubKey" : {
                "asm" : "OP_HASH160 6b52e73d9d62dd84f9c974c0989ebc186d90531f OP_EQUAL",
                "hex" : "a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87",
                "reqSigs" : 1,
                "type" : "scripthash",
                "addresses" : [
                    "3BUVYHpNB6J9X4GsXUo51d7UkfM322SspM"
                ]
            }
        }
    ]
}

  • 秘密鍵の確認
dumpprivkeyで各アドレスの秘密鍵を確認します。

結果はマスクしてあります。
$ monacoind dumpprivkey MAe39gF7wwh2g5MMYwmwiEYpSqjgFw7g83
xxxxprivateKey1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
noojloon@noojloon-MS-7821:~$ monacoind dumpprivkey MVe4ucgqSPRkR1yescuuX17D7t5vcikznd
xxxxprivateKey2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
noojloon@noojloon-MS-7821:~$ monacoind dumpprivkey MGQuGAWjYKcRYWKZndTBocxevJMsvRDkLm
xxxxprivateKey3xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

  • マルチシグネチャアドレスからの送金トランザクションを作成
createrawtransactionで送金トランザクションを作成します。

マルチシグネチャアドレス宛送金のtxidと、getrawtransactionで確認したデータの中身から、voutの大きいほうのnとそのscriptPubKeyhexを利用します。
$ monacoind createrawtransaction '[{"txid":"64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac","vout":1,"scriptPubKey":"a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87","redeemScript":"5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"}]' '{"MR2j3u5oNWZzAAxeHtSinHUEVNiSbkn8zJ":0.05}'
0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae8640100000000ffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000

この辺は普段の処理と比べるとややローレベルな処理になるため、今まで自動的に処理されていたことも自分でやる必要があります。

この例でいうとお釣りについて一切考えずに処理しているため、0.10の入金に対して0.05の出金ということになり、差額は手数料として消えるようです。

fee0.001にするには、以下のようにお釣り用のアドレスも送金先に指定します。
{"あて先":0.05,"お釣り用":0.049}

  • 送金トランザクションに署名する
signrawtransactionで先ほど作成したトランザクションに署名します。
$ monacoind signrawtransaction '0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae8640100000000ffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000' '[{"txid":"64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac","vout":1,"scriptPubKey":"a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87","redeemScript":"5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"}]' '["xxxxprivateKey1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]'
{
    "hex" : "0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000b500483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca243014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000",
    "complete" : false
}

一つではコンプリートしないので、もう一つ署名します。
$ monacoind signrawtransaction '0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000b500483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca243014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000' '[{"txid":"64e87a959a46cad0e25b9536ce0f996b56f89807a9215229d278a4e220ae68ac","vout":1,"scriptPubKey":"a9146b52e73d9d62dd84f9c974c0989ebc186d90531f87","redeemScript":"5221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853ae"}]' '["xxxxprivateKey2xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]'
{
    "hex" : "0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000fdff0000483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca24301493046022100e6b0a31018d371bd52bdbb5047c8a2d4978404cd300b51edbdcdd84eefd5eb54022100c20925bac763d3ebf706d0481c52f70ca49aa951086b8df1c1bf6e8780f380a2014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000",
    "complete" : true
}

コンプリートしました。

  • トランザクションの送信
二つの署名が終わったトランザクションを、sendrawtransactionで実際に送信します。
$ monacoind sendrawtransaction 0100000001ac68ae20e2a478d2295221a90798f8566b990fce36955be2d0ca469a957ae86401000000fdff0000483045022006265235010d34980be0054469137987a059db6b18803d2310195d24b0fac295022100a6b6c349b6378ed4ea9b9443bbcde4d2673d42f8b3bb108669e759940e1ca24301493046022100e6b0a31018d371bd52bdbb5047c8a2d4978404cd300b51edbdcdd84eefd5eb54022100c20925bac763d3ebf706d0481c52f70ca49aa951086b8df1c1bf6e8780f380a2014c695221020ac51c1293db7576d703d4c4a79eac4ba7bd3a4141bc6265bc93742f6666b03a2103e0b36973abfa94708e393e1f68ca5f600646696ed41bcabf3822ed5155c405ca21035df97df1f5ff93e4ba4df6b9a2f7aa2c2f7f9909ca2609b0714e1a0d6c6fb43853aeffffffff01404b4c00000000001976a914bbe8720c230325a27efff1e236bd61008dd97e1988ac00000000
4095c488fe886b66560540fb953cab44e2ab36f70c40fc5cc9e169f1e26e459b


半分手数料で持っていかれましたが、無事出金できました。


Read More...

2015年4月16日木曜日

Navigation DrawerとFragmentをAPI level 11未満で使いたい


ちょっといまさらですが、NavigationDrawerについて調べてみました。

NavigationDrawerを実装するには、サポートライブラリのandroid.support.v4.widget.DrawerLayoutを使います。

NavigationDrawerに限らず、画面を切り替えたりするのにFragmentを使うと便利なのですが、API level 11以上です。

API level11というとAndroid 3.0以降ということになりますが、Android 2.3.3 - 2.3.7がまだ1割弱は現存しているため、割り切ってしまうのもどうかなぁという気もします。

そこで方法がないか調べてみたところ、同じくサポートライブラリのandroid.support.v4.app.Fragmentを使えば可能でした。


ということで今回は、android.support.v4.widget.DrawerLayoutとandroid.support.v4.app.Fragmentを使って、NavigationDrawerを実装してみます。

NavigationDrawerから設定画面と詳細設定画面を切り替えるサンプルを作成します。

まずは、パーツを作成します。
設定画面と詳細設定画面のFragment用のレイアウトをそれぞれ作成します。

fragment_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content_settings"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Settings"
        android:id="@+id/textView1" />
</LinearLayout>

fragment_advanced_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content_advanced_settings"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Advanced Settings"
        android:id="@+id/textView2" />
</LinearLayout>

何の変哲も無いレイアウトです。

次に、android.support.v4.app.Fragmentを継承したクラスをそれぞれ作成します。

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class SettingsFragment extends Fragment{

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_settings, container, false);
    }
}

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class AdvancedSettingsFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_advanced_settings, container, false);
    }
}

onCreateViewメソッドをオーバーライドし、先ほどのxmlレイアウトファイルをそれぞれ紐付けます。

これでパーツは出来たので、次にメイン画面を作成します。

activity_main.xml

<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </FrameLayout>

    <LinearLayout
        android:id="@+id/drawer"
        android:orientation="vertical"
        android:layout_width="320dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#ffffffff">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Menu1"
            android:id="@+id/menuText1"
            android:textColor="#ff505050" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Menu2"
            android:id="@+id/menuText2"
            android:textColor="#ff505050" />
    </LinearLayout>

</android.support.v4.widget.DrawerLayout>

android.support.v4.widget.DrawerLayoutをルートに配置し、一つ目の子がコンテンツ用、二つ目の子がNavigationDrawer用です。

コンテンツはFragmentを使って実装し、NavigationDrawerは今回はそのままここに書いてしまいます。

それでは組み立てます。

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBarDrawerToggle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

public class ActivityMain extends ActionBarActivity {

    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initDrawer();
        initDrawerMenu();

        changeFragment(new SettingsFragment());
    }

    private void initDrawer() {
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.app_name, R.string.app_name);
        mDrawerToggle.setDrawerIndicatorEnabled(true);
        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_drawer);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);
    }

    private void initDrawerMenu() {
        TextView menu1 = (TextView) findViewById(R.id.menuText1);
        menu1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changeFragment(new SettingsFragment());
                setTitle("Settings");
            }
        });
        TextView menu2 = (TextView) findViewById(R.id.menuText2);
        menu2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changeFragment(new AdvancedSettingsFragment());
                setTitle("Advanced Settings");
            }
        });
    }

    private void changeFragment(Fragment f) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction()
                .replace(R.id.content_frame, f)
                .commit();
        mDrawerLayout.closeDrawers();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (mDrawerToggle.onOptionsItemSelected(item)) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

今時っぽくするためにActionBarを使いたいので、またもやサポートライブラリからActionBarActivityを継承してクラスを作成します。

initDrawer()メソッドの中で、NavigationDrawerとActionBarの設定を行っています。

getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_drawer)で左上の三本線のアイコンを設定しています。
これ、なぜだか分かりませんが、あんまりネットで参考になる日本語サイトがなく、結構ハマりました。

アイコン画像はここで拾って来て、drawableに放り込んでおきます。

アイコンからNavigationDrawerを呼べるように、onOptionsItemSelected()内でActionBarDrawerToggleのonOptionsItemSelected()を呼びます。

initDrawerMenu()メソッドの中で、NavigationDrawerのメニューの設定を行っています。

今回はそれぞれOnClickListenerを設定し、コンテンツの変更とついでにActionBarのタイトルの変更を行っています。


実際のコンテンツの変更については、changeFragment()メソッドの中で実装しています。

FragmentManagerを利用してコンテンツを変更し、NavigationDrawerをクローズする処理を行っています。


出来ました。

まぁメニュー二つとかそんなシンプルな構成であれば、NavigationDrawerを使う必要もないのですが、とりあえず最近のアプリっぽくなりました。

Read More...

2015年3月10日火曜日

IngressのGlyph Hackを自動化する ~Input Subsystem~


お蔵入り寸前だったGlyph Hackの自動化ですが、Translaterメダルやら、ハッキングボーナスやスピードボーナスの上限解放やら、なんとかモチベーションを持ち直すニュースがあったので、再チャレンジします。

ということで、今回はInput Subsystemとやらを調べてみました。

まず、Input Subsystemで利用するイベントデバイスファイルは/dev/input/にあるとのことなので確認してみます。

自分の環境では、/dev/input/配下に以下が格納されていました。

event0
event1
event2
event3
event4
mice

それぞれが何らかのデバイスに対応しているそうですが、これだけでは分からないので、BusyBoxをインストールして、hexdumpコマンドで覗いてみます。

# /system/xbin/hexdump /dev/input/event0
0000000 1d21 0003 4cbf 0004 0003 0039 69d4 0000
0000010 1d21 0003 4cbf 0004 0003 0035 023c 0000
0000020 1d21 0003 4cbf 0004 0003 0036 028a 0000
0000030 1d21 0003 4cbf 0004 0003 003a 004a 0000
0000040 1d21 0003 4cbf 0004 0003 0030 0006 0000
0000050 1d21 0003 4cbf 0004 0000 0000 0000 0000
0000060 1d21 0003 8987 0004 0003 003a 0018 0000
0000070 1d21 0003 8987 0004 0003 0030 0002 0000
0000080 1d21 0003 8987 0004 0000 0000 0000 0000
0000090 1d21 0003 c0fa 0004 0003 0039 ffff ffff
00000a0 1d21 0003 c0fa 0004 0000 0000 0000 0000

なにやら出力されてきました。
しかし、何かやろうとすると、毎回バイナリを解読させられる運命にあるようですね。

ざっと画面をタッチしたりしながら眺めていると、はじめの2byteはイベントが発生した時間のようです。

次の2byteは入力のタイプEV_ABSを表す3かと思いましたが、なんだか違いそうです。

次の2byteもx軸かy軸かを表すもの、、、じゃなさそうです。
座標は後ろから2番目の2byteのようですね。
うーん。。分からない。

ということでもうちょっと良い方法がないか調べたところありました。
これはBusyBoxのインストールも不要です。

# getevent /dev/input/event0
0003 0039 00006be2
0003 0035 0000029d
0003 0036 000002aa
0003 003a 0000004d
0003 0030 00000008
0000 0000 00000000
0003 003a 00000037
0003 0030 00000006
0000 0000 00000000
0003 0039 ffffffff
0000 0000 00000000

type、code、valueってことなんだろうけど、なんか分かりそうで分からない。

さらに色々調べたところ、geteventコマンドに素敵なオプションを発見。

# getevent -lt /dev/input/event0
[  320377.729707] EV_ABS       ABS_MT_TRACKING_ID   00009411
[  320377.729707] EV_ABS       ABS_MT_POSITION_X    00000296
[  320377.729707] EV_ABS       ABS_MT_POSITION_Y    00000348
[  320377.729707] EV_ABS       ABS_MT_PRESSURE      00000048
[  320377.729707] EV_ABS       ABS_MT_TOUCH_MAJOR   00000007
[  320377.729707] EV_SYN       SYN_REPORT           00000000
[  320377.766317] EV_ABS       ABS_MT_TRACKING_ID   ffffffff
[  320377.766317] EV_SYN       SYN_REPORT           00000000

これなら読み解こうという気になります。

では、まずは登場人物を整理します。

type
EV_ABS 3EV_SYN 0

code
SYN_REPORT 0
ABS_MT_TRACKING_ID 57
ABS_MT_POSITION_X 53
ABS_MT_POSITION_Y 54
ABS_MT_PRESSURE 58
ABS_MT_TOUCH_MAJOR 48

value
ABS_MT_TRACKING_IDのIDや、ABS_MT_POSITION_Xのx座標など。

次に、タッチイベントを発生させるための流れを整理します。

まずEV_ABSABS_MT_TRACKING_IDで一連のイベントのIDを設定します。
Android側の動きをgeteventで見ていると、1ずつインクリメントされ連番になっているようです。
ただ、適当な値でも特に文句は言われませんので、乱数とかでも大丈夫なのかも??

次に座標を送信します。
ABS_MT_POSITION_Xと、ABS_MT_POSITION_Yでイベントが発生した座標を設定します。
続いてABS_MT_PRESSUREで圧力、ABS_MT_TOUCH_MAJORで断面積ということで、いまいち良く分かりませんが設定します。
最後に、EV_SYNSYN_REPORTを送信します。
ここまでが座標送信のワンセットです。

座標、ABS_MT_PRESSUREABS_MT_TOUCH_MAJORSYN_REPORTのセットを連続で複数送信するとスワイプイベントになります。

イベント終了時にはABS_MT_TRACKING_IDにFFFFFFFFを設定して送信し、EV_SYNSYN_REPORTを送信して完了です。


ではやってみましょう。

# sendevent /dev/input/event0 3 57 3800
# sendevent /dev/input/event0 3 53 1222
# sendevent /dev/input/event0 3 54 1237
# sendevent /dev/input/event0 3 58 72
# sendevent /dev/input/event0 3 48 7
# sendevent /dev/input/event0 0 0 0
# sendevent /dev/input/event0 3 57 -1
# sendevent /dev/input/event0 0 0 0

ちゃんと動きました。

さらに複数座標を送信して、動きをつけて軌跡を描けるか試しました。

成功!ちなみに描いたのは「SUCCESS」です。
なぜこんなに滑らかな曲線になるのかは謎です。


しかし、rootとってshellからなら何でも出来てしまうってのは結構恐ろしい事だなぁ、と。
Google先生が練り上げたAndroidのセキュリティの壁も、いとも簡単に超えてしまうわけですね。

まぁ当然なんですが。

とりあえずroot系アプリ入れるときは、ちょっと注意したほうがいいなと思いましたとさ。

Read More...

2015年2月16日月曜日

IngressのGlyph Hackをハックする


IngressのGlyph Hackをハック中。

フレームバッファのバイナリをBitmapに変換して必要なとこだけキャプチャするのは完了。
こうして人の脳は退化していくわけですね。

ついでに自前の検出器を作成して、それがどの図柄なのかを検出するのも完了。
画面上の表示位置が変わらないし図柄がすごく単純なので、OpenCVなんて使う必要は全くありませんでした。

そしてあとは自動で絵を描くだけだ~と思ったら、後一歩のところで壁にぶつかりました。

そもそも、画面キャプチャ、検出器作成まで出来たら、あとはイージーだと思って何も調べてなかったのですが、結構厄介な問題がありました。

よそ様のアプリを操作するためにタッチイベントを生成するには、android.permission.INJECT_EVENTSが必要なのですが、こいつがいわゆるシステム権限らしくうまく動きません。

AndroidManifest.xmlで宣言しても、SecurityExceptionが発生します。


システム権限ということであれば、/system/app配下に放り込めば良いのかと考え、以下のようにしてADB Shellから無理やり放り込んでシステムアプリ化してみました。

$ adb shell
$ su
# mount -o rw,remount /system
# cp /sdcard/AutoGlyph.apk /system/app
# chmod 644 /system/app/AutoGlyph.apk
# reboot

が、やはり動きません。


INJECT_EVENTSprotectionLevelsignatureで、公式の説明にはthe application that declared the permissionと同じ証明書で署名されていないとダメだとあります。
googleの署名じゃないとダメってことでしょうか?

もしかするとprotectionLevelsignatureOrSystemであれば、システムアプリ化するだけでいけたのかもしれませんが。。

これはお蔵入りの予感。。


ただ、LMT Launcherとかは普通に動いてるわけで、何かやり方はあるんでしょう。
セキュリティ的には出来ちゃダメな気もしますが。

システムアプリを偽装できるのか、Input Subsystemあたりを利用するのか。
LMT Launcherのpackagenamecom.android.lmtなのが気になりますが、それだけではダメな気がします。

う~ん。。


Read More...