SSブログ
プログラム ブログトップ
前の5件 | -

BSAVE / SAVEM 前史 #3 [プログラム]

2010/03/19 プログラム解説部分を加筆修正しました

この記事は「BSAVE / SAVEM 前史 #2」の続きです。


セーブしたいデータ個数分だけループを回す方法だと、データ量が多くなればなるほど時間が掛かります。
しかも、ループ内でやっている事と言えば、メモリ上にあるデータをバッファ内に、そっくりそのままコピーするだけです。

バッファへデータをコピーするなら、マシン語を使用してブロック転送をする方法もあるのですが、BSAVE をするプログラムが BLOAD を必要とするというのも…。
かと言って、プログラム内にデータ文でマシン語を格納するとプログラムが肥大化してしまいます。

そこで、ちょっとトリッキーな方法ですが VARPTR() を使用した方法にトライしてみます。




N-BASIC には VARPTR() という関数があり、変数の値が保存されているアドレスを調べることができます。
数値変数では、そのアドレスに整数型/単精度型/倍精度型それぞれが2/4/8バイトで保存されています。
文字列変数だけは特別で、VARPTR() で返されるアドレス AD に文字数、AD+1, AD+2 に実際に値が保存されているアドレス(ポインタですね)が保存されています (この 3 bytes をまとめてストリングディスクリプタと呼んでいます)。

このストリングディスクリプタを操作すれば、コピーを繰り返すことなくバッファにデータセットできるはずです。

DEFINT A-Z
T1$="":T2$=""
FIELD #1, 128 AS D1$, 128 AS D2$
    |
FOR AD!=BA! TO EA! STEP 256
    |
  POKE VARPTR(T1$)  ,128
  POKE VARPTR(T1$)+1,PEEK(VATPTR(A1))
  POKE VARPTR(T1$)+2,PEEK(VATPTR(A1)+1)
  POKE VARPTR(T2$)  ,128
  POKE VARPTR(T2$)+1,PEEK(VATPTR(A2))
  POKE VARPTR(T2$)+2,PEEK(VATPTR(A2)+1)
  LSET D1$=T1$:LSET D2$=T2$
  PUT #1,RC:RC=RC+1
    |
NEXT
CLOSE #1

1レコードは 256 bytes なのですが、文字列変数には 255 bytes までしか入れられないので、仕方なく2つの変数に分割してあり、似たような処理が二つありますが、メインとなる処理はこれだけです。
A1, A2 の二つの整数変数にはセーブしたい領域の先頭アドレスが入っていて、T1$, T2$ の二つのストリングディスクリプタに文字数とアドレスを強制的に代入しています。
その後、LSET D1$=T1$:LSET D2$=T2$ とすることで、バッファ内へデータをコピーしています。

前回のプログラムではデータ個数分だけループを回していましたが、今回はレコード数分だけ回せばいいので結果的に処理速度は向上します。

Binary data saving sample program #2 (N-BASIC)
バイナリデータセーブ サンプルプログラム #2 (N-BASIC)


ただでさえ少ない Disk-BASIC のメモリをバイナリセーバー自身が浪費してしまわないように注力した結果出来上がったのがこのプログラムです。

前回のモノに比べるとプログラムが大きくなってしまっているのが難点ですが、配列変数を使用しなくなったので全体としてのメモリ使用量は減っています。
前回のように文字列型の配列変数を 256 個定義すると、ストリングディスクリプタだけで 768 bytes もメモリを消費してしまいますから。
N-BASIC に unsigned int 変数型が用意されていれば、もう少しプログラムサイズも小さくなるのでしょうがね…。

ちなみに、掲載した N-BASIC のプログラムはデバッグ済みですので問題なく利用していただけるはずですが、ご使用は自己責任でお願いします。

おわり

タグ:N-BASIC PC-8001

BSAVE / SAVEM 前史 #2 [プログラム]

2010/03/18 タイトルを変更しました

この記事は「BSAVE / SAVEM 前史 #1」の続きです。


シーケンシャルファイルだと収納効率が悪いので、ランダムファイルを使用した方法を考えてみます。
PEEK() 関数で読み込んだデータを FIELD 命令を使ってバッファにセットするというのはどうでしょう。

ファイルバッファのサイズは 256 bytes 固定なので、256 個の要素を持つ整数配列を使用してその中に格納するようにします。

FOR AD!=BA! TO EA!
    |
  FOR I=0 TO 255
    D$(I)=CHR$(PEEK(AD))
  NEXT
    |
  FOR I=0 TO 255
    LSET B$(I)=D$(I)
  NEXT
  PUT #1,RC
    |
NEXT

1 byte をセットするのにループを2回繰り返すため効率が悪いので、2個のループをまとめてしまいます。
FOR AD!=BA! TO EA!
    |
  FOR I=0 TO 255
    LSET D$(I)=CHR$(PEEK(AD))
  NEXT
    |
  PUT #1,RC
    |
NEXT


この処理をコアにしてオプションパーツをコテコテと貼り付け、N-BASIC 用の MOUNT / REMOVE 処理を振りかけたモノが以下のプログラムです。

Binary data saving sample program (N-BASIC)
バイナリデータセーブ サンプルプログラム(N-BASIC)

セーブしたいデータ個数分だけループを回し、データバッファが満杯になるとディスクへ書き出しています。
でも、ループ内でやっている事と言えば、メモリ上にあったデータがバッファ内に、そっくりそのままコピーされただけなんですよね。
コピーするだけなら、もっと賢い方法があるんじゃないかという事で、次回に続きます。

つづく

タグ:N-BASIC PC-8001

BSAVE / SAVEM 前史 #1 [プログラム]

2010/03/18 タイトルを変更しました

マシン語を使用したモノや BASIC のデータ領域/ワークエリア等を保存する方法として、まず最初に思いつくのはモニタモードでカセットテープに保存する方法ですが、これは速度も遅く信頼性も低いものでした。
「じゃあ、ディスクへの保存なら大丈夫だろう」と考えますが、最初期の Disk-BASIC には BSAVE / SAVEM 等のバイナリデータ保存用の命令はありませんでした。

話は横へそれますが


APPLE II は別として、国産 PC で FM-8 以前にディスクへのバイナリデータのロード/セーブが可能だったモノってありましたっけ?
そう考えると APPLE II って偉大です。

それはさておき


BASIC にバイナリデータのロード/セーブ命令が無いといってバイナリデータを扱えなかった訳ではありません。
BASIC に精通した真のプログラマは、プログラムを組んで問題を解決するものです。

まず最初に思いつくのは、メモリから PEEK() 関数を使用して読み込んだデータをシーケンシャルファイルに出力する方法ですが、これには問題があります。

BASIC のリファレンスマニュアルにはあまり詳しく書かれていないのですが、シーケンシャルファイルで扱えるデータは基本的にテキストデータだけ(',' (コンマ)を除く 20H から 7EH と 80H から FFH まで)です。
シーケンシャルファイル入力で使用する INPUT # 命令は INPUT 命令と違いコンソールからではなくファイルから入力を行います。 しかし、これはコンソールからの入力と同様にコントロールコードが有効になっているのです。
INPUT 命令では入力時に CR / ',' (コンマ) はデータの区切り/終了を意味しますが、同様に INPUT # 命令でも入力される文字列中に LF / CR / ',' (コンマ)が現れるとデータの区切りとみなされるため、データとしては無視されてしまいます。 また、00H も無視されてしまいます。

N-BASIC では、
00H(Null), 07H(Bell), 0AH(LineFeed), 0BH(Home), 0CH(Clear), 0DH(CarriageReturn), 1CH(CursorRight), 1DH(CursorLeft), 1EH(CursorUp), 1FH(CursorDown)
が、影響を受けるコードです。

そのため、影響を受けるコードを正常に読み込むには、例えば 00H → \@, 07H → \G, \ → \\ という具合にエスケープシーケンスを使用して 20H 以上のコードに変換しておく必要があります。
これだとデータを取りこぼすことなく読み込めますが、文字列変数に格納されたデータを1文字づつ取り出してチェックし、エスケープシーケンスならばコントロールコードに戻す必要があり、処理時間が多くかかります。
その上 20H 以下のデータが大量に存在するとデータの収納効率が非常に悪ってしまうので、ディスクの容量を節約するためにも、これよりもっとな方法を考えた方がいいですね。


つづく

タグ:PC-8001 N-BASIC

ちょっとしたプログラムプロテクトの話 #2 [プログラム]

ちょっとしたプログラムプロテクトの話 #1」のつづきです。




コード内のメッセージを見たい VS メッセージを覗かれたくない

この戦いに決着をつけるためのアプローチとしては、大きく分けて2つの方法が採られました。

ひとつは、リストを見られないように BASIC プログラム自体に細工をすること。
もうひとつは、コード内にあるメッセージを「暗号化」することでした。




ひとつめのアプローチは BASIC プログラム自体に細工をすることです。

あるゲームではリストの各所に REM 文が挿入してあり、コメントの先頭文字が「CLR(画面消去)」になっていました。 リストを取ると頻繁に画面消去が繰り返され、内容を確認する事がとても困難になります。

また、あるゲームでは 'P' オプション付きでセーブされているのですが、ワークエリアを操作して 'P' オプションを解除しても、リストを取ると特定の場所の表示時に「暴走」するような仕掛けがしてありました。
リスト中に「…:PC=98」という記述があり、この部分に細工がしてありました【PC-9801版 大○○2】

どちらの場合も、「MON」命令が無効化されていたのですが、無効化を解除した後でモニタから直接 BASIC のテキストを操作する事で破られてしまいました。




様々な方法で BASIC のテキストを「不可視化」する試みが行われましたが、ことごとく破られてしまうためか、この後テキスト自体を不可視化することはあまり行われなくなります。

これは BASIC の不可視化よりもディスク自体のプロテクトにウェイトが置かれるようになったのと、C コンパイラでの開発が主流になってきたのが原因と思われますが、真相は定かではありません。




ふたつめのアプローチは、コード内にあるメッセージを暗号化することでした。

これは現在でも使用されている方法です。 もちろん当時よりは高度な暗号化技術が使われているので「暗号強度」は比べるまでもありませんが…。


最初に用いられた暗号化は、「シーザー暗号」と呼ばれる、単純に
'IBM' → 'HAL'
'シンブンシ' → 'スアベアス'
という具合にキャラクタコードをずらしただけのものでした。 しかしこれだと、表示されたメッセージと比較する事で簡単に見破られてしまいます。

また、メッセージ全体を暗号化すると、
'ココ ニハ ナニ モ ナイ' → 'スス#ノヘ#ネノ#ヨ#ネオ'
と、単語の区切りに使用する「スペース」が、特定の文字(この場合は '#' )に変換されることから、元のメッセージとの対応が容易に推測されてしまいます。
(この場合は ' '(0x20) '#'(0x23) が ASCII コードでは +3 の違いなので、
<暗号化された文字> - 3 = <元の文字>
という推測が簡単に導けます。)




雑誌投稿や同人系のゲームでは、単純な「シーザー暗号」でも問題はないでしょうが、パッケージで発売されるゲームとなると、もう少し複雑な仕組みでないと「技術力が低い」と馬鹿にされてしまいます。
そこで「シーザー暗号」よりも、もう少し高度な「ビット反転」が使われます。
ビット反転だけだと見破られてしまう可能性があるのですが、暗号化/復号化の両方で同じルーチンが使える利点があります。


Encrypt_sample
メッセージ暗号化ルーチン サンプル(MSX)

これは、友人に頼まれて作った「簡易暗号化ルーチン」の復刻版(!)です。
このままだとリストが見難いので、テキストに直したものを掲載しておきます。
list
10 DEFINT A-Z
20 A$="ここには Shield が おいてあります"
30 FOR I=1 TO LEN(A$)
40  C=ASC(MID$(A$,I,1))
50  IF C>&H85 THEN E=C XOR &H5C ELSE IF C>&H40 THEN E=C XOR &H1F 
    ELSE E=C
60  PRINT CHR$(E);
70 NEXT
Ok
run
ニニコカ Lwvzs{ ハ♣ ノホソヘ、ウチ
Ok


この暗号化の利点は、単純なので CPU パワーを食わないことと、アルファベット/記号部分(0x21 - 0x7f)とひらがな/カタカナ部分(0x86 - 0xfd)の反転させるビットを違うパターンにする事で、解読され難くなっている事です。

欠点としては、
'`' 0x60 → 0x7f 「削除」
'「' 0xa2 → 0xfe 「未定義文字」
'」' 0xa3 → 0xff 「カーソル表示」
' ' 0x90 → 0xcc 'フ'
' ' 0xa0 → 0xfc 'わ'
と変換されるため、BASIC でプログラミングする場合、ルーチンをこのまま使用すると
「バッククォート(0x60)
「山括弧(0xa2 - 0xa3)
'フ'
'わ'
をメッセージ中に使用できなります。
そのため BASIC で上記の文字を表示させるには例外処理を追加する必要があります。

また、例外処理を追加する事で暗号化されたメッセージに「迷彩」が施され、単純なビット反転だけではなくなるので、逆に破られにくくなる利点もあります。




BASIC でゲームを作る時代は瞬く間に過ぎ去り、C やマシン語での開発も汎用ツール(ゲームエンジン/スクリプトエンジン)を使う方式へと代わりました。
今では「暗号化」はエンジンの一機能として存在していますが、ほとんど誰も気に留めることはありません。

コード内のメッセージを見たい VS メッセージを覗かれたくない

たぶん永遠に続くであろう戦いの「始まりの時代」のお話でした。


(参考)
Wikipedia: 単一換字式暗号
Wikipedia: シーザー暗号
Wikipedia: ゲームエンジン
Wikipedia: スクリプトエンジン (ゲーム)

ちょっとしたプログラムプロテクトの話 #1 【追記あり】 [プログラム]

2010/02/22 初期のプロテクト例を追記

日参している「何とか庵日誌」。
MSX の投稿雑誌打ち込みプログラムでの「ささやかな暗号化の話」が取り上げられていたので、便乗記事です^^;



アドベンチャーやロールプレイングのネタバレやシミュレーションでの「ズル」を防ぐにはプログラムテキストの「不可視化」、表示されるメッセージの「暗号化」が必要ですが、黎明期のゲームでは全く対処していないモノも多くありました。 BASIC でメインプログラムが組まれていた頃は、リストを表示すれば全部バレてしまうモノがほとんどでした。




FM-8/7 の F-BASIC には「UNLIST」命令があり、これを実行すると「LIST」命令を実行しても表示されなくなります。
そのうち、この機能を使ってリスト表示を「阻止」するモノが現れてきました。
しかし、「MON」命令を無効にしなかったため、直接メモリ内に値を書き込んで「UNLIST」を無効にしてしまえば、表示できてしまいました。

F-BASIC の DISK VERSION には「SAVE」命令に 'P' オプションが用意されており、オプション付きでセーブされた BASIC プログラムだと「LIST」命令を実行しても 'Protected Program' と表示され、リストが表示できません。 この場合「POKE」命令も無効にされていますが、「MON」命令は使えるので「UNLIST」と同様に解除できてしまいました。
'P' オプションを付ける事でセーブされたプログラム自体は暗号化されて保存されますが、これでは何も役に立ちません。

ゲームを作る側も、何とか「プロテクト外し」を阻止しようと「技術の向上」で対処します。
「POKE」「MON」命令を無効にしてワークエリアを書き換えられないようにしてしまいました。
しかし、これもあらかじめ 'P' オプションのワークエリアをクリアしたものを「SAVEM」命令でセーブしておき、BASIC プログラムをロードした後に「LOADM」命令でロードする事で、簡単に突破されてしまいました。

2010/02/22 追記ここから

BASIC の内部が解析され色々な情報が明らかになってくると、Disk-BASIC を使用しないテープ版のプログラムでも凝ったプロテクトの物が登場してきました。
LIST 命令処理ルーチン内の「未処理トラップ」を操作して、LIST 命令を実行しても「Ok」だけが表示されるようにして「不可視化」を実現していました。
このプロテクトも上記の方法で「MON」命令の無効化を解除した後、通常は使われていない未処理トラップの中から書き換えられているトラップを探して元に戻す事で、破られてしまいました。

2010/02/22 追記ここまで




FM-8 の後に発売された PC-8801 の N88-Disk BASIC にも F-BASIC と同等の機能が装備されていました。
Disk-BASIC では 'P' オプション付きでセーブされた BASIC のプログラムをロードすると、スクリーンエディタでのプログラムの追加/変更/削除ができません(「DELETE」だけは可能ですが、意味がありませんね)。
また、ダイレクトモードから「LIST」「MON」「POKE」「BLOAD」「CALL」「USR()」が実行できなくなります。
この辺りには、F-BASIC での教訓が生かされているのでしょう。

FM-8 では通用した『あらかじめ 'P' オプションをクリアした状態を「BSAVE」命令でセーブしておき「BLOAD」命令でロードする』もブロックされてしまいます。

そこで考え出されたのが、「コマンド拡張法」です。
将来の拡張用に用意された命令(例えば「IRESET」)を拡張し、'P' オプションのワークエリアをクリアするようにアセンブラでマシン語を組みます(真のプログラマは、直接メモリに16進コードで打ち込みます)。
例えば F320 番地から、21 AD EE 11 30 F3 3E C3 77 23 …(以下は自重)
これを実行すると「IRESET」命令が 'P' オプションのワークエリアをクリアできるように設定されます。
あらかじめ通常の BASIC で起動して「IRESET」命令を使用できるようにしておきます。
プロテクトを解除したいプログラムをロードし「IRESET」命令を実行します。後は同じファイル名で通常('P' オプションをつけない)のセーブをすれば BASIC プログラムのプロテクトは解除されます。
「IRESET」はダイレクトモードから実行してもブロックされない命令だというのがミソです。




コード内のメッセージを見たい VS メッセージを覗かれたくない

この戦いに決着をつけるためのアプローチとしては、大きく分けて2つの方法が採られました。

つづく
前の5件 | - プログラム ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。