LoginSignup
2
1

More than 3 years have passed since last update.

初めてHSPを触りつつDirect SSTPを書く

Posted at

前回

コマンドラインからのゴースト操作のために、C#のTCPでSSTPを送信するプログラムを書きました。

C#で伺か(SSP)にSSTP通信する - Qiita
C#で伺か(SSP)にSSTP通信してエミリをつついてシルフィアをなでる - Qiita

しかしながら、いちいちソケット通信をはさむ必要からか反応速度が遅く、体験が悪かったです。

そこで、より高速が望めそうなプロトコル間通信としてDirect SSTPで書き直すことを考えました。

C#は諦めた

C#かつDirect SSTPといえば前述した記事にもある
SSTPLib
が最有力です。
しかしながら、そもそもこのコードのリーディングを諦めてTCPに逃げた都合上、メモ帳でソースを読むだけでは何をしているのかさっぱりわかりませんでした。

FMOを取得するあたりまではなんとかわかったのですが、そこからバイト列を操作したりなんなりで、動く実物がないことには理解が限界でした。
高度にオブジェクト化されていることもメモ帳リーディングでの妨げになっていましたし…

HSP版が読みやすい

そこでなんとかDirect SSTPの情報をネットからあさっていると
HSPでDirectSSTPを送信する | すくりや
HSPでDirectSSTPの送信結果を表示する | すくりや
を見つけました。
これのいいところは実物のコードと単体で動作するexeファイルが同梱されていることです。

しかもウィンドウ関連を入れても総行数200未満と大変読みやすそう。

ということで、サンプルをコピペしながら動作を理解することにしました。

HSPの開発環境を作る

ただ、HSPのプログラミング環境を作る必要が私の勝手なラインの
「プログラミング言語をインストールしない」
に抵触するかが謎でした。
C#で言うとVisual StudioはNG。
csc.exeはセーフ。
という謎ライン。

HSPはどうかというと、

HSP3 概要#INSTALL

インストーラーを起動するか、または任意のディレクトリにhsp34フォルダを解凍する ことで使用できるようになります。(推奨ディレクトリは、「c:\hsp34」です)
...
5. アンインストール
フォルダをまとめて削除することでアンインストールすることができます。
標準のスクリプトエディタは、標準で以下のレジストリに設定を保存します。必要な場合は、こちらの削除も行なってください。
...
HKEY_CURRENT_USER\Software\OnionSoftware\hsed3_3

とかなりポータブルで許容範囲内かなという感じでした。

また、極々簡単なコードの場合は、HSP製のサードパーティのCLI用コンパイラなどを取得すれば、HSP本体は不要でexeの作成までできるので、かなり依存性が少ないと感じました。

(実際にはWin32を扱うための機能だったりが必要だったためHSP本体をダウンロードし、必要なライブラリ(.asファイル)のみを作成プログラムの同一ディレクトリにコピーしincludeを通しました。
なのでhsp351\commonを必要としただけで、
他のファイルや標準のエディタなどは一切起動しませんでした)

HSPのコンパイラはいろいろあるようですが、今回は更新日時が近かった
hspc - SoupSeed
を利用しました。

独立ファイル群を用意するだけでメモ帳開発ができるHSPはそこそこ手軽かなという印象です。

サンプルを動かす

まずはコードで明示的にincludeされているモジュールを集めます。

kernel32.as
mod_regexp.as
user32.as
これらはHSP本体に付属しているのでダウンロードしてcommonディレクトリからコピーすればOKです。
役割はWindowsのプロセス間通信と正規表現のためのモジュールであることは名前から連想できると思います。

マクロを読み込む

コードでincludeしている3つのモジュールを読み込んだだけではまだエラーがでます。
何の変哲もない以下のコード

for i, 1, length(sharedMemVal), 1
;   hogehoge
next

がエラーになります。
HSPにはなんとfor構文がないようです。
あるのはrepeatなどで、上記を書き換えると

repeat length(sharedMemVal) - 1
    i = cnt + 1
loop

このようになります。
ではforはなにかというと、

HSP3 プログラミング・マニュアル#QUICK_START

繰り返しの記述は通常、repeat~loop命令で行ないます。これは、C言語のfor、while、doを 簡略化したものと捉えることができます。 この他にも、while~wend、do~until、for~next、switch~caseなどC言語ライクなマクロ命令が用意されています。 詳しくは、「標準マクロ定義ファイル」を参照してください。

ということでマクロとして用意されており、その定義ファイルを読み込まないと動作しないわけです。

HSP3 プログラミング・マニュアル#HSPDEF
から、ファイルはhspdef.asであり、明示的にincludeしなくとも直下ディレクトリを探索されることがわかるので、これも設置しましょう。

前述のようにサンプルコードのforrepeatにすることも検討したのですが、それを解決したところ依存するmod_regexp.asでもforが使われていることが判明したため、素直にマクロを読み込みました。

バッチ化に向けて削る

サンプルが動けばあとはウィンドウ関連のコードなどを削っていけばいいと思います。
ウィンドウを完璧に消すには
#packopt hide 1
を追加してコンパイルでした。
ただプリントデバッグには便利なので最後に付けるといいかもしれません。

ここで、参考コードのgetSakuraFMOの返り値を見てみます。

ウインドウハンドル取得方法
filemapping object "Sakura" からデータを取得することで確実にウインドウハンドルを取得できます。メモリオブジェクトの文書を参照して下さい。

内容は「メモリオブジェクトの文書」と同一なのですが、

#defcfunc getSakuraFMO
    MAP_OBJ_NAME_SAKURA = "Sakura"
    sharedMemSize = 1024 * 64
    OpenFileMapping FILE_MAP_READ,FALSE,MAP_OBJ_NAME_SAKURA
    hMapObjSakura = stat
    if (hMapObjSakura==0) {
        return ""
    }

はまだわかりますが
その後の

    MapViewOfFile hMapObjSakura,FILE_MAP_READ,0,0,sharedMemSize
    sharedMemPtr = stat
    dupptr sharedMemVal, sharedMemPtr, sharedMemSize
    sfmo = ""
    for i, 1, length(sharedMemVal), 1
        l = sharedMemVal(i)
        b1 = (l & 0x000000FF) >> 0
        b2 = (l & 0x0000FF00) >> 8
        b3 = (l & 0x00FF0000) >> 16
        b4 = (l & 0xFF000000) >> 24
        sfmo += strf("%c%c%c%c", b1, b2, b3, b4)
    next

の仕組みがわかっていない状態ですね…
ともかく出力を見ると、私の場合は

sakura.png

こんな感じです。
Direct SSTPは
「ウインドウメッセージを伺かのプロセスにSSTPを送ればいいよ」
と読めますが、実際には

  1. Sakuraのファイルマッピングオブジェクトを読み込む?
  2. 読み込んだファイルマッピングオブジェクトをマップビューとして開く?
  3. マップビューの内容をバイト列操作をしながらstringに書き出す
  4. 書き出した中に格納されているhwnd(ウィンドウハンドル)の数値を抜き出す
  5. hwndの数値に対してメッセージをSendMessageする

という手順を踏むようです。
そしてそのあたりに伺かとしてのサポートはないので単純なC、というかWin32API周りの知識を身につける必要がありそうです。

遠回りしましたが、そんなこんなでシェイプアップしたsample.hspがこちらです(開発中)

;#packopt hide 1
; テスト
#include "dsstphsp_1_2_0/dsstp.hsp"
#define WM_COPYDATA     0x004A

#include "mod_regexp.as"
#module
; "hwnd"とか指定するとhwndの配列が返ってくる 
#deffunc getParamsFromSakuraFMO str param, str sfmo, array ret
    re = "\\." + param + strf("%c", 1) + "([^\\r\\n]+)"
    matches result, sfmo, re, 0, 0, 0
    i = 0
    repeat stat
        split result(cnt), strf("%c", 1), r
        ret(i) = r(1)
        i += 1
    loop
    return
#global


oncmd gosub *OnCopyData, WM_COPYDATA

sdim msg, 4096
msg = {"
NOTIFY SSTP/1.1
Charset: Shift_JIS
Sender: HSP
Event: OnDive
Reference0: 道頓堀
Script: \\h\\s[5]どぼ〜ん。\\w9\\w9\\u\\s[11]…\\w5…\\w5…\\w5…\\w5…。\\e
Option: notranslate
HWnd: 0"}

sdim s, 4096
poke s, 0, msg

sfmo = getSakuraFMO()
mes sfmo

getParamsFromSakuraFMO "hwnd", sfmo, hwnds

hwndTarget = int(hwnds(0))
sendDSSTP hwndTarget, s
mes stat
wait 1000
end

*OnCopyData
    dupptr cds, lParam, 12
    data_id   = cds(0)
    data_size = cds(1)
    dupptr data_ref, cds(2), data_size
    if data_id == 9801 {
        sdim data, data_size/4
        memcpy data, data_ref, data_size
        mes data
    }
    dim cds, 1
    dim data_ref, 1
    end

ゴーストを複数立てないならhwndlsitではなく伺か本体のhwndでよく、任意のフィールドを引っ張るgetParamsFromSakuraFMOではなくhwnd狙い撃ちの抜き出し関数にすればもっとスリムになりそうですね。

あとはこれは引数駆動にしてメッセージを変動させれば、コマンド用DirectSSTPの完成ですね。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1