Hatena::ブログ(Diary)

eggshell blue

2012-02-04

複数台の Wiimote をPCに認識させて使う

| 複数台の Wiimote をPCに認識させて使うを含むブックマーク 複数台の Wiimote をPCに認識させて使うのブックマークコメント

タイトルの通りですが、snesgtだったりPowerPointだったりいろいろな使い道のあるHIDことWiiリモコンですが、制御ソフトを開発する人の多くは一人プレイヤーだからかどういうわけか1台のみ制御可というソフトが多いところで、複数台(Bluetoothの限界上は7台?)のWiimoteを認識させる場合について。

Wiimote自体は多数の機能を持っているため、例えばモーションセンサやジャイロなどの機能を使おうとすると場合によっては複数のソフトウェアドライバが必要になりますが、通常のソフトウェアでの使用であれば、キーボードエミュレーションの機能があれば十二分という場合が殆どであり、その場合は GlovePie 一つで事足ります。

また、Bluetoothスタックによるかも知れませんが、少なくとも東芝スタックにおいては、Wiimote自体は汎用HIDヒューマンインターフェースデバイス)としてシステムに登録されるため、追加のデバイスドライバも必要にはなりません。従って、Win7以降、x64/x86の環境による違いを意識する必要がほとんど無いということも利点となります。

今回はWiimote+GlovePieという例ですが、これはPS3のコントローラーと混載した環境でも成立します。従って、複数人で何かしらのソフトウェアを用いる場合、ソフトウェア側で予め適切にキーの割り当てを行っておき、そのキーに対し、GlovePie側でWiimoteもしくはPS3のコントローラーボタンを各キーに割り当てるという作業を行うことで、多数のWiimoteを使用する環境が成立するという仕組みです。

なお、MotionPlus内蔵のWiimoteは、現時点で最新版の GlovePie 0.45 では対応していないようで、こちらの記事から別途 PIEFree.exe のみ取得する必要があります。

以下に複数台の Wiimote を使用する場合のスクリプト例を記載します。wiimoteオブジェクト配列、とかではなく、認識した順番に wiimote1, wiimote2,…というような割り当てになるようなので、そのまま順次記載していけば良いと思われます。

wiimote1.Led1=true
key.A=wiimote1.One
key.B=wiimote1.Two
key.C=wiimote1.Up
key.D=wiimote1.Right
key.E=wiimote1.Down
Key.F=wiimote1.Left

wiimote2.Led2=true
key.G=wiimote2.One
key.H=wiimote2.Two
key.I=wiimote2.Up
key.J=wiimote2.Right
key.K=wiimote2.Down
key.L=wiimote2.Left

wiimote3.Led3=true
key.M=wiimote3.One
key.N=wiimote3.Two
key.O=wiimote3.Up
key.P=wiimote3.Right
key.Q=wiimote3.Down
key.R=wiimote3.Left

wiimote4.Led4=true
key.S=wiimote4.One
key.T=wiimote4.Two
key.U=wiimote4.Up
key.V=wiimote4.Right
key.W=wiimote4.Down
key.X=wiimote4.Left

2011-12-17

もうパスワードで悩まない!

| もうパスワードで悩まない!を含むブックマーク もうパスワードで悩まない!のブックマークコメント

とある通販サイトで適当にパスワード打ち込んだら、後日思い出せなくなって涙目になったのでカッとなってパスワードジェネレーターを作ってみた。

Bookmarklet にしてご利用ください。Firefox 8.0でのみ動作確認しています。

概要

ソースコードは最下部に記載しています。ドメイン名とマスターパスワードSalt)から、都度パスワードを算出する Bookmarklet です。マスターパスワードが変わらない限り、同じドメインのページでは常に同じパスワードが発行されますので、もうこのサイトのパスワード何だったっけ、と頭を悩ます必要がありません。ただし、マスターパスワードが漏洩すると元も子もありませんので、マスターパスワードの管理だけはしっかりするようにしてください。

初回実行時には記号使用の有無と文字数を聞かれます。これらの値はブラウザの LocalStorage に保存されるため、履歴が有効であれば次回以降は生成されたパスワードのみが表示されます。

下記ソースをコピーし、都度マスターパスワードを打ち込む場合は

var salt = prompt('Salt?');

に、いちいち打ち込むのが面倒くさい場合は

var salt = '適当なパスワードに書き変えてください';

としてください。書き換える場合は日本語でもアルファベットでも構いませんが、強度的には日本語の方が望ましいです。

詳細

元ネタはこちら。ソースが Obsolete になってたので、書き直すついでに Bookmarklet 化して、HMACを使うようにアルゴリズムを変更。

設定した Salt秘密鍵に、ドメイン名をメッセージとして HMAC を算出し、求めたバイト列を元に、パスワードとして使う文字列を切り出しています。一応、なんちゃって重複排除機能を搭載。

SaltUTF-8 対応の文字列であれば何でも構いません。日本語の文章でも問題はなく、むしろ攻撃回避の観点からはそちらの方が望ましいとも言えます。また、HMACの構造上、仮に算出されたパスワードから HMAC ハッシュ値が判明したとしても、元の秘密鍵にたどり着くことはできませんが、念のためパスワード文字列の算出に用いる文字列シャッフルして、パスワードからハッシュ値への解読をしにくいような対策を施しています*1

HMAC-SHA256 の算出には Crypto-js を使用しており、動的にコードを読みに行っています。このため、外部コードの読み込みにかかるタイムラグを回避するため、 setTimeout で時間差実行しています。

パスワード算出には乱数変数を使用していませんので、 salt を変更しない限り、あるドメインに対して常に同じパスワードが返されます。これは、例えば毎月パスワードを変えている、などという場合は不便になるので、

var hmacBytes = Crypto.HMAC(Crypto.SHA256, document.domain + '_' + (new Date()).getFullYear().toString() + ((new Date()).getMonth() + 1).toString(), salt, { asBytes: true });

というように書き換える等の対処が必要です。

問題点

といったところでしょうか。

強度の確認

英大文字・小文字、数字および指示がある場合は記号も、必ず一字は使用するような設計にしています。強度確認は パスワード チェッカー: 安全性の高いパスワードの使用 | Microsoft セキュリティ が便利です。

どうぞご利用ください。

ソース

javascript: (function(){

  /* Salt がマスターパスワードになります。漏らさないよう注意。 */
  var salt = prompt('Salt?') || '適当なパスワードに書き変えてください';

  if (salt == '' || typeof(salt) != 'string') {
    alert('salt が設定されていません!');
  }else{

    if(!document.getElementById('crypto-sha256-hmac')){
      var s = document.createElement('script');
      s.id = 'crypto-sha256-hmac';
      s.src = 'https://crypto-js.googlecode.com/files/2.3.0-crypto-sha256-hmac.js';
      void(document.body.appendChild(s));
    }

    var mod = function(e, s){ return Math.abs(parseInt(e)) % s.length; };

    var shuffle = function(s, seed){
      var q = ''; var p = -2;
      while(mod(seed.slice(p-1, p), s) == mod(seed.slice(p, p+1), s)){ if ( Math.abs(p-2) > seed.length ){ p = -2; break; }else{ p--; } }
      var border = [ mod(seed.slice(p-1, p), s), mod(seed.slice(p, p+1), s)].sort(function(a,b){return a-b;});
      switch ( mod(seed.slice(p-2, p-1), 'aaa') ){
        case 0:
          q = s.substring(0, border[0]) + s.substring(border[1]) + s.substring(border[0], border[1]);
          break;
        case 1:
          q = s.substring(border[0], border[1]) + s.substring(0, border[0]) + s.substring(border[1]);
          break;
        case 2:
          q = s.substring(border[1]) + s.substring(border[0], border[1]) + s.substring(0, border[0]);
          break;
      }
      return q;
    };

    var digit = '0123456789';
    var uc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    var lc = 'abcdefghijklmnopqrstuvwxyz';
    var symbol = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
    var allstr = ''; for(var i=33; i<127; i++){ allstr += String.fromCharCode(i); }

    var genPass = function(binarray, len, isUseSymbol){
      var i = 3;
      var src = '';
      var str = [];
      var dupCheck = true;
      var isDuplicated = false;
      var duplication = [];

      str.push(uc.charAt(mod(binarray[0], uc)));
      str.push(lc.charAt(mod(binarray[1], lc)));
      str.push(digit.charAt(mod(binarray[2], digit)));

      if(isUseSymbol){
        str.push(symbol.charAt(mod(binarray[3], symbol)));
        i = 4;
        src = shuffle(allstr, binarray);
      }else{
        src = shuffle(digit + uc + lc, binarray);
      }

      while(str.length < len){
        if(dupCheck){
          for(var j = 0; j < str.length; j++){
            if( str[j] == src.charAt(mod(binarray[i], src)) ){
              isDuplicated = true;
              duplication.push(mod(binarray[i], src));
              break;
            }
          }
        }
        if(!isDuplicated){ str.push(src.charAt(mod(binarray[i], src))); }
        isDuplicated = false; i++;
        if (i >=  binarray.length) {
          for(var k = 0; k < duplication.length; k++){ if(str.length < len){ str.push(src.charAt(duplication[k])); } }
          dupCheck = false; i = 0;
        }
      }
      return shuffle(str.join(''), binarray);
    };

    setTimeout(function(){
      var hmacBytes = Crypto.HMAC(Crypto.SHA256, document.domain, salt, { asBytes: true });
      var l = parseInt(localStorage.getItem('length'));
      var useSymbol = eval(localStorage.getItem('useSymbol'));
      var q = 3;

      if ( ( isNaN(l) ) || ( typeof(useSymbol) != 'boolean' ) ) {
        useSymbol = window.confirm('記号を使用しますか?');
        if (useSymbol) { q = 4; }
        l = prompt(q + '文字以上、' + hmacBytes.length + '文字以下で文字数を設定してください。', 10);
        if ( ( !isNaN(parseInt(l)) ) && ( parseInt(l) <= hmacBytes.length ) && ( parseInt(l) >= q ) ){
          localStorage.setItem('length', l.toString());
          localStorage.setItem('useSymbol', useSymbol.toString());
        }else{
          return 0;
        }
      }
      
      window.prompt('Pass phrase:', genPass(hmacBytes, l, useSymbol));
      
    }, 500);

  }
})();

*1:そもそも生成されたパスワードが第三者の目に触れる可能性は低く、またその上でこのアルゴリズムに則って生成されたものであると判明するという極めて低い確率で起こる条件をクリアした上での、パスワードの元になるハッシュ値の先頭部分を解読しにくくするというある種の「難読化」ですので、例えば得られたHMACが全て同じ値になってしまった場合にはシャッフルは起こりませんが、そこまで想定したコードにする必然性が感じられないため、中途半端と言えば中途半端な実装になっています。

2011-11-23

Chromium Updator 改

| Chromium Updator 改を含むブックマーク Chromium Updator 改のブックマークコメント

Google が開発に参加している Chromium ブラウザの自動更新スクリプトが、 Googleサーバー構成変更に伴って久しく使えなくなっていたので手直ししました。

see) http://d.hatena.ne.jp/RobinEgg/20090818/p1

必要なもの:

全てのバイナリ

"%USERPROFILE%\AppData\Local\Chromium"

に設置してください(Chromiumデフォルトルート)。

@echo off
setlocal
set CHROME="%USERPROFILE%\AppData\Local\Chromium"

if not exist %CHROME% mkdir %CHROME%
cd /d %CHROME%

if exist %CHROME%\revision move /Y revision revision.old
wget "http://commondatastorage.googleapis.com/chromium-browser-continuous/Win/LAST_CHANGE" -O .\revision
if not exist %CHROME%\revision.old goto getNew
fc .\revision .\revision.old > nul
if errorlevel 1 goto getNew
exit /b

:getNew
del /f /s /q .\chrome-win32.zip
for /f "usebackq tokens=*" %%i in (`type revision`) do @set REV=%%i
wget "http://commondatastorage.googleapis.com/chromium-browser-continuous/Win/%REV%/chrome-win32.zip" -O .\chrome-win32.zip
if errorlevel 0 7za.exe x -y .\chrome-win32.zip
rd /S /Q Application
move /Y chrome-win32 Application
if errorlevel 1 del /F /Q revision*
endlocal

相変わらずエラー処理も何もないですが、ご自由にお使いください。

2011-07-10

SimpleDLNAServer 公開

| SimpleDLNAServer 公開を含むブックマーク SimpleDLNAServer 公開のブックマークコメント

フリーの UPnP AV / DLNA サーバーを公開しました。

開発動機

  • PS3 Media Server(以下PMS)は非常に高性能ですが、高性能故に動作が緩慢だったり、設定項目が多すぎて簡単に使えないというジレンマがあった。
    • 「TS配信にPMSを使う」はずが「如何にPMSを使いこなすか」という本末転倒な状況をどうにかしたい
  • PMSmp3配信を行うと、酷い文字化けに遭遇することが多かった。
    • これがきっかけで我が家のライブラリAAC に統一されつつある
    • どうしても海外ソフトでは日本語対応が弱くなる
  • 折角クライアントPS3 を使うのに、生 ts の配信に難があったり、トランスコードに頭を悩ませるのはおかしい。
  • foobar2000 + foo_upnp.dll では、ライブラリ内容を一気に配信しようとするため、数百エレメントを持つライブラリだとクライアント側が非常にしんどいことになる。

などなどです。

流石にネットワーク周りまで一から書き上げることは難しいと思っていたため、フリーのライブラリはないかしらと探していたところ、Platinum UPnP というライブラリがあり、ドキュメントは整備されていないもののサンプルプログラムが付属していたため、これを参考に書き上げました。

特徴

上記開発動機で書いた悩みを解消するのが最大の目的でしたので、それがそのまま特徴になります。

などなど。

対象

PS3 (とXBOX)がメインターゲットですが、そのほかの DLNA クライアントでも動作は可能だと思います。実際、手元では

で正常動作を確認しています。ただし、最近発売されている薄型テレビでは各社独自の実装が行われているようですので、これについては動作するかどうかは一切検証していませんし、わかりません。

開発環境・依存関係

Platinum を使用している関係上、ライセンスGPL v2 (もしくはそれ以降)となります。

詳細

http://sites.google.com/site/simpledlnaserver/ をご覧下さい。

2011-07-09

.net/C# で GetDetailsOf を使う

| .net/C# で GetDetailsOf を使うを含むブックマーク .net/C# で GetDetailsOf を使うのブックマークコメント

音楽や動画ファイルのメタデータを取得する際、 taglib などの外部ライブラリを使用しても良いのですが、使う範囲が限られているのであれば、 Windows 標準の shell32.dll を使用するのも一つの選択肢です。

ただ、これには弱点があり、 XP/Vista/7 間で微妙に互換性がないことと、特に AAC/mp4 ファイルのメタデータ取得ができたりできなかったり、という問題があります。

エクスプローラーの詳細表示と同じ情報を取得できるので、特に文字化けを気にしなくて済むというのは利点ですが、AAC 系のファイルは XP では扱いづらいこと、また未だに XP マシンが跋扈している現状、使い勝手は正直良くないですが、とりあえず載せてみます。

3OS 間で互換性がない、というのは、 Shell32.ShellClass のインスタンスを作成する際、例えば 7 で開発したものをそのまま XP に持って行くと、 InvalidCastException が発生し、インスタンスを作れない、というものです。これを回避するには、 Activator.CreateInstance メソッドを利用します。

shell32.dll は未だに COM Component ですので、参照設定から "Microsoft Shell Controls And Automation" を登録しておきます。

public void test()
{
  Shell32.Folder folder = null;

  string someDirectory = @"c:\hoge\fuga";
  string someFile = @"test.mp3";

  try
  {
    Type wshell = Type.GetTypeFromProgID("Shell.Application");
    object wshInstance = Activator.CreateInstance(wshell);
    folder = (Shell32.Folder)wshell.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, wshInstance, new object[] { someDirectory });
  }
  catch {  }

  Shell32.FolderItem folderitem = folder.ParseName(someFile);

  // 再生時間の表示。ただし7上。XPでは 27 ではなく 21 となる。
  System.Diagnostics.Debug.WriteLine(folder.GetDetailsOf(folderitem, 27));
}