Location via proxy:   
[Report a bug]   [Manage cookies]                

古いPCゲームソフトとCPU使用率の改善

古いPCゲームソフトとMIDI ファイルの読み込み - narukoの開発メモ

上記の続き.

このプログラムはゲーム起動中に CPU core 1 つを使用率100%にしつづけてしまう. 原因は GDI を利用しており、WindowsAPI の PeekMessage() を利用した無限ループになっている. これの直し方について最近試して使っている ChatGPT に聞きながら進めてみた.

ChatGPT が教えてくれた OllyDbg 経由でこのゲームを起動して、使用率があがる場面でとりあえず pause すると PeekMessageA() 付近で停まる. 命令を見る分には下記のようになっており、これは基本的な使い方だと思われる.

while(PeekMessageA()){
	GetMessageA();
	...
}

これを下記に書き換える. コンパイラでは while(cond){...} は if(cond) と do{...}while(cond); に分離されているので、コンパイル後の while(cond) を書き換えることになる.

while(wrap_peek_message_a()){
	GetMessageA();
}

wrap_peek_message_a() を作る.

int __attribute__((stdcall, dllexport))
wrap_peek_message_a(LPMSG m, HWND h, UINT m0, UINT m1, UINT op)
{
	const int r = PeekMessageA(m, h, m0, m1, op);
	if(r == 0){
		Sleep(1);
	}
	return r;
}

このゲームプログラムは Sleep() を使用してない. 本来は WindowsAPI を呼ぶための import table に Sleep() を追加して80386アセンブル命令を埋め込む形を使いたかった. そのやり方がよくわからなかったので、自前で dll を作り(32 bit コンパイラなんて久しぶりに使った)、 exe ファイルを改変するソフト(ChatGPT が教えてくれたもの. よくしらない)を利用して その dll を読むようにして、wrap_peek_message_a() を呼ぶようにする. これらの過程はちょっと変だし、GUIを毎回触る必要があり改めたい.

とりあえずこれで OllyDbg を読み込み問題の CALL PeekMessageA を CALL wrap_peek_message_a にすることで CPU の使用率が大幅に下がることを確認. EXE ファイルも更新して応急措置としての作業は終わりになる.

ABI と Visual C++ 4

筆者はいままで Windows のソフトをバイナリレベルでいじることがなかったので今回はじめて知ったこと. 80386 系の C で作られた関数の呼び出し規約(ABI)は Windows 独自の __stdcall と __fastcall がある. Windows API は __stdcall であり、問題ない. __fastcall は引数にレジスタ ECX と EDX 、残りは stack に push し、 call 後に呼び出した関数側が stack pointer を戻して呼び出し元に戻る (RET xxx).
それでこのバイナリは解析ソフトによると Visual C++ 4 で作られていて、 __fastcall に似ているがレジスタ渡しは ECX のみである.

先述のように CALL をまるまる書き換えてこっちで作った C の関数を実行させる方法が __stdcall の Windows API ではできるが static 関数ではこれが使えなくて困っている. 自作関数を呼び出す場合はやりかたはあるが、自作関数から元のプログラムの関数を呼ぶ場合には最善策が思いつかない.

この件、詳しい方がこれを読んでいたら対策方法を教えてほしい.

応急処置からのちゃんとした処置方法

今回の問題の根本的な理由は PeekMessage を利用した無限ループ以外に GDI ではマシな方法がなかったことである. Windows のソフトの作り方には詳しくないので最悪はいまもマシな方法がない可能性もある. Sleep() をいれるのは悪くはないのだが、最適な部分で再開させているわけではない. このゲームではさほど問題にならないが、スクロールがあるエンディングシーンでは「処理が異様に早くて読めない」から「処理がやや早くティアリングが目立つ」になったのでやはりあまりよくない.

根本的には適切なループ再開条件を解析して適切なタイミングで動作を再開させることにしたい. その他小さい要改善点を直すとなると、先述の ABI の問題なども障害になっている.

VHDR の未使用文字列

以前ストリートファイター2の文字列を調べていたところ、SSF2 に 「よく ここまで たどりついたな。だが 今回は ロケテスト・バージョン。(以下略)」という文字列が残っていたので、Vampire Hunter - Darkstalkers' Revenge でも本で似たような文字列があったのを思い出して調べた.

文字列はポインタの配列に入っているものが多く、そこから抜き出した文字列からは未使用データは見つからなかった. ロケテ($03af $0388 $0392)という文字コードをいれて ROM の中を調べるとでてきたのは、下記の文字列だった(制御記号は抜いてある).

CONGRATULATIONS! THANK YOU FOR PLAYING.
今回はロケテスト中だからこのくらいにしておくわ。だけど本番ではこうはいかないから楽しみにしておきなさい。

当時見た本が手元になく、このセリフは関西弁もある記憶があるが、製品版の ROM には標準語だけが残っているらしい. またこの付近のプログラムを解析して埋まってるであろう未使用プログラムを呼び出してゲーム画面に出すこともできるのだろうが、手間なので解析はここまでにした. (支援してくれたらやります)

昨日の補足

nesdev にある Standard Read for 2 Controllers and Famicom のコードが最適でそのコードは rol 命令を利用している. ror; bcc とするにはループの外で <$6e,x に #$80 を store する必要がある. sty zp,x という addressing は実在するため 1 byte 無駄なく実装できた.

	org $C5F7
	tax
	ldy	#$81
	sty	$4016
	dey
	sty	$4016
	sty	<$6e,x
lc604:
	lda	$4016,x
	and	#%11
	cmp	#1
	ror	<$6e,x
	bcc	lc604
	rts
	fill	$C60F

拡張ポートの拡張コントローラの非対応ソフト

Joycard系を認識しないソフトを日本向けライセンス品でみつけてしまった. 下記.

これらのソフトをみたら、 zeropage addressing をまったく利用していない! 拡張コントローラ認識のための修正方法はソフトを直すしかないので下記のコードをいれてみたら、ちゃんと動いた. かなり詰めたので $C5F7 から $C60F に入る. あと、この address は三國志ジンギスカンで全く同じでそれにもびっくりした.

	org $C5F7
	tax
	ldy	#$09
	sty	$4016
	dey	
	sty	$4016
lc604:
	lda	$4016,x
	and	#%11
	cmp	#1
	ror	<$6e,x
	dey
	bne	lc604
	rts
	fill	$C60F

$4016.w.bit7:4 は未使用 bit のため ldy #9 を利用して, ldy 命令を2つ削減しつつ、 FF2 でやっていた cmp #1 で発生する Carry を利用するというたぶん Nasir 氏が書いたコードをいれたら動いた. ほかのソフトの入力ポートのルーチンをみたが、 cmp #1 をやっているのは少なく sta; lsr; ora; lsr; ror を利用しているものばかりだった.

その他、 ガチャポン戦士4で拡張コントローラが使えるのが3人全員MANで黄色のみという限定的な設定. それ以外でも2人が MAN の設定の3通りではかなり不規則に使えるコントローラが変わってこれも規定違反でないだろうか.

flash への不揮発データ書き込みの高速化検討

W29C040 への書き込みは 0x100 bytes 単位であり、この転送がとても早いことにいまさら気づいた. 1 byte 単位への書き込みでも polling なしに 0x100 bytes 単位で書いたほうが早いのではないかといまさら気づいた. byte programming data の command 発行から不揮発データ書き込み完了は typ(ical) 7 us から 9 us, SST39SF040 は typ の記載がなく max 20 us となる.

一方 MCU から SPI 経由では 1 memory cycle が 4 SPI cycle となり、これが実測 2.0 us 程度で、コマンド発行に 4 memory cycle がいるので 8.0 us となる. よって片方の flash で待ってる間にもう片方へコマンドをいれると書き込みの待ち時間は減らせる.

ここで2通りの転送方法を検討する.

従来の 1 DMA cycle を拡張

従来の 1 DMA cycle は CPU memory へ command 発行 → PPU memory へ command 発行 → CPU memory へ polling → PPU memory へ polling としているから、(CPU memory へ command 発行 → PPU memory へ command 発行) x n → DMA 終了として polling を省略してしまう. n は 4 から 8 程度をいまのところ想定している. これで単純計算で n 倍早くなる.

利点はファームウェアの変更が少ないことと、memory で 0xff で埋まってる場所を飛ばすとか小さすぎる sector erase の対応(未実装)などの細かい処理を入れやすい.

欠点は次の方法より遅いことと descriptor を多用するので MCU の RAM を消費しがちである. 現状としては RAM (.data) の容量不足は感じておらず、 flash (.text と .rodata) の容量不足は確定的で、flash の容量が多いモデルにすることが決まっており必然的に RAM の容量が増えるから大したことではない.

1 DMA cycle で動的に拡張

1 DMA cycle は (CPU memory へ command 発行 → PPU memory へ command) x n として DMA 動作中に別の DMA をかけて SPI 出力前にデータを更新するなり、n 回ループを達成できたら DMA を止めてしまうなどを MCU のデバイスだけで動かして、 MCU の中の CPU は関知しないという方法. n は 0x100 単位を想定している.

この方法を検討したところ、address の部分は TC をカウンタとして利用すること address と data の更新用の DMA を設けるなどすれば動かせることを確認. ただし、 TC は 2 つ, TC または TCC は 1つ、 DMA ch は 4 つ必要となる. address の increment counter は TCC でもできるのだが、 TCC はカウンタ読み出しコマンドの発行が必要でこれが手間である(TC はいらない). ただ、TC は 16 bit で比較することに制限があるのでこれだけのための DMA を止める条件に TCC のカウンタが必要となる.

この動的更新の確認はできており、そのついでに EVSYS の組み方を修正して、タイマで 1 memory cycle にわざと待ちをいれることも可能にした. typ 8 us では動かないこともあるとか、 typ 25 us のデバイスもあるので polling をしない代わりに周期時間の調整は重要となる.

この方式は data 0xff を飛ばすとか、sector size を考慮するとか細かい部分で並列的に動かすというのは実質不可能なので、chip erase や erase に対する polling は同時、バンク切り替えも同時、PCからのデータ更新も同時として1まとまりに動作させることが必要になると予想される.

erase の polling

これは必要であるが、現状の方式では programming cycle のあとに必ず polling をいれていたので、erase と programming の polling は兼用となっていた. 片方がなくなるので polling の組み直しが必要となってしまった.

米国と台湾の銀行の話

US Bank → Wise → 日本の銀行

PLAID の接続後、 自分の US Bank 口座 → 自分の Wise USD 残高へ送金できました. 手数料は 0.13 % (0.0013 倍) でした. この後 Wise で日本円に両替して(手数料$2ぐらい)、自分の日本の銀行口座へ日本円で送金(手数料200円)できました. 自分の操作はすべてPCからオンラインでかなり早くできました.

これは立て替えの精算が目的です. 外国人(私)が Zelle を使えないなど、事情を話して送金してもらっています. 相手はわざわざ US bank の店舗まで行って送金してくださっているようです. だから、商売として同じことをしてほしいというと渋い顔をされそうです.

新光銀行 (台湾)

新光銀行の Online ATM (網路ATM *1 ) が廃止されたのは事実なのですが、台湾の別の銀行は IE + Active X なしでドライバを提供することでサポートを続けているところがすこしあります.

これを利用してキャッシュカードをICカートリーダにつなぐことで他行扱いで残高を見ることはできました. いまのところ必要がないので試してませんが少し手数料は増えますが送金もできそうです.

今のところ大手でもサポートする銀行(台湾銀行,台北富邦銀行)と廃止するところ(郵局)がわかれているので網路ATMはそのうち全部なくなるかもしれません.

*1:網路銀行と別,それは日本のオンラインバンキングと同じ

Flash Cartridge Troubleshooting: Challenges and Solutions

タイトルはAIが決めた. 内容は開発日誌でローカルのテキストファイルからの抜粋.

04-30
基板 TxROM と Memory AM29F040B の組み合わせは終わったので、以前の別の flash cartridge を出して動作確認開始.
基板 VRC6B と Memory W49F002 と W29C040 をみて W49F002 の使用確認とドライバの実装. ただしこれは変則的な設定がいるので一旦保留.
基板 UOROM と Memory MX29F040 を先に試す.

05-01
基板 UOROM と Memory MX29F040 は簡単にいける予想は外れて ID を取るまでにチェックしていない部分が大量にあり実装に手間取った. UOROM の場合は単純な配線では command address A14 の都合で program できない.
device で command address A14 がある場合に途中でエラーを出して停める必要があるが今のところ簡単にできないため、 VRC6B の組み合わせの対応をみてから実装したほうがいい.
基板 VRC6B はシミュレータを dump ができるまで実装. CPU address $8000-$bfff と同 $c000-$dfff の可変バンクを2つとも有効にすると、バンクサイズがあわないのでうまくいかないことを再確認.
programming に関しては flash address A14 を計算して, 15'h2aaa と 15'h5555 を割り振るアドレスを交換する必要がある. (どちらのバンクでも可能) ただしスクリプトの実装でこれを交換するようにできていないのでこれらのパラメータをクラス化したうえで VRC6B だけの例外実装を組めるようにする必要がある.
明日あさってで実装がうまくいかない場合は当分の間保留にする.

別途 UOROM の配置について flash A14 = CPU A14 とする方法もあるが, 現状のシステムで sector erase までちゃんと動かすにはかなり難しそうである. 管理可能容量が 0x44000 bytes (17 pages) になるので nesfile format としても変になる.

05-02
基板 VRC6 の flash ID 取得について例外的なクラスを実装.

05-03
W29C040 のコードを通すが assert で止まった.

05-05
assert で止まる理由を探したところ、 1 回左シフトにすべきところを 10 回左シフトになっていて、mruby の配列から uint8_t *の変数にできないというエラーだった.
その過程で mruby, C ともに W29C040 のコードを通っていない部分が多々あり修正した. とりあえずシミュレーションは通ったが、 fifo の buffer size は 0x100 の倍数である必要があるはずでバッファサイズの確保のコードを確認する必要がある.

05-06, 05-07
バッファサイズのエラーチェックを追加.
基板 VRC6 への command address A14:0 を確保する例外的なバンクアドレスの確保について実装をするため、 class ProgrammerBase にあった変数を class CommandAddress, class CommandDestination に分離. また class DriverFlash にもメソッドを分離. 中途半端な変数を除去し、 VRC6 への command address の例外的実装を継承したクラスとして実装、動作確認.
この動作自体は問題なかったが別途問題が発生.

  • VRC6 への programming は 0x4000 bytes の bank でやっているが最小 bank 単位は 0x2000 bytes で ROM image が 0x40000 bytes 未満で layout=top だと矛盾が発生してちゃんとかけない.
  • W29C040 と W49F002U の並列 programming はできるが、 W29C040 が圧倒的に早い. W29C040 のコードが残っている関係で W49F002U の転送速度が異様に遅い.
  • programming bank size より小さい sector を持つ場合の sector erase の対応を考えていない. SST39SFxxx と W49F002U が該当する. 39SF のほうは均等な sector size に対して、 49F の不均一なそれの対応は面倒そうである. 不均一 sector size は古めの flash に多く PC の bios で使われていたのだろうか.
  • 転送 bank の末尾 data が 0xff だと MCU が deadlock する. なんでいままででなかったのか? すぐに直した. ファームウェアの実装はかなり出来上がっていると思う.

基本的な programming 機能は実装したので他の機能の実装または、ハードの実装に切り替える.

米国、台湾、日本の銀行手続き

Union Bank → US Bank (米国)

UFJ 銀行の口座を持っているとなぜか米国の Union Bank の口座が作れるというやつで 2018年 ごろに作りました. 滞在時以外は休眠口座となっていたのですが、昨年と今年は日本円の価値が少なすぎて米ドルで決済するようにしてます.

2023年6月ごろに Union Bank は買収された US Bank に口座番号作り直しシステム移行ってことで、去年の今頃はエラーばかりで地獄でした. 去年は米国内の送金で Zelle ってのを使ってみようとしたのですが、携帯電話番号を持っていないのでエラー. ほかの手段で試そうとしてもエラー... で銀行に電話したら携帯電話番号を持っていないとなにも使えないという残念な回答でした.

2024年2月ごろに再度試してみるとシステムの統合をかなり直したのか、Debit Card で ATM でおろせたり、オンライン(おそらく米国内)決済はできるようになりました. エラーで携帯電話番号を登録してないから使えないというメッセージもでるようになりました.

自分の online banking ではなぜか住所が米国滞在時のままで、Debit Card 認証ではそこの Zip Code をいれると通ります. 納税の書類は日本の現住所に届きます. ... 謎です.

US Bank の Debit Card を日本のATMで使う

  • 出金金額は1万円単位で最高金額は5万円から10万円のようです.
  • セブン銀行で試したところ10万円までです.
  • セブン銀行ATMでは引き出し額を右側「米ドルに換算した値を米ドルで相手の銀行に請求」か左側「日本円を相手の銀行に請求」の2択がでますが、右側は絶対に不利ですので選ばないでください. ギリギリ合法の詐欺です.
  • ちゃんと読んでも理解ないとよく考えず右利きは損をする右側のボタンを押してしまいます. 具体的には:
    • 右側: 公定レートから3.5%を手数料としてセブン銀行が取ります. さらにその決済額から手数料として 3% を手数料として US Bank が取ります.
    • 左側: 決済額を US Bank が公定レートで米ドルにした上で手数料として 3% を手数料として US Bank が取ります.
  • どのケースでもこれに $2.5 の手数料がかかるはずですがなぜかそれは返してくれました.

US Bank の Debit Card を台湾のATMで使う

  • 出金金額は 2 万台湾元が限度のようです.
  • 台湾銀行ATMの場合、通貨の質問はなく台湾元を US Bank に請求し、US Bank は規定の 3% + $2.5 手数料を取りました.
  • 國泰世華銀行ATMの場合、通貨の質問はあるので台湾元を US Bank に請求する選択肢を選びます. この場合2つの選択肢は上下にあり、詐欺っぽい文面でもありませんでした. 手数料は規定通りでした.
    • 決済後にカードを60秒以内に取れとカウントダウンが始まります. 私はここで手続きに60秒かかりますと勝手に解釈して待機してしまいました. ATMにカードを飲まれ翌日現金輸送車にきてもらい回収してもらいました. (大事になってしまった)
    • エラーになった決済は online banking には載ってましたが後日全額戻っていました.
  • セブン銀行ATMでは返してくれた $2.5 は返してくれませんでした.

US Bank → Wise → 日本の銀行

Wise 関連は提灯記事がWeb検索結果に大量にでてとにかく Wise の Debit Card を作れ!! とでて欲しい情報がまったくでてきません. この Debit Card は作らなくていいです.

米国内送金はできないようですが、これは例外的に. Wise と接続して外国の(JPY)通貨に両替して Wise の(JPY)残高にすることができるようです. まだ送金してませんが、この記事(2023年7月)では自動でできなかったと書いてある PLAID の接続はできました. わたしはこの経路ではまだ送金はしていません.
https://dattesar.com/usbank-to-wise/

この記事は知らずに3000円を預けて米国の口座を作り、そこに送金してもらったのですが送金相手はオンラインでできずに手数料を$25もとられたと言われてしまいました.

新光銀行 (台湾)

口座を作った 2017年当時、 Online Banking は IC カードリーダを自分のPCにつなぐ Online ATM がありました. これは Active X を多用しているとかで Internet Explorer のサポート終了で2019年ごろなくなりました.

それと一緒に新光銀行独自の online banking と携帯電話アプリ(Mobile Gardian)の2段階方式があります. 携帯電話アプリは携帯電話を新しくすると窓口で手続きする必要があり、2度ほどかえてもらっていました.

今年も同様の手続きにいったところこの Mobile Gardian は銀行員が知らず、銀行員が本部に問い合わせたところ、この方式は古いからSMS認証に替えると台湾の携帯電話番号を教えろといわれ、半ば強制的に方式を替えられてしまいました. その携帯電話番号は短期滞在専用のものでもう使えません...

今回の台湾旅行でホテルの予約で国内送金を多用できたのに残酷です.

みずほ銀行 (日本)

みずほ銀行では携帯電話アプリかトークンでの2段階認証に替えるとメールがきました. トークンを希望する場合は電話をしろとあるので電話して、住所と名前と誕生日と口座番号を伝えて書類の郵送を頼みました. とどいた書類は住所と名前と口座番号を手書きして返信用封筒にいれて、後日郵便ポストにいれることになりました.

UFJ銀行では10年前に同様の認証に替えるとメールがきて、オンラインで手続きしたら、トークンが来ました. (先月そのトークンの電池が切れてオンラインでt更新手続きをしました)

みずほ銀行の認証方式は10年間遅れています. 手続きはすべてアナログで個人情報を口頭で言って復唱させて、さらに送られてきた紙に書いて、郵送する. みずほ銀行は0120番号を設営し、電話番を雇い、往復の郵便代を払っています.

みずほ銀行を解約したくなりました.

開発日記

Disk System の data の続き. head が最外周に移動した直後に読み込みを開始したところ、予想通り data の byte の並びに対して数 bit ズレたデータを取得できた. 別のデータは data の初期値が 1 になることも確認した. 予想外なのはこの2つ以外のパラメータがまだあるということ.

この状態のデータは bit 7 か bit 0 が正常なデータと一致しないので、別の初期化が足りておらず、この状態から復元することは難しいと判断した. CRC のフラグが該当 bit の 出力に影響しているような予想も立ててみたが、関連性が不明. 具体的にはファイル開始からCRCのまでの長さは可変であるために、ハード実装としてCRCチェックを埋め込むには複雑すぎて挙動の予想がつかない.

C での文字列領域の確保

前置き(長い)

現在開発中の機材の不具合を分析していたところ MCU ではなく PC 側のソフトがよくないと判断をした. 問題の機能を修正したところ、プログラム終了時に segmentation error が出るようになった. その理由は memory leak であり、malloc はしたのに free を忘れているとか、free を多重にやってしまうことだ. つまり潜在的なバグが表に出てきてしまった.

文字列領域の確保と可変長配列 (C99 VLA)

自分のソースを分析したところ malloc で多いものは下記だった.

  • 可変長となる文字列領域の確保
  • クラスもどきの初期化と終了

文字列に関しては可変長であることから、1つの関数内で malloc-free が完結し有効期間が短い. クラスもどきは初期化と終了で行うもので有効期間が長い.

void func(int a, int b, int c)
{
	static const char format[] = "%d...%d %d\n";
	int n = snprintf(NULL, 0, format, a, b, c) + 1;
	char *buf = malloc(n);
	snprintf(buf, n, format, a, b, c);
	hoge(buf);
	free(buf);
}

これを可変長配列に変える.

void func(int a, int b, int c)
{
	static const char format[] = "%d...%d %d\n";
	int n = snprintf(NULL, 0, format, a, b, c) + 1;
	char buf[n];
	snprintf(buf, n, format, a, b, c);
	hoge(buf);
}

可変長配列について、実際にアセンブラレベルでどういう処理をしているのかは知らないので個人的には MCU 用に使う気はないのだが、性能が高い PC であれば気にはならない(と思い込んでいる). それよりも free を書かなくていいことは気分が楽になる.

上の例では malloc-free が1つの関数内であるので忘れにくいが、実際には文字列生成関数の中で malloc をして生成済みデータのポインタを返す例がかなりあって、その処理では free を忘れていた. 作った直後は忘れることはないが、後日その関数を使い回すと忘れてしまう.

この例を自分が書いたソースで多数発見した. 仕方ないので文字列生成関数は長さを得るだけと、確保済みの領域に書き込む処理の2つに分けて可変長配列を使う前提に変えた.

関数 main() の引数 argv を UTF-8 にする

Windows の main() の argv の文字コードは default code page となっているので、制限や問題が多く一般配布するようなソフトでは使えない. よって UTF-8 を使うことに仕様を決めた.

#if defined(_WIN32_) || defined(_WIN64_) 
static int utf8_main(int argc, char **argv)
#else
int main(int argc, char **argv)
#endif
{
...
}

#if defined(_WIN32_) || defined(_WIN64_) 
static void argv_wchar_to_utf8(int c, wchar_t **wv, char **v, char *dest)
{
	for(int i = 0; i < c; i++){
		int l = wchar_to_utf8_length_get(wv[i]);
		wchar_to_utf8_set(wv[i], dest, l);
		v[i] = dest;
		dest += l;
	}
}
#include <windows.h>
int main(void)
{
	int c;
	wchar_t **wv = CommandLineToArgvW(GetCommandLineW(), &c);
	int length = 0;
	for(int i = 0; i < c; i++){
		length += wchar_to_utf8_length_get(wv[i]);
	}
	char *v[c], utf8_buffer[length]; //C99 VLA
	argv_wchar_to_utf8(c, wv, v, utf8_buffer);
	const int r = utf8_main(c, v);
	LocalFree(wv);
	return r;
}
#endif
  • GetCommandLineW() について, 関数 main の argv を default code page → wchar_t → UTF-8 にすることも可能だが、無駄なので wchar_t を取得する.
  • CommandLineToArgvW() については結局 LocalFree というものが必要で本題と矛盾している.
  • この2つの関数は wmain() を利用することで不要となるはずだが、 msys2 ではうまく使えなかった.
  • 変数 length, utf8_buffer[] については長さを取得した都度変数宣言をすると scope の都合でうまく実装できないので for ループを2つにわける必要が出る.
  • main() での argv に渡される引数は元々は wchar_t で、utf8_main() の中で内容がファイル名である場合はファイル名を渡す APIUTF-8 を wchar_t にまた戻す、その過程でその逆の変換が多重に発生している. この無駄は今のところみなかったことにしている.

結局、矛盾と無駄は残りつつその中ではマシな実装ということになってしまった.