UWSCのみでFUKIDASIをカラフルにする

DLLを使わずXP対応した、カラフルFUKIDASI決定版。
いやはや、可能だったとは、、、。



スクリプト

一応、Asmモジュールもつけてますが、
UWSCにできないことはない? - じゅんじゅんのきまぐれ
と同じなので、それのインクルード(CALL)でも可。

OPTION EXPLICIT

DIM fcs[] = $FF0000, $C00040, $800080, $4000C0, $0000FF
DIM msg="吹き<#CR>出しで", bc=$00FF00, al=0
Fukidashi.Show(fcs, msg, G_MOUSE_X, G_MOUSE_Y, 0, 16, "MS ゴシック", bc, al)
MSGBOX("OK?")
Fukidashi.Show(fcs, msg, G_MOUSE_X, G_MOUSE_Y, 1, 32, "MS Pゴシック", bc, al)
MSGBOX("OK?")
Fukidashi.Show(fcs, msg, G_MOUSE_X, G_MOUSE_Y, 2, 8, "MS P明朝", bc, al)
MSGBOX("OK?")
Fukidashi.Show(fcs, msg, G_MOUSE_X, G_MOUSE_Y, 3, 10, "MS 明朝", bc, al)
MSGBOX("OK?")
Fukidashi.Show(fcs, msg, G_MOUSE_X, G_MOUSE_Y, 4, 72, "MS ゴシック", bc, al)
MSGBOX("OK?")

Fukidashi.Dispose()	// 使い終わったら必ず必要


MODULE Fukidashi
	DIM _loop, _addr, _para, _event, _hwnd

	// FUKIDASIとほぼ同じ引数。テキスト色が配列で先頭で省略不可
	PROCEDURE Show(fcs[], msg=EMPTY, x=0, y=0, dir=0, pt=10, font="MS Pゴシック", bc=$FFFF, al=0)
		DIM fc = 0, fcnum = LENGTH(fcs)
		IF fcnum > 0 THEN fc = fcs[fcnum-1]
		IFB _hwnd THEN
			FUKIDASI()
			_loop = FALSE
			SLEEP(0.01)
			WHILE !_loop
				SLEEP(0.01)
			WEND
		ENDIF
		FUKIDASI(msg, x, y, dir, pt, font, fc, bc, al)
		IF LENGTH(msg) THEN THREAD _Hook(fcs, msg, x, y, dir, pt, font, bc, al)
	FEND

	PROCEDURE Fukidashi
		DEF_DLL CreateEventA(DWORD,bool,bool,string): DWORD: kernel32

		_loop = TRUE
		_hwnd = 0

		DIM code = "VYvsUYtFCIlF/ItN/IN5IAB1IotV/IN6FAB0GYtF/IPAMFBqAGoCi038i1EU/9KLTfyJQSCL"
		code = code + "VfyDeiAAdSSLRfyDeBAAdBuLTfyDwTBRagBqAGoAi1X8i0IQ/9CLTfyJQSCLVfyLQghQavyL"
		code = code + "TfyLUQRSi0X8iwj/0YtV/IlCDItF/ItADIvlXcIEAMzMzMzMVYvsUYtFCIlF/ItN/ItRDFJq"
		code = code + "/ItF/ItIBFGLVfyLAv/Qi038iUEIi1X8g3okAHQVagBqAGoAi0X8i0gEUYtV/ItCJP/Qi038"
		code = code + "g3kgAHQYi1X8g3ocAHQPi0X8i0ggUYtV/ItCHP/Qi038i0EIi+VdwgQAzMzMzMzMzMzMzMzM"
		code = code + "zMxVi+xRx0X8EREREYtF/ItNDDtILHUhi1X8g3oYAHQYi0X8g3ggAHQPi038i1EgUotF/ItI"
		code = code + "GP/Ri1UUUotFEFCLTQxRi1UIUotF/ItIDFGLVfyLQij/0IvlXcIQAMzMzMw="
		Asm.Asm	// 何故か初期化されないので入れとく
		_addr = Asm.Set(code)
		_para = Asm.Alloc(80)
		DIM eventName = "JuneColorFukidashiEvent", i = 0
		IFB _addr > 0 AND _para > 0 THEN
			Asm.SetDword(_addr + $117, _para)
			i = i + Asm.SetDword(_para + i, Asm.GetProcAddress(Asm.hU32, "SetWindowLongA"))
			i = i + 4	// hWnd
			i = i + Asm.SetDword(_para + i, _addr + $110)
			i = i + 8
			i = i + Asm.SetDword(_para + i, Asm.GetProcAddress(Asm.hK32, "OpenEventA"))
			i = i + Asm.SetDword(_para + i, Asm.GetProcAddress(Asm.hK32, "SetEvent"))
			i = i + Asm.SetDword(_para + i, Asm.GetProcAddress(Asm.hK32, "CloseHandle"))
			i = i + 8
			//i = i + Asm.SetDword(_para + i, Asm.GetProcAddress(Asm.hU32, "SendMessageA"))
			i = i + Asm.SetDword(_para + i, Asm.GetProcAddress(Asm.hU32, "CallWindowProcA"))
			i = i + Asm.SetDword(_para + i, $85)	// WM_NCPAINT
			i = i + Asm.SetString(_para + i, eventName)
		ENDIF
		_event = CreateEventA(0, FALSE, FALSE, eventName)
	FEND

	PROCEDURE Dispose()
		IFB _hwnd THEN
			DIM fcs[] = 0
			Show(fcs)
			DIM i = 0, res = Asm.Run(_addr + $90, _para, i)
		ENDIF

		IF _para THEN Asm.Free(_para)
		IF _addr THEN Asm.Free(_addr)
		IF _event THEN Asm.CloseHandle(_event)
	FEND

	PROCEDURE _Hook(fcs[], msg, x, y, dir, pt, font, bc, al)
		_Paint(fcs, msg, x, y, dir, pt, font, bc, al)
		DIM res = 0, i = 0

		IFB _hwnd = 0 AND _para > 0 THEN
			_hwnd = IDTOHND(GETID(GET_FUKIDASI_WIN))
			Asm.SetDword(_para + 4, _hwnd)
			res = Asm.Run(_addr, _para, i)
		ENDIF

		WHILE _loop
			res = Asm.WaitForSingleObject(_event, 10)
			IFB res = 0 THEN
				_Paint(fcs, msg, x, y, dir, pt, font, bc, al)
			ENDIF
		WEND

		_loop = TRUE
	FEND

	PROCEDURE _Paint(fcs[], msg, x, y, dir, pt, font, bc, al)
		// WIN32API
		// テキスト描画用
		DEF_DLL GetDC(hwnd): uint: user32
		DEF_DLL ReleaseDC(hwnd, dword): int: user32
		DEF_DLL DrawTextW(dword, wstring, int, {long, long, long, long}, uint): int: user32
		// テキストフォント変更用
		DEF_DLL SelectObject(dword, dword): dword: gdi32
		DEF_DLL DeleteObject(dword): bool: gdi32
		DEF_DLL CreateFontW(int, int, int, int, int, dword, dword, dword, dword, dword, dword, dword, dword, wstring): dword: gdi32
		DEF_DLL GetDeviceCaps(dword, int): int: gdi32
		DEF_DLL MulDiv(int, int, int): int: kernel32
		// 文字色・背景色変更
		DEF_DLL SetTextColor(dword, dword): dword: gdi32
		DEF_DLL SetBkColor(dword, dword): dword: gdi32
		DEF_DLL SetBkMode(dword, int): int: gdi32

		// とりあえずFUKIDASI出して情報収集
		DIM wid = GETID(GET_FUKIDASI_WIN)
		DIM hwndF = IDTOHND(wid)
		DIM hdcF = GetDC(hwndF)
		DIM l = 0, t = 0, r = STATUS(wid, ST_CLWIDTH), b = STATUS(wid, ST_CLHEIGHT)
		// 微調整
		SELECT dir
		CASE 1
			t = 5; b = b + t
		CASE 2
			b = b - 10
		CASE 3
			l = 5; r = r + l
		CASE 4
			r = r - 10
		SELEND

		// フォント作成とフォント・色設定
		DIM ro = r, bo = b, nHeight = MulDiv(pt, GetDeviceCaps(hdcF, 90), 72)
		DIM hfontF = CreateFontW(0 - nHeight, 0, 0, 0, 0, FALSE, FALSE, FALSE, 0, 0, 0, 0, 0, font)
		DIM hfontO = SelectObject(hdcF, hfontF)
		DIM cFr = SetTextColor(hdcF, 0)
		DIM cBk = SetBkColor(hdcF, bc)
		DIM cBf = FALSE, cBt
		IFB al < 0 THEN
			cBf = TRUE
			cBt = SetBkMode(hdcF, 1)
		ENDIF

		// 描画位置を特定する
		DIM off = DrawTextW(hdcF, msg, LENGTH(msg), l, t, r, b, $400)
		l = l + (ro - r) / 2; r = (ro + r) / 2; ro = r
		t = t + (bo - b) / 2; b = (bo + b) / 2

		// 一文字づつ描く
		DIM fcnum = LENGTH(fcs)
		DIM msgnum = LENGTH(msg), i, tar, lo = l, s = -1
		FOR i = 1 TO msgnum
			tar = COPY(msg, i, 1)
			IFB tar = CHR(10) THEN
				t = t + nHeight
				l = lo
				s = s - 1
				CONTINUE
			ELSEIF tar = CHR(13) THEN
				s = s - 1
				CONTINUE
			ENDIF
			IFB fcnum > i + s THEN
				SetTextColor(hdcF, fcs[i+s])
			ELSE
				BREAK
			ENDIF
			off = DrawTextW(hdcF, tar, 1, l, t, r, b, $400)
			off = DrawTextW(hdcF, tar, 1, l, t, r, b, 0)
			// 次の文字の準備
			l = r; r = ro
		NEXT

		// 後片付け
		SelectObject(hdcF, hfontO)
		DeleteObject(hfontF)
		SetTextColor(hdcF, cFr)
		SetBkColor(hdcF, cBk)
		IF cBf THEN SetBkMode(hdcF, cBt)
		ReleaseDC(hwndF, hdcF)
	FEND
ENDMODULE


MODULE Asm
	DEF_DLL GetLastError(): DWORD: kernel32
	DEF_DLL VirtualAlloc(DWORD, DWORD, DWORD, DWORD): DWORD: kernel32
	DEF_DLL VirtualFree(DWORD, DWORD, DWORD): bool: kernel32
	DEF_DLL GetModuleHandleA(string): DWORD: kernel32
	DEF_DLL GetProcAddress(DWORD, string): DWORD: kernel32
	DEF_DLL CreateThread(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD): DWORD: kernel32
	DEF_DLL WaitForSingleObject(DWORD, DWORD): DWORD: kernel32
	DEF_DLL CloseHandle(DWORD): bool: kernel32
	DEF_DLL GetExitCodeThread(DWORD, var DWORD): bool: kernel32

	PUBLIC hK32
	PUBLIC hU32

	PROCEDURE Asm
		hk32 = GetModuleHandleA("kernel32")
		hU32 = GetModuleHandleA("user32")
	FEND

	FUNCTION Set(code)
		DIM dom = CreateOleObj("Microsoft.XMLDOM")
		DIM binMan = dom.createElement("tmp")
		binMan.dataType = "bin.base64"
		binMan.text = code
		DIM i = LENGTH(code), size = i * 3 / 4
		IFB i > 3 THEN
			IF COPY(code, i-1, 1) = "=" THEN size = size - 1
			IF COPY(code, i, 1) = "=" THEN size = size - 1
		ENDIF
		DIM data[size-1], b = binMan.nodeTypedValue
		FOR i = 0 TO size - 1
			data[i] = b[i]
		NEXT

		RESULT = VirtualAlloc(0, size, $1000, $40)
		IF RESULT THEN SetByte(RESULT, data)
	FEND
	FUNCTION Alloc(size)
		RESULT = VirtualAlloc(0, size, $1000, $4)
	FEND
	FUNCTION Free(addr)
		RESULT = VirtualFree(addr, 0, $8000)
	FEND

	// 戻りはtypeによって異なる
	//  type=0   : スレッド終了コード
	//  type=1   : スレッド終了コード取得失敗エラーコード
	//  type=258 : スレッドハンドル
	//  type=他  : その他エラーコード
	FUNCTION Run(addr, para, var type, timeout=$7FFFFFFF)
		DIM hThread = CreateThread(0, 0, addr, para, 0, 0), res = 0
		RESULT = GetLastError()
		type = 258
		IF timeout >= 0 THEN type = WaitForSingleObject(hThread, timeout)
		SELECT type
		CASE 0
			IFB GetExitCodeThread(hThread, res) THEN
				RESULT = res
			ELSE
				type = 1
				RESULT = GetLastError()
			ENDIF
			CloseHandle(hThread)
		CASE 258
			RESULT = hThread
		SELEND
	FEND

	FUNCTION SetByte(p, data[])
		DEF_DLL RtlMoveMemory(DWORD, BYTE[], DWORD): kernel32
		RESULT = LENGTH(data)
		RtlMoveMemory(p, data, RESULT)
	FEND
	FUNCTION SetDword(p, data)
		DEF_DLL RtlMoveMemory(DWORD, var DWORD, DWORD): kernel32
		RESULT = 4
		RtlMoveMemory(p, data, RESULT)
	FEND
	FUNCTION SetString(p, data)
		DEF_DLL RtlMoveMemory(DWORD, string, DWORD): kernel32
		RESULT = LENGTHB(data)
		RtlMoveMemory(p, data, RESULT)
	FEND

	PROCEDURE Dump(addr, size)
		DEF_DLL RtlMoveMemory(BYTE[], DWORD, DWORD): kernel32
		DIM data[size], i, buf = "", bufa = ""
		RtlMoveMemory(data, addr, size)
		FOR i = 0 TO size - 1
			buf = buf + FORMAT(data[i], 3, -1)
			IFB data[i] >= $20 AND data[i] < $7F THEN
				bufa = bufa + CHR(data[i])
			ELSE
				bufa = bufa + "."
			ENDIF
			IFB i MOD 16 = 15 THEN
				PRINT buf + " " + bufa
				buf = ""
				bufa = ""
			ENDIF
		NEXT
		IF LENGTH(buf) THEN PRINT buf + FORMAT(" ", 49 - LENGTH(buf)) + bufa
	FEND
ENDMODULE

もう、
UWSCのFUKIDASIをカラフルにする - じゅんじゅんのきまぐれ

UWSCのFUKIDASIを多色にする - じゅんじゅんのきまぐれ
もいらんね。
後者はシンプルなので、あっても良いかもしれないけど。

応用のためのヒント

Fukidashiモジュール初期化処理でごちゃごちゃ設定してるように、

  • Asm.Run(_addr, _para, i) で、WndProcをフック
    • _para+4の位置にフック対象のHWNDを指定
    • Asm.SetString(_para + i, eventName) で設定するイベントを開く
    • もとのWndProcは、_para+12にある
    • 新しいWndProcは、「i = i + Asm.SetDword(_para + i, $85) // WM_NCPAINT」とあるように、WM_NCPAINTが来たら、SetEventしている
    • そしてもとのWndProcを呼び出す
  • Asm.Run(_addr+$90, _para, i)で、WndProcフック解除
    • FUKIDASIの場合不要みたいなので解除後のSendMessage(WM_NULL)は無効化している

さてー、これで何に応用できますかね?