Hackの作成

1. まえがき

 Palm OS 4からOS 5へ変化したことで、利用者にとって(そしておそらくは開発者にとっても)災難といえるのはHackが動作しなくなったことでしょう。Hackが使えないという理由でOS 5への移行ができない利用者もいるようです。

 ただし、「Hack」という言葉は様々な使われ方をしており、「OS 5ではHackが動かない」という言い方は正確ではありません。
改めてHackという言葉の定義をしてみましょう。

 Hackには以下3つの意味があるようです。

 (1)HackMaster及びその互換ソフトにより管理されるソフトウェア
 (2)Palm OS関数の動きを変えることでPalm OS全体の機能を拡張するソフトウェア
 (3)単独のアプリケーションではなくPalm全体の動作を変更、拡張するソフトウェア

これらは、リストの若い番号ほど範囲が狭くなります。私のソフトウェアを例にすると、古いバージョンのDA Launcher、More Font HackなどはHackMasterを必要とするので(1)であり、(2)であり、かつ(3)です。PowerJOG、PowerRUN、DA Launcher(機能拡張版)等はPalm OS関数に割り込む形で実現されていますが、HackMasterを必要としないので、(2)であり(3)ですが(1)ではありません。またDA Launcher(コマンドストローク版)はシステム全体の機能を拡張するので(3)ですが、(2)でも(1)でもありません。

 OS 5では、OS 4まで可能であったPalm OS関数の置き換えができなくなったため、上記のうち(1)及び(2)に相当するソフトウェアが動作しなくなりました。一方(3)に該当するソフトウェアでも関数の置き換えをしていなければ引き続き動作します。


 OS 5では(1)、(2)を実現するためのメカニズムが利用不可能になり、それに代わって(3)をより作りやすくするための拡張がなされています。ただし、(1),(2)に比べるとはるかに少ない自由度しかないため、(1)、(2)のソフトウェアが全て(3)で実現できるわけではありません。それでも最近はOS 5に対応したHackが続々と登場していることで分かるように、かなりの種類ソフトが実現できることには間違いないでしょう。

 今回は、(2)の方式で作られたHackをOS 5で動作させる方法について解説します。


2.サンプル プログラム

 今回のサンプル プログラムは、ソフトウェア キーボードを変更するものです。
(OS 4対応のプロジェクトをダウンロード) (OS 5対応のプロジェクトをダウンロード)

 サンプル プログラムを起動すると図2のように画面上部にフィールドが表示され、下部に「Ha ck ON」というボタンが表示されます。この状態でソフトウェア キーボード アイコンをタップすると、従来どおり図3のようにソフト キーボードが表示されます。

図1

図2

 次にHack ONボタンをタップするとHackが有効になり、図3のように「Hack OFF」ボタンが表示されます。

図3

図4

この状態でソフトウェア キーボード アイコンをタップすると、図4のようなメッセージが表示されます。例えばこの状態で予定表に移動しても、図5のようにメッセージは表示されたままとなり、Hackが動作しつづけていることが分かります。

図5

 Hackが動作した状態でソフトを削除するとクラッシュの原因となりますが、図6のようにHackがONの状態でソフトを削除しようとしても削除できません。

図6

 以下サンプルプログラムの解説を行います。

 まずはMainFormHandleEventです。ここではHackのONとOFFを行います。

static Boolean MainFormHandleEvent(EventType * eventP)
{
  Boolean handled = false;
  FormType * frmP;

  switch (eventP->eType)
    {
  .
  .
  .
  case ctlSelectEvent:
    switch ( eventP->data.ctlSelect.controlID){
      case MainOnButton:
        InstallHack( true ); // Hackをインストールする
        MainFormInit(FrmGetActiveForm());
        // ONボタンを隠し、OFFボタンを表示するために画面の再描画
        handled = true;
        break;

      case MainOffButton:
        InstallHack( false );
        MainFormInit(FrmGetActiveForm());
        handled = true;
        break;
      }
      break;

      default:
        break;
      }

    return handled;
}

 InstallHackという関数はHackの組み込みと削除を行うものです。またMainFormInitを呼び出すことで画面の書き換えを行い、Hack ONボタンとHack OFFボタンの表示切替も行っています。

 次はMainFormInitです。

static void MainFormInit(FormType *frmP)
{
  FieldType *field;
  UInt16 fieldIndex;
  UInt32 dummy;

  fieldIndex = FrmGetObjectIndex(frmP, MainDescriptionField);
  field = (FieldType *)FrmGetObjectPtr(frmP, fieldIndex);
  FrmSetFocus(frmP, fieldIndex);

  if ( FtrGet( APP_CREATOR, FTR_NUM_HACK_INSTALLED ,&dummy) == errNone ){
    // 既にHackがインストールされている
    FrmShowObject( FrmGetActiveForm(), FrmGetObjectIndex(FrmGetActiveForm(), MainOffButton));
    // HackOFFボタンを表示
    FrmHideObject( FrmGetActiveForm(), FrmGetObjectIndex(FrmGetActiveForm(), MainOnButton));
    // HackONボタンを非表示
  } else { // まだインストールされていない
    FrmShowObject( FrmGetActiveForm(), FrmGetObjectIndex(FrmGetActiveForm(), MainOnButton));
    // HackOnボタンを表示
    FrmHideObject( FrmGetActiveForm(), FrmGetObjectIndex(FrmGetActiveForm(), MainOffButton));
    // HackOFFボタンを非表示
  }
}

 このようにFeature Managerを使うことでHackの状態を識別しています。Featureはリセットすることによりクリアされますが、Hackの組み込み状態などはアプリケーションを切り替えても保持されるため、リセットで初期化される状態の管理には非常に便利です。

次が実際にHackの組み込みを行うInstall Hackです。

InstallHack( Boolean install)
{
  DmOpenRef db;
  MemHandle h;
  MemPtr p;
  UInt32 old;
  LocalID dbID;
  UInt16 cardNo;

// あとで処理をするために自分自身をOpenする
  SysCurAppDatabase (&cardNo, &dbID);
  db = DmOpenDatabase( cardNo , dbID, dmModeReadOnly);

  if ( install){// Hackのインストール時
  // Hack動作中はコードが格納されているリソースが移動するとクラッシュしてしまう
  // プログラム本体が格納されているリソースが移動しないようにロックしておく
    h = DmGetResource( 'code', 1);
    p = MemHandleLock( h);
    // 処理が終わったので自分自身のDBをクローズ
    DmCloseDatabase( db);

  // 現在のPalmOS関数のアドレスを取得し、Feature Managerにより記録する
    old = (UInt32)SysGetTrapAddress( sysTrapSysKeyboardDialog );
    FtrSet( APP_CREATOR, 1000, old );

  // HackMasterのルールに従って現在のアドレスを保管しておく
  // PalmOSの呼び出しテーブルを置き換え、作成した関数が呼ばれるようにする
    SysSetTrapAddress( sysTrapSysKeyboardDialog , KeyboardHackEntry );

  // Hackが動作中に削除されないようにProtectしておく
    DmDatabaseProtect(cardNo, dbID, true);

  // HackがInstall済みであることをFeatureで示す
    FtrSet( APP_CREATOR, FTR_NUM_HACK_INSTALLED, 0);

  } else {
  // Hackのアンインストール時
  // 呼び出しテーブルを元に戻す
    FtrGet( APP_CREATOR, 1000, &old );
  // 元のポインタを取得
    SysSetTrapAddress( sysTrapSysKeyboardDialog , (MemPtr)old );
  // 元のアドレスに戻す
  // Featureを消しておく
    FtrUnregister( APP_CREATOR, 1000 );

  // ロックしておいたコードリソースをアンロックする
    h = DmGetResource( 'code', 1);
    MemHandleUnlock( h);
    DmCloseDatabase( db);

  // Protectを解除する
    DmDatabaseProtect( cardNo, dbID, false);

  // Hack Install済みのマークを消す
    FtrUnregister( APP_CREATOR, FTR_NUM_HACK_INSTALLED);
  }
}

 処理内容についてはコメントの通りなので詳細は省略しますが、一番キーとなる関数はSysGetTrapAddressとSysSetTrapAddressです。この二つの関数によってPalmOS関数の置き換え、拡張が可能となっており、これらが動作しなくなったため、OS 5ではHackが利用不可能になったわけです。

 最後に説明するのはHackの本体であるKeyboardHackEntry関数と、そこから呼び出されているKeyboardHack関数です。

// Hackの処理自体を記述する。用意されたダイアログを表示する
// 実際のソフトではここを拡張する
static void
KeyboardHack(void)
{
  LocalID dbID;
  DmOpenRef db;

  dbID = DmFindDatabase( 0,"OS5Sample3");
  db = DmOpenDatabase( 0, dbID, dmModeReadOnly);

  FrmAlert( KeyboardReplacementAlert);

  DmCloseDatabase( db );
}


static void
KeyboardHackEntry(KeyboardType kbd)
{
// Hack本体を呼び出す
  KeyboardHack();

/*
今回は標準のPalmOS関数の機能を置き換えているが、
TrapしたPalmOS関数の処理を続ける場合、以下のようにする。
  {
    static void (*p)(KeyboardType kbd);
    FtrGet( APP_CREATOR, 1000, (UInt32 *)&p);
    // Trapセット時に記録した元々のアドレスを取得する
    (*p)(kbd); // 元々のPalmOS関数を呼び出す
*/

  }
  return;
}

 ユーザーがソフトキーボードを表示するためのアクションを行うとSysKeyboardDialogが呼び出されます。この関数のアドレスは変更されているため、実際にはKeyboardHackEntryが呼び出されます。
 KeyboardHackEntryでは、処理本体を行うKeyboadHack()を呼び出して関数が終了しているため、実際のソフトキーボードは表示されません。自分自身でなんらかの処理を行った後に実際の関数を呼び出す方法はコメントとして記述してあります。

 このサンプルでの処理は、用意されたダイアログを表示するだけの簡単なものですが、実用的なソフトウェアにする場合はKeyboardHack関数を強化する必要があるでしょう。


3.Palm OS 5での問題点

 前述の通り、OS 5ではPalm OS関数の呼び出しアドレスの取得と設定を行うSysSetTrapAddressとSysGetTrapAddressが動作しません。従ってこのソフトウェアを実行しても実際には何も起こらないことになります。
 本プログラムの場合は何も起こらないだけなのですが、プログラムによっては致命的なエラーやデータの紛失にもつながりません。Palm OS 4以前を対象としたソフトであってもOSのバージョンをチェックするか、SysGetTrapAddressの返り値をチェックしてエラーメッセージの表示などを行うべきでしょう。

4.Palm OS 5でのHackの作り方:Notification Manager

 OS 5では、(3)のHackソフトを作るための仕組みとしてNotification Managerが利用できます。Notification Managerは元々、システムに何らかのイベントがあった場合にアプリケーションを呼び出すための機能でした。例えば、以下のようなイベントがNotification Managerによってアプリケーションに伝えられます。

 ・メモリーカードが挿入された、取り出された
 ・画面の色数が変更された
 ・時間・日付が変更された
 ・電源がOFFになろうとしている、ONになった
 ・ユニバーサルコネクタになんらかの機器が接続された。

 OS 5では、これらイベントの種類が大幅に増えており、これによりPalm OSが実行する作業への割り込みや、OSの動作を変更することが可能になっています。OS 5では以下のようなNotificationが追加されました。

Notification 呼び出しもとのPalmOS関数内容
sysNotifyProcessPenStrokeEventEvtProcessSoftKeyStroke Graffitiエリアでペン入力が行われた。
sysNotifyVirtualCharHandlingEventSysHandleEvent Virtual Characterが発生した
sysNotifyEventDequeuedEventEvtGetEvent 何らかのイベントが発生した
sysNotifyIdleTimeEventSysHandleEvent Idleタイムが設定の時間に達して電源がOFFになろうとしている
sysNotifyAppLaunchingEventSysAppStart アプリケーションが起動中である。
sysNotifyAppQuittingEventSysAppQuit アプリケーションの狩猟処理中である。
sysNotifyInsPtEnableEventInsPtEnable/InsPtDisable カーソルの表示状態が変わった
sysNotifyKeyboardDialogEventSysKeyboardDialog ソフトキーボードが表示されようとしている。

 これらのPalm OS関数を置き換えていたHackソフトは、同等の機能をNotification Managerで実現できる可能性が高いことになります。置き換えていた関数がここに見当たらない場合は、まったく別の実現方法を検討しなくてはなりません。

 今回のサンプルプログラムはSysKeyboardDialogを置き換えていますので、Notifacation Managerを利用してOS 5上で動作するように改造してみましょう。

5.改造方法

OS 4用のHackとOS 5用のHackは、まったく別の仕組みで実現しなければならないため、OSバージョンの判断を行う関数が必要になります。まずはOSがOS 5以降かを判断するIsOS5()です。

static Boolean
IsOS5(void)
{
  UInt32 romVersion;

  FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
  return (romVersion >= sysMakeROMVersion(5,0,0,sysROMStageDevelopment,0) );
}

 これはROMの判断の場合に、良く出てくる使い方ですから問題はないと思いますが、注意が必要なのはsysMakeROMVersionの4つ目のパラメータです。通常はsysROMStageReleaseを用いますが、Palm TungstenTだけはここがReleaseeではなくDevelopmentになってしまっていますので、TungstenTがOS 5マシンと認識されるようにsysROMStageDevelopmentを用います。

 次に変更が必要なのはHackの組み込みを行うInstallHackです。

static void
InstallHack( Boolean install)
{
  if ( IsOS5()){
    InstallHackOS5( install );
  } else {
    InstallHackOS4( install );
  }
}

 OS 5以降と以前で呼び出す関数を分けています。InstallHackOS4は上で説明したInstallHackとまったく同じものです。

 次がOS 5用のInstallHackOS5です。

static void
InstallHackOS5( Boolean install)
{
  UInt16 cardNo;
  LocalID dbID;

  SysCurAppDatabase (&cardNo, &dbID);

  if ( install ){
    SysNotifyRegister( cardNo, dbID, sysNotifyKeyboardDialogEvent, 0, sysNotifyNormalPriority, 0) ;
    FtrSet( APP_CREATOR, FTR_NUM_HACK_INSTALLED, 0);
    // インストールしたことをマークする
  } else {
    SysNotifyUnregister( cardNo, dbID, sysNotifyKeyboardDialogEvent , 0) ;
    FtrUnregister( APP_CREATOR, FTR_NUM_HACK_INSTALLED);
    // マークを削除
  }
}

 このようにOS 4用のものに比べると非常に簡単になっています。SysNotifyRegisterにパラメータとしてsysNotifyKeyboardDialogEventを指定することで、該当するNotificationを受信できるようになります。

 Notificationの受信には、関数のポインタによる方法とLaunchCommandによる方法があります。関数のポインタを指定するとHackと同様に関数が直接呼ばれます。一方LaunchCommandによる方法ではLaunchCommandによってアプリケーションが呼ばれます。これらには以下のような利点、欠点があります。

・関数のポインタによる方法
  ・アプリケーションの起動処理、終了処理が動作しないため速い。
  ・LaunchCommandを受信できない共有ライブラリなどでも利用可能
  ・Palm OS 5.0にはバグがあり、ARMネイティブアプリケーションが動いているときにNotificationを受信するとクラッシュすることがある。

・LaunchCommandによる方法
  ・Notification受信時にアプリケーションの起動、終了処理が動作するため、遅い。ただし、アプリケーションDBが開かれているためリソースにアクセス可能。
  ・Current Appilcation DBが自分自身に変わってしまうため、現在実行中のアプリケーションを取得できない。
  ・Launch Commandを受信できない共有ライブラリなどでは利用不可能

 どちらも一長一短があるのですが、関数のポインタによる方法はクラッシュするバグがあるため、現在はLaunch Commandによる方法を選択せざるを得ない状況です。Launch CommandによりNotificationを受信するにはSysNotifyRegisterの第4パラメータ(関数のポインタ)を0に指定します。

 次にNotificationを受信するためにMain関数を変更します。

static UInt32 KeyNotificationPalmMain(UInt16 cmd, MemPtr cmdPBP, UInt16 launchFlags)
{
  .
  .

  switch (cmd)
  {
    .
    .
    .
    case sysAppLaunchCmdNotify :
      notifyP = ( SysNotifyParamType *)cmdPBP;
      if( notifyP->notifyType == sysNotifyKeyboardDialogEvent){
        return KeyboardNotifyHandler(notifyP);
    }
      break;
    .
    .

  }

  return errNone;
}

 このようにLaunch CommandとしてsysAppLaunchCmdNotifyが送られます。Notificationの種類はcmdPBPで示されるSysNotifyParamTypeに格納されていますので、それに合わせた適切なハンドラー関数を呼び出してやります。

 最後は呼び出されるKeyboardNotifyHandlerです。

static Boolean
KeyboardNotifyHandler(SysNotifyParamType *notify)
{
  DmOpenRef db;
  LocalID dbID;

  // 既に他のHackが処理済なら何もしない
  if (notify->handled == true)
    goto EXIT;

  // Hack本体を呼び出す
  KeyboardHack();

  // 処理済フラグを立てることで実際のキーボードが起動するのを防ぐ
  notify->handled = true;

  EXIT:
    return 0;
}

基本的にはHackの処理を行うKeyobardHackを呼び出すだけですが、その前後にあるnotify->handledの扱いに注意してください。

 Notificationが処理されたかどうかはnotify->handled によって示されますが、複数のHandlerがあり、既に処理が行われた場合でも後続のHandlerは呼び出されますので、既に処理が終わっていたら何もしないようにしなくてはなりません。

 また、自分で処理を完了する場合はそれを示すためにhandledをtrueにしなくてはなりません。これを忘れると自分の処理が終わったあと標準のソフトキーボードが表示されてしまいます。

6.最後に

 OS 4用のHackがOS 5に移植できるかどうかはTrapしていたPalm OS関数に該当するNotificationが用意されているかどうかで大きく変わってきます。Data ManagerやFont ManagerなどをTrapしていたHackの実現は非常に困難ですが、ユーザインタフェースを変更するようなHackは比較的移植の実現性が高いようです。ぜひ魅力的なHackを作ってください。



copyright(C)2002 Hacker Dude-san all rights reserved.