はじめに
さて、前回はMSX-BASIC経由でファイル転送を受けるためのbase64デコーダをBASICで作成しました。
その後、SCREEN5画像を転送しようと数百行を1行ずつコピペを試みて途中で挫折したわけですが、今回は強い味方、Pythonの兄貴に登場していただき、退屈なコピペ作業から何から何まで一気に片付けてもらいましょう!
2023/10/29改訂・・・ 接続先がMSX-DOSの場合への対処を追加しました。
実装コード「msx0babaput.py」
プログラムは以下のような流れでファイルを転送します。
1.MSX0のBASIC画面にtelnet接続
DOS画面だったら、BASIC画面に変更
2.newコマンドで初期化し
3.basicプログラムを流し込み
4.runコマンドで流し込んだBASICプログラムを実行
5.実行したBASICプログラムは、入力待ち状態で待機
6.そこに指定されたファイルをbase64でエンコードして、76文字ずつ送信する
7.入力を受けたBASICプログラムはbase64でデコードし結果をファイルに書き込み、また入力待ち状態で待機
8.ファイルをすべて送信終わったら、終了文字「`」を送信する
9.「`」を受けたBASICプログラムはファイルをクローズして終了
10.実行にかかった時間を表示し、終了
import sys
import telnetlib
import time
import re
import base64
import os
def is_valid_filename(filename):
# ファイル名が8文字+拡張子3文字であるかどうかをチェックする正規表現
pattern = r'^[A-Za-z0-9_]{1,8}\.[A-Za-z0-9_]{3}$'
return re.match(pattern, filename)
def execute_full_process_telnetlib(host, program, message_file_path):
start_time = time.time()
message_file_name = os.path.basename(message_file_path)
if not is_valid_filename(message_file_name):
return "Invalid file name format. ",0
try:
# Telnet接続
print("msx0babaput started connecting to msx0")
tn = telnetlib.Telnet(host, 2223)
# DOSモードで起動している場合、BASICインタープリタモードに切り替える
tn.write(b'\r\n')
response = tn.read_until(b'A>', timeout=1) # 1秒以内にプロンプトが表>示されない場合はタイムアウト
if b'A>' in response:
tn.write(b'basic\r\n') # BASICインタープリタモードに切り替え
tn.read_until(b'Ok')
sys.stdout.write("Start initilizing.")
sys.stdout.flush()
tn.write(b'new\r\n')
tn.read_until(b'Ok')
sys.stdout.write("\rStart initilizing..Done.")
sys.stdout.flush()
print()
sys.stdout.write("Start building base64decoder on msx0 basic.")
sys.stdout.flush()
program = program + '\r\n20 LET F$ = "' + message_file_name + '"\r\n'
# BASICプログラムをサーバに送信
tn.write(program.encode('ascii') + b"\r\n")
tn.write(b'run\r\n')
tn.read_until(b'?')
sys.stdout.write("\rStart building base64decoder on msx0 basic..Done.")
print()
print("Start Transferring a target file to msx0 basic")
# メッセージをBase64エンコードして送信
with open(message_file_path, 'rb') as file:
messages = file.read()
base64_message = base64.b64encode(messages).decode('ascii')
total_chunks = len(base64_message) // 76
for i in range(0, len(base64_message), 76):
tn.write(base64_message[i:i+76].encode('ascii') + b"\r\n") # 改行コードをCR+LFに変更
tn.read_until(b'?')
# インジケータ表示
sys.stdout.write("\rTransferring: {}/{} chunks".format(i//76, total_chunks))
sys.stdout.flush()
print()
end_time = time.time()
execution_time = end_time - start_time
# "`"を送信し、BASICプログラムを終了
tn.write(b'`\r\n')
tn.read_until(b'Ok')
result="Done"
return result, execution_time
except Exception as e:
return str(e)
finally:
# Telnet接続を閉じる
tn.close()
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: python msx0babaput.py <host> <message_file_path>")
sys.exit(1)
host = sys.argv[1]
message_file_path = sys.argv[2]
# BASICプログラム
basic_program = '''
10 DEFINT A-Z:DIM F$, I$, O$, B$, T$, C$, B(4), OB(3):CLEAR 10000\r\n
20 LET F$ = "OUTPUT.BIN"\r\n
30 ON ERROR GOTO 9000:KILL F$\r\n
40 S=57:OPEN F$ AS #1 LEN=S\r\n
50 INPUT ":"; I$\r\n
60 IF I$="`" THEN CLOSE #1:END\r\n
70 GOSUB 100 'CALL Base64 decoder (I$:Input Text, O$:Output Text, B$:Base64 table)\r\n
80 GOSUB 1000'CALL SAVE(O$:decoded text)\r\n
90 GOTO 50\r\n
100 'Base64 decoder (I$:Input Text, O$:Output Text, B$:Base64 table)\r\n
110 LET O$ = ""\r\n
120 LET B$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\r\n
130 FOR I = 1 TO LEN(I$) STEP 4\r\n
140 T$ = MID$(I$, I, 4)\r\n
170 FOR J = 1 TO 4\r\n
180 B(J) = 0\r\n
190 NEXT J\r\n
200 FOR J = 1 TO 4\r\n
210 B(J) = INSTR(B$, MID$(T$, J, 1)) - 1:IF B(J) < 0 THEN B(J)=0\r\n
220 NEXT J\r\n
230 OB(1) = B(1) * 4 + B(2) \ 16\r\n
240 OB(2) = (B(2) AND 15) * 16 + B(3) \ 4\r\n
250 OB(3) = (B(3) AND 3) * 64 + B(4)\r\n
260 FOR J = 1 TO 3\r\n
270 O$ = O$ + CHR$(OB(J))\r\n
280 NEXT J\r\n
290 NEXT I\r\n
300 LET O$ = LEFT$(O$, LEN(O$) + (MID$(T$, 3, 1) = "=") + (MID$(T$, 4, 1) = "="))\r\n
310 RETURN\r\n
1000 'SAVE APPEND(O$:decoded text)\r\n
1010 IF LEN(O$)<S THEN 1100 \r\n
1020 FIELD #1,INT(S) AS T$\r\n
1030 LSET T$=O$:PUT #1\r\n
1040 RETURN\r\n
1100 CLOSE#1\r\n
1110 S=1:OPEN F$ AS #1 LEN=S\r\n
1120 F=LOF(1)\r\n
1130 FIELD #1,1 AS T$\r\n
1140 FOR I=1 TO LEN(O$)\r\n
1150 LSET T$=MID$(O$,I,1)\r\n
1160 PUT #1,F+I\r\n
1170 NEXT I\r\n
1180 RETURN\r\n
9000 IF ERL=30 AND ERR=53 THEN RESUME 40\r\n
9100 ON ERROR GOTO 0\r\n
'''
# 全プロセスを実行
result, execution_time = execute_full_process_telnetlib(host, basic_program, message_file_path)
print(result)
print("Execution time: {} seconds".format(execution_time))
実行
さて、実行してみましょう。
クライアントPC側は、Python3を使用します。開発時はWSL上のPython3.5を使用しました。MSX0側はMSX-BASICを起動しておきます。MSX-DOSだと初期化で止まります。実行時はMSX-BASICを初期化して、プログラムを流し込みますので、保存されていないBASICプログラムなどありましたら、保存しておいてください。
【免責】十分な試験を行っていませんので、まだまだバグが含まれていると思います。その点を理解いただいての使用をお願いします。実行した結果についての責任は一切負えません。
送付ファイル
じゅげむの一節をWikipediaからコピペしてmessages.txtとして保存しておきます。
じゅげむじゅげむ ごこうのすりきれず かいじゃりすいぎょの すいぎょうばつ ふうらいばつ くねるところにすむところ やーぶらこうじのぶらこうじ ぱいぽぱいぽ ぱいぽのしゅーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴー ぽんぽこなーの ちょうきゅうめいのちょうすけ
実行コマンド
MSX0のIPアドレスと送信ファイル名を引数として渡します。サンプルではMSX0のIPアドレスが192.168.0.4、ファイル名がmessages.txtになります。
python3 msx0babaput.py 192.168.0.4 messages.txt
実行結果
実行結果、以下のような出力となります。
$ python3 msx0babaput.py 192.168.0.4 messages.txt msx0babaput started connecting to msx0
Start initilizing..Done.
Start building base64decoder on msx0 basic..Done.
Start Transferring a target file to msx0 basic
Transferring: 7/7 chunks
Done
Execution time: 30.669430017471313 seconds
427バイトの送信に30秒、遅い!
実行結果確認
さて、正常に送信できたか確認してみます。
以下のツールを使用します。msx0baview.pyです。
$ python3 msx0baview.py 192.168.0.4 messages.txt
10 CLEAR 1000
20 LET F$ = "MESSAGES.TXT"
30 OPEN F$ FOR INPUT AS #1
40 FOR I = 1 TO 9999
50 LINE INPUT#1, LI$
60 PRINT LI$
70 IF EOF(1) THEN 90
80 NEXT I
90 CLOSE #1
99 END
20 LET F$ = "messages.txt"
run
じゅげむじゅげむ ごこうのすりきれず かいじゃりすいぎょの すいぎょうばつ ふうらいばつ くねるところにすむところ やーぶらこうじのぶらこうじ ぱいぽぱいぽ ぱいぽのしゅ
ーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴー ぽんぽこなーの ちょうきゅうめいのちょうすけ
Ok
Execution time: 4.0685014724731445 seconds
じゅげむじゅげむ・・・、無事転送されてそうですね。成功です。
最後に
転送速度が遅いですよね。
この問題を解決してくれるのが、前々回の記事への@fujitanozomu(藤田 望)さんコメントで記載いただいた↓プログラム、機械語実装です。
10 DEFINTA-Z
15 CLEAR300,&HDFFF:FORI=0TO81:POKE&HE000+I,VAL("&H"+MID$("EB7E235E2356424BF5C50604298F298F298F298F298F298FF51A13FE2B2818FE2F2819FE3A3808FE5B3808C6B9180FC604180BC6BF18073E3E3718023E3FB56FF110C9C102037C02037D0203F1D60420B7C9",2*I+1,2)):NEXT:DEFUSR=&HE000
20 INPUT "Please input the text using base64:"; I$
30 O$=LEFT$(USR(I$),(LEN(I$)\4)*3)
40 PRINT "Result:"; O$
50 END
このプログラムを組み込んで実行した結果は↓。10秒ちょいで終わります。
$ python3 msx0babaput3.py 192.168.0.4 messages.txt
msx0babaput started connecting to msx0
Start initilizing..Done.
Start building base64decoder on msx0 basic..Done.
Start Transferring a target file to msx0 basic
Transferring: 7/7 chunks
Done
Execution time: 10.110921144485474 seconds
BASICプログラム部分を以下に置き換えることで簡単に導入できます。皆さんも試してみてください。
basic_program = '''
10 DEFINTA-Z:DIM F$, I$, O$:CLEAR 10000,&HDFFF\r\n
15 FORI=0TO81:POKE&HE000+I,VAL("&H"+MID$("EB7E235E2356424BF5C50604298F298F298F298F298F298FF51A13FE2B2818FE2F2819FE3A3808FE5B3808C6B9180FC604180BC6BF18073E3E3718023E3FB56FF110C9C102037C02037D0203F1D60420B7C9",2*I+1,2)):NEXT:DEFUSR=&HE000\r\n
20 LET F$ = "OUTPUT.BIN"\r\n
30 ON ERROR GOTO 9000:KILL F$\r\n
40 S=57:OPEN F$ AS #1 LEN=S\r\n
50 INPUT ":"; I$\r\n
60 IF I$="`" THEN CLOSE #1:END\r\n
70 GOSUB 100 'CALL Base64 decoder\r\n
80 GOSUB 1000'CALL SAVE\r\n
90 GOTO 50\r\n
100 'Base64 decoder(I$:encoded text, O$:output text)\r\n
110 L=LEN(I$):O$=LEFT$(USR(I$),(L \ 4)*3+(MID$(I$,L-1,1)="=")+(MID$(I$,L,1)="="))\r\n
200 RETURN\r\n
1000 'SAVE APPEND(O$:decoded text)\r\n
1010 IF LEN(O$)<S THEN 1100 \r\n
1020 FIELD #1,INT(S) AS T$\r\n
1030 LSET T$=O$:PUT #1\r\n
1040 RETURN\r\n
1100 CLOSE#1\r\n
1110 S=1:OPEN F$ AS #1 LEN=S\r\n
1120 F=LOF(1)\r\n
1130 FIELD #1,1 AS T$\r\n
1140 FOR I=1 TO LEN(O$)\r\n
1150 LSET T$=MID$(O$,I,1)\r\n
1160 PUT #1,F+I\r\n
1170 NEXT I\r\n
1180 RETURN\r\n
9000 IF ERL=30 AND ERR=53 THEN RESUME 40\r\n
9100 ON ERROR GOTO 0\r\n
'''
現場からは以上です!