2021年1月7日木曜日

MSXを技術的に振り返る

MSX開発の関係者を匂わせるタイトルかもしれませんが、私は全然関係ない(外野)です。

私はマイコン世代(死語)の人間ですが、実はリアルタイムでMSXは触ったことすら無いです。私が最初に触ったパソコンはPC-9801(16bit機)で、8bit機のMSXは触る機会すらありませんでした。

なので、MSXには全然詳しくないし、思い入れとかも全然ありません。PC-9801だと使えないスプライトが使えたり、PSG音源が標準実装されている点が羨ましいと思った記憶がある程度です。

最近、TinyMSXというMSXエミュレータを開発した関係で、MSXのハード仕様的な部分であれば現役でMSXを使っていた世代の諸先輩方よりも詳しくなりました。そんな客観的視点から「MSXとは何だったのか」を分析してみようと思います。

なお、MSXにはMSX、MSX2、MSX2+、MSX turboRという4シリーズあります。

以降、初代MSXのことは便宜上「MSX1」と表記します。

まず、MSX1のハード構成は以下の通りです。

  • CPU: Z80A互換
  • MMU: 独自システム(スロット)
  • Sound: AY-3-8910
  • Video: TMS9918A

TMS9918Aはテキサス・インスツルメンツ社が開発した画像処理装置(Video Display Processor)です。かなりクセが強いですが、16KBという比較的少ないビデオメモリ(VRAM)でそこそこカラフル(16色中16色を同時発色可能)な映像表現ができます。

TMS9918は元々、1979年にリリースされたテキサス・インスツルメンツのTI-99/4というパソコン(ホームコンピュータ)に搭載するために開発されたVDPです。そして、TMS9918Aは1981年にリリースされた後継機のTI-99/4Aに搭載されました。TMS9918との違いは画面モードにMode 2が追加された点です。(Mode 2は256x3の豊富 & カラフルなキャラクタパターンを表示できる画面モードで、MSX1の市販ゲームソフトの大半がMode 2を使っています)

なお、TI-99/4は最初に16bit CPU(TMS9900)を搭載した先進的なホームコンピュータとして知られています。ただし、変態キーボードを搭載していたり、競合のAppleIIと比べて値段が高かったりといった難があり、1981年までの2年間で2万台以下しか売れませんでした。一方、後継機のTI-99/4Aはかなりヒットして、北米でトータル280万台出荷されました。

MSX1が販売された1983年の時点では、TMS9918Aは既に2年落ちの古いVDPでしたが、TI-99/4Aのヒットにより安価に調達できたものと考えられます。同年にSEGAから発売されたSG-1000にもTMS9918Aが採用されました。

ちなみに、SG-1000と同じ年(というか同じ発売日)に任天堂からファミリーコンピュータも販売されましたが、ファミコンのVDP(PPU)は、TMS9918Aと違い、3色のカラフルなスプライトを同時に64枚(TMS9918Aは単色で同時に32枚)表示でき、1ドット単位の滑らかなBGスクロールができる(TMS9918Aだと8ドット単位のカクカクなBGスクロールしかできない)という、当時としてはとても優れたものでした。

MSX1のCPUは1976年にリリースされたZ80の互換CPU(8bit)です。

そして、PSG(音源)のAY-3-8910は、1978年にリリースされたサウンドチップで、当時のゲームサウンドではほぼデファクトスタンダードの地位だったので、ゲーム機やホビー系パソコンなどに広く搭載されていました。なお、一部のMSXではYAMAHA製のYM2149(AY-3-8910の互換チップ)が使われています。

つまり、1983年に発売されたMSX1は、1976〜1981年ごろに普及した(2〜7年落ちの)古いチップセットが使われています。MSXの基本コンセプトは「ホームコンピュータの標準化」なので、ハード部分は枯れた技術の寄せ集めの方が参入障壁が下がり都合が良かったものと想像できます。

ただし、MSX2(1985年)でだいぶ状況が変わります。

MSX2では、ASCII, Microsoft, YAMAHAがMSX用に共同開発したV9938(通称MSX-VIDEO)というTMS9918A上位互換のVDPが採用されました。

V9938は、カラフルなビットマップ表示(※これはASCIIからのリクエスト)や1行に80列表示できるテキスト表示(※これはMicrosoftからのリクエスト)などが出来る反面、ゲーム向けの機能強化については申し訳ない程度にしか強化されませんでした。

当時のASCIIとMicrosoftはあまりゲームを重視していなかったようです。

グラフィックはかなりキレイになりました。

ただし、ビットマップベースの画像処理には膨大な量のデータ処理が必要です。TMS9918Aは8x8のキャラクタベースの画像処理でしたが、ビットマップベースだとデータ量が単純計算で64倍に増えることになります。しかし、CPUは依然としてZ80(9年落ち)なので、ビットマップベースの膨大な量のデータ処理には向きません。その問題を解決するため、V9938にはコマンドと呼ばれるDMA装置が実装されています。しかし、そもそもデータ処理量が多いのでコマンド実行速度は期待したほど速くありませんでした。そのため、V9938はファミコンよりもキレイなグラフ表示などができる反面、キャラクタベースの画像処理主体のゲーム・グラフィックとしての性能はファミコン(MSX2発売時点で2年落ち)に劣るものでした。実際、ザナックEXと呼ばれるファミコン版ザナックをMSX2へ移植したゲームが存在しますが、処理落ちがかなり酷いものになってしまいました。

MSX2+(1988年)でVDPがV9938からV9958に強化され、横方向のハードウェアスクロールが可能になるなどのマイナーチェンジがされましたが、CPUは依然としてZ80(12年落ち)でした。CPUについてはMSX turboRでR800(16bit)になり劇的に速くなったものの、VDPはV9958に据え置かれており、今度はコマンドのウェイト発生が動作速度upする上でのネックになったものと想像できます。

MSXは世界向けの標準規格を目指していましたが、MSX2の時点で海外のメーカーはほぼ撤退し、MSX2+では日本メーカー3社のみが残り、最後のMSX turboRに至っては日本メーカー1社だけの日本ローカル規格になってしまいました。

MSXの標準化路線自体は正しかったものの、MSX2以降でミスディレクションしているように見えます。MSX1はCPUとVDPのバランスが良かったのですが、MSX2以降の全てのシリーズではCPUとVDPのバランスが常に悪かったという点が目立ちます。MSXはどちらかというと、「ゲーム開発できるゲーム機」という側面が強かったので、ゲーム(ホビー)向けに振り切っていれば、標準規格としてもっと生き長らえることができたかもしれません。

YAMAHAはV9938の開発後、SEGAマークIIIに搭載される315-5124というTMS9918A上位互換(V9938とは別の系譜)のVDPを開発しています。315-5124はMode 4という機能が追加実装され、Mode 4ではカラフル(512色中32色同時発色)なスプライトとBGを表示でき、1ドット単位の滑らかなスクロールも実現できます。更に、Mode 4のBGはV9938のようなビットマップベースではなくキャラクタベースなので、V9938と違いバランスが良い(Z80の処理性能でも制御しやすい)VDPだといえます。Mode 4はファミコンのPPU以上の性能があります。ファミコンだとBGの属性テーブルのビットレイアウトが独特(1バイトで2x2ブロック=4キャラクタ分の属性を設定する複雑な仕様)なのに対して、315-5124はネームテーブルと属性テーブルが各1バイトのシンプルなレイアウトになっているので、315-5124の方がゲームプログラムの開発には適していると思います。

要するに、V9938ではなく315-5124を搭載したMSX (MSX1+?) が欲しかった。

2020年12月24日木曜日

TinyMSX

MSXであってMSXではない謎エミュレータ(TinyMSX)を作ってみました。

https://github.com/suzukiplan/tiny-msx

TinyMSXは、以下のゲームを動かすことができます。

  • SG-1000
  • Othello MultiVision
  • MSX1 (16KB/32KBカートリッジ) ※メガロムは現時点では未対応

オセロマルチビジョンはオマケ。

SG-1000実機を持っている人はレアというか、相当な物好きに限られると思います。1983年7月15日に発売されました。

発売日が任天堂のファミコンと同じ日なので、どうしても比べたくなるのが人の性...

(SG-1000 vs ファミコン)

  • 値段: ほぼ同額だがファミコンの方が200円安い
  • CPU: Z80A vs 6502 ... どちらかといえばZ80の方が高性能
  • 画像処理: TMS9918A vs 独自PPU ... ファミコンの方が表現力が高い
  • 音声処理: SN76489(PSG) vs 独自APU ... ファミコンの方が表現力が高い
SG-1000がファミコンに勝っていた点はCPUのクロックレートだけで、値段、グラフィック、サウンドの面ではファミコンが圧倒的に高性能でした。

    CPUのクロックレートについても、ファミコンのRP2A03(約1.79MHz)の命令の方が全般的に低いサイクル数で命令が実行できたので、SG-1000のZ80A互換(約3.58MHz)が勝っていたとは言い難いです。Z80はパソコン向きに設計されているCPUなので、レジスタが沢山あったり、16bit演算ができたり、ループ命令があったりと機能的に複雑だったこともあり、1命令あたりの消費クロック数が高い欠点がありました。加えて、ファミコンはただでさえシンプルな6502から更にBCDモードの機能を取っ払っています。

    画像処理については、

    • 表示スプライト数: SG-1000は32個 vs ファミコンは64個
    • スプライト色: SG-1000は単色+透明 vs ファミコンは3色+透明
    • 水平スプライト数の上限: SG-1000は4個 vs ファミコンは8個
    • BGの色: SG-1000は横8pxにつき2色 vs ファミコンは1キャラクタに4色
    • BGのスクロール: SG-1000はナシ vs ファミコンはアリ
    • BGのプライオリティ: SG-1000はナシ(スプライトが常に前面)vs ファミコンはアリ

    という具合。

    SG-1000(TMS9918A)が唯一勝っていた点はスプライトのサイズが豊富だったことぐらい(ファミコンは8x8 or 8x16だったのに対してSG-1000は8x8, 16x16, 8x8の2倍, 16x16の2倍)だと思います。

    あと細い所ではファミコンだと0番スプライトの描画検出によりスキャンライン描画タイミングを捕捉できたので、標準機能のみでIRQライクなことができたので、ラスタースクロールが可能です。(IRQについては拡張マッパーで使うこともできました)

    音声については、SG-1000はPSG(AY-3-8910)を若干簡素化したSN76489(矩形波3+ノイズ1)なのに対して、ファミコンは矩形波2+三角波1+ノイズ1+ADPCMで更に矩形波はデューティー比を変更することでノコギリ波に似た音色を出すことができました。

    つまり、用途を「ゲーム」に特化して考えれば、SG-1000がファミコンに勝てる要素はほぼ皆無で、尚且ファミコンよりも微妙にお値段が高いので、この事実だけ見れば「SG-1000はファミコンに負けて当然だね」と思えるかもしれません。

    ただ、SG-1000がファミコンに勝てる芽が一切無かった訳ではないと思われます。

    SG-1000が搭載している装置(Z80, TMS9918A, SN76489)は、独自アーキテクチャで固められたファミコンと比べてゲームを作れるプログラマの人口が(当時は)多かったので、コンテンツ供給力では1983年時点ではSG-1000の方が優位に立っていたと思われます。また、SG-1000とファミコンが発売された1983年には、ASCIIとマイクロソフトが共同で開発したMSXが発売されましたが、MSXもSG-1000とほぼ同じハードウェア構成(Z80, TMS9918A, AY-3-8910)だったことも追い風になったかもしれません。

      ただ、結果的にSG-1000で発売されたタイトルはほぼ全部自社タイトル(一部、オセロマルチビジョンのツクダオリジナル製品もある)で占められていました。当時は現在のようにサードパーティーが参画するという発想自体が無かったのですが、サードパーティーを積極的に取り込む戦略だったら、未来は少し違っていたかもしれません。

      それでも私がSG-1000にめちゃくちゃ思い入れがあるから自らエミュレーターを作った・・・ということであれば美談になるのですが、実はそうでもなく、自作のZ80エミュレータを使ったゲーム機のエミュレータをOSSで作っておきたかったので、ハード構成が単純で作りやすいMSX1 & SG-1000をターゲットにしてみただけだったりします。

      2020年11月25日水曜日

      運動中は消化の良いものを食べましょう

      20km前後のサイクリングにだいぶ慣れてきたので、そろそろ50km超えをトライしてみようということで、三連休の半ばに彩湖を目標にサイクリングしてきました。

      AM10:00に出発して12:00頃に到着。

      到着後、GoogleMapで近所の適当なファミレスを探索。23区内ならランダムエンカウントで問題無いけど、流石に埼玉(朝霞市)でそれは無謀だろうということで。(実際、GoogleMapを使っても結構探索に手間取りました)

      昼食はドリンクバーでジュースを飲みつつピザをチーズ増量&ポテト付きで食べたのですが、これがマズかった。いや、味は美味しかったのですが、これが原因で帰宅後に地獄を見ることになるとは、この時は思いもよらなかったのです。

      約1時間の食事休憩後、朝霞市内を適当に周り、朝霞大橋を渡った所で折返して16:00過ぎに帰宅。

      走行時間は5時間ぐらい。道程の8割ぐらいは荒川サイクリングロード(仮称)だから信号停止とかが無いので、確実に平均時速10km以上は出ていた筈(もっと速いかも?)だから、恐らく50km以上走った筈です。(次はちゃんとサイクリングコンピュータとかを使おう...)

      初めて50km以上走ったものの、足は思っていたよりも筋肉痛にはならなかったので、もっと先まで行けたかも。ただし、何故か腕が筋肉痛になりましたが。

      筋肉痛は想定内なので問題無いのですが、帰宅後から謎の腹痛がして気持ち悪い。

      何だこれ?

      まぁ、動けないレベルではないし、時間が経てば自然と治るだろう...と、早め(21:00頃)に就寝したのですが、腹痛がひどくなるばかりで寝付けず、24時を過ぎたあたりで「これ、危ないヤツではないか?」と思い始めました。

      想定外の体調不良というのは中々怖い。

      ネット上の医療情報を漁って「激しい運動をすると内蔵がぐちゃぐちゃになるから補給は消化を良いものを」みたいな記事を見て「あ、昼食のピザがアカン奴だったのでは?」と気づく。幸い手元に緩下剤(夏に受けた健康診断で貰ったヤツの残り)があったのですが、先ずは薬には頼らず、民間療法で何とかすることにしました。

      ネットで「便秘に効くストレッチ」みたいなものを参考にストレッチしたり、DAKARAを飲んでみたり、タバコを吸ったり色々試したAM2:00過ぎ、ようやく変わり果てたピザ(宿便)との再開を果たし、無事腹痛が収まりました。

      うーん、こわい。

      じゃなくて、運動する時の補給には気をつけましょう。

      恐らく、今回のように中間地点で補給する場合、ゼリー状のヤツとかが良さそう。ファミレスやファーストフードに寄るなら若干のクールダウンを挟んだ後のタイミングで。固形食を摂る場合は咀嚼回数を普段の倍程度しつつ、すぐに動かない(長めに休憩する)ようにすれば問題無かったかもしれない・・・まぁ、何の医学的な根拠もないカンですが。問題が発生したら原因を特定して対策を実施、治ったら再発防止処置を講じるといういつものやり方。

      2020年11月21日土曜日

      本日のサイクリング

      ふと海が見たくなり、自宅から荒川サイクリングロード(通称)で葛西方面まで行ってきました。

      今日はかなり風が強かった(強風注意報が出てました)のですが、行きは追い風だったので割とすぐに荒川サイクリングロードの先っぽまで到着。(距離にして自宅から13kmぐらい)

      写真は中川側(だから余計に)ですが、海っぽさが足りない...

      強風が内陸から海側方向だったので、潮風が全部吹き飛ばされてしまったのかも。

      かといって、ここから潮風が感じられる千葉 or 神奈川方面まで行く体力は多分無いので、そのまま葛西臨海公園をぐるりと回って行こうかと思いましたが、良い感じの時間(12:45頃)だったので、餌場を求めて江戸川区の市街地へ。

      ただ、運悪くココス的な感じのファミレスにエンカウントせず只管北上。結局、四ツ木あたりまで北上したところでようやくジョナサンを見つけてIN。(14時30頃)

      GoogleMapとか使えば良いのですが、テクテクライフを動かしながら彷徨っていた関係で、ファミレスエンカウントは(土地勘が無いところでは)完全に運ゲーです。テクテクライフを動かす所=土地勘が無い所なので、餌場探しの難度が中々高いです。(嫌いではありません)

      2020年10月26日月曜日

      MODサイバーセキュリティ要員を募集中らいし

       MOD(防衛省)がサイバーセキュリティ要員の募集をかけてます。

      https://www.mod.go.jp/js/saiyou/pdf/s20201007_01.pdf

      確か、今年の2月ぐらいにも同じ募集があったような気がしますが、集まらなかったのかなぁ...

      菅内閣になってから、デジタル移行が重要視されており、その絡みもあって単純にサイバーセキュリティ要員が不足しているから追加募集という風にも考えられますが、前回の募集と違ってお賃金の額が例示されている点が気になる。

      募集条件は割と緩いです。

      • 正社員(or官公庁の正職員)として勤続合計13年以上
      • 年齢制限あり
      • IPAのITスキル標準レベル3以上
      • 犯罪歴や懲戒歴等なしの日本人

      私は募集条件を満たしています。

      というか、母数(条件を満たしている人数)は普通に多いと思います。

      かなり興味があるのですが、今の仕事をちゃんとバトンタッチするなり終わらせるなりしてからじゃないと公務員にはなれないので中々悩ましい。公務員になると当然営利の副業はNGですが、営利じゃなくて無報酬であっても民間企業の手伝いはNGになる(憲法でそういう風に規定されている)ので難しい。

      なお、お賃金はお安め(個人的にはこの点は全然問題なし)

      ちなみに、この採用試験に合格できる人なら、民間でその気を出せば普通に倍以上稼げると思います。それでも尚、やりたいと思える=全体の奉仕者として働く熱意といった感じでしょうか。(もちろん、お賃金に限った話ではないですが。ただ、お賃金額を例示したということは前回募集ではその辺がネックになったのかな?などと邪推してしまう...防衛省職員のお賃金は法律で決まっているので、採用等級だけ示せば例示不要な筈で、前回募集でも「係長相当職員(行(一)3級)」と明示されていたので、載せる意味なんて無いのでは?と思うのですが...

      転職を考えてた2013〜2014年頃、丁度警察でも似たような募集(サイバー犯罪の調査員とかだったかな?)があって心揺らぎましたが、当時も似たようなことで悩んで結局民間企業に転職しました。当時抱えていた悩みどころは解消済みなのですが、今はタイミングだけが合わない。中途半端に仕事を投げ出せば不可能ではないですが、まぁ、それは性格的に無理です。

      つくづく、公務員とは縁が無いなぁ。(興味はあるのですが)

      2020年10月23日金曜日

      UIImagePickerControllerで許可を求められない件

       iOSアプリでフォトライブラリから画像を取得する方法として、

      1. UIImagePickerControllerを使用する方法
      2. PHPhotoLibraryを使用する方法
      という2種類の方法があります。(※どちらもiOSが提供するAPIです)

      違いとしては、前者(UIImagePickerController)はOSが提供するUIを利用するのに対して、後者(PHPhotoLibrary)はフォトライブラリからデータを参照する(UIを独自に実装する必要がある)という点にあります。

      なので、UIImagePickerControllerの方が手軽に使えます。

      UIImagePickerControllerは、iOS11から仕組みが大きく変わり、OSプロセスで写真を選択し、選択された写真をアプリプロセスへ返す形になりました。

      iOS10以前のUIImagePickerControllerは、アプリプロセスからフォトライブラリにアクセスしていたので、フォトライブラリへアクセスしようとしたタイミングで以下のような確認が求められていました。

      フォトライブラリのアクセス許可

      しかし、iOS11以降では許可が不要になりました。

      iOS11以降の場合、PHPhotoLibraryを使用しているアプリでのみフォトライブラリへのアクセス許可が求められます。

      (セキュリティ的にどちらが安全か?)

      仮に信頼できないアプリを利用する時、UIImagePickerController(許可不要)とPHPhotoLibrary(許可が必要)のどちらが安全でしょうか?

      UIImagePickerControllerは、許可自体不要ですが、許可をしていない以上アプリが勝手にフォトライブラリをアクセスすることは出来ません。

      一方、PHPhotoLibraryは必要なタイミングで許可を与えなければならず、許可を与えてしまった以上どのような目的でフォトライブラリにアクセスされるか分かりません。

      なので、セキュリティ的にはUIImagePickerController(許可不要)の方が安全です。

      セキュリティ意識が高い方は、iOS11以降ではフォトライブラリへのアクセス許可を求めてくるアプリに基本的に許可を与えないようにすることをオススメします。何が抜かれるか分からないですからね。必要に迫られて許可を与える場合は「写真を選択...」を選んで、アプリにアクセス権限を与えても良い写真のみ選ぶと良いです。(私は抜かれて困る写真は入っていないので割とホイホイとフルアクセス許可を与えてますが...これはこれで寂しい)

      以下の記事で、「iOS11からUIImagePickerControllerで許可が求められなくなったので、PHPhotoLibraryで許可を求めるようにした(そうしないとリジェクトされるかも)」という内容が紹介されていたのですが、UIImagePickerControllerを使って許可を求めない分にはリジェクト対象にはなりません。(何故なら、アプリではフォトライブラリに直接アクセスしていないのでAppStoreのレビューガイドライン違反になりません)

      https://qiita.com/Masataka-n/items/468a38379c3f3bf132ca

      そもそも、PHPhotoLibraryで求める許可は、PHPhotoLibraryでフォトライブラリへアクセスするためのものです。フォトライブラリへのアクセス権限には「全部許可」「一部許可」「許可しない」の3種類が現在(iOS14では)ありますが、例えば「一部許可」した状態でUIImagePickerControllerを起動しても一部ではなく全部の写真が表示される「許可の矛盾状態」が起きます。ユーザ視点で見ても「許可していない写真が見えている」と不安になるのではないでしょうか。要するにUIImagePickerControllerを用いるためにPHPhotoLibraryでアクセス許可を求める実装はNGです。

      PHPhotoLibraryでアクセス許可を求めるならPHPhotoLibraryでPHAssetsへアクセスする独自UIを作るべきです(・・・が、写真を選ぶだけなのにわざわざUIImagePickerControllerを使わずにアクセス許可を求めてくる方が、「このアプリはセキュリティ的に大丈夫なのだろうか?」と個人的には不安に思ってしまいます)


      以下のフォーラムで、Appleフレームワークエンジニアが同じようなことを言っています。個人的には日本語でヒットする記事よりも、Appleフレームワークエンジニアの回答が全面的に正しいと思ったので、今回記事にしてみました。

      https://developer.apple.com/forums/thread/653414

      (Appleフレームワークエンジニアの回答を引用) 

      UIImagePickerController runs out-of-process since iOS 11. There is no need to prompt for Photos Library access before showing the picker on iOS 11 and above, unless you really need to work with the selected PHAssets.


      As outlined in this years session, there are some considerations for using the picker with Limited Library:


      > The picker will still show the entire Photos Library and all photos and videos can be selected by the user. No matter what the users selects in the picker, the PHAssets you can access will not change.


      There is no support to only show assets in the picker that you app has access to when in Limited Library mode. 

      Please feel free to provide feedback and outline the use case where your app would need to further restrict the selection.

      2020年10月18日日曜日

      iOSサブスクリプション実装後に心臓に悪い日々を過ごさなくて済むために読んでおいた方が良い記事

       先日、本業でAppStoreサブスクリプションに対応したサービスをリリースしたのですが、クライアント側の実装に大きな問題があり、現在その対策版の審査待ちという状況です。初めて特急審査出してみました。既にサービス開始してしまっていることから、それだけでもかなり心臓に悪く、眠れぬ日々が続いております。

      今後ほかの方が同じ轍を踏まなくて済むように、一体どのような問題が起こり、どのような対処をしたのか可能な限りなるべくわかり易く書き記しておこうと思います。


      (AppStoreサブスクリプションの概要)

      サブスクリプションを定期購読すると「レシート」と呼ばれるものが発行されます。アプリでこのレシート取得して、自前のサーバへ送信&検証することで、ユーザが購読者か否かを判定する...というのが、基本的なサブスクリプション実装方法です。(詳細はコチラを参照)


      (今回発生した問題)

      定期購読したユーザがアプリを再起動すると、定期購読中と判定されない不具合が発生しました。

      つまり、買ったのに「買ってない」と判定されてしまう致命的なバグです。


      (問題の原因)

      アプリでレシートを取得するには、Bundle::appStoreReceiptURLというAPIを用いますが、どうもこのAPIがnilを返しているらしいことがサーバのログから判明しました。

      不思議なことに暫く時間を置いてからリトライすれば、正しい値が返されます。

      これと類似する現象が以下のStack Overflowで公開されていました:

      https://stackoverflow.com/questions/20027322/appstorereceipturl-on-mainbundle-always-returns-nil

      上記によると、

      SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}];
      refreshReceiptRequest.delegate = self;
      [refreshReceiptRequest start];

      という形でレシートをリフレッシュする必要があるとのことでした。

      ただし、上記実装ではローカル変数(弱参照)でSKReceiptRefreshRequestインスタンスを保持しているので問題があります。

      以下の記事にもありますが、SKReceiptRefreshRequestインスタンスは強参照(strong@propertyなど)で保持しなければなりません。

      https://asmz.hatenablog.jp/entry/in-app-purchase-not-working-in-tvos

      そこで、アプリ起動時(ログイン時)にSKReceiptRefreshRequestインスタンスをリクエスト中は強参照で保持しつつ、リクエストを出す形の修正をしてApple審査に提出しましたが、リジェクトされました。


      (審査リジェクト原因)

      リジェクト理由は、アプリ起動後にタップ(ログイン)したところハングアップした(ガイドライン2.1違反)とのことです。

      以下のStack Overflowに書かれていますが、AppStoreのsandbox環境でSKReceiptRefreshRequest::startを発行すると、クレデンシャルの入力が要求され、それをキャンセルすると二度と応答が返らない(アプリを一度削除してから再インストールする必要がある)とのことです。

      https://stackoverflow.com/questions/30114489/skreceiptrefreshrequest-not-working-the-second-time-it-is-called-after-a-cancel

      上記のAnswerに、

      The only solution is to present an alert, telling the user to remove and download your app again from the store and to not cancel the credential box when the app asks for the apple ID/password. 

      翻訳: 唯一の解決策は、ユーザーにアラートを提示し、アプリを削除して再度ダウンロードするように指示することと、アプリがアップルID/パスワードを要求したときにクレデンシャルボックスをキャンセルしないように指示することです。

      という、「そんなまさか!?」と思うようなことが書かれていますが、考えうる限りどうもそれが事実のようです。という訳で、Apple審査を通すため、SKReceiptRefreshRequest::startの裏でNSTimerをスケジュールして一定時間(60秒)応答が無かった場合は、「アプリを削除して再度ダウンロードするように指示することと、アプリがアップルID/パスワードを要求したときにクレデンシャルボックスをキャンセルしないように指示」する旨のアラートを表示するという、〇〇(自粛)のような修正を入れて審査再提出しました。


      (何故テストしきれないのか)

      根本的な原因は、sandbox版AppStoreと本番AppStoreの挙動に違いが多すぎることにあります。

      本番AppStoreでテストするには、一度Apple審査を通す必要があります。

      Apple審査を通せば「プロモコード」を発行することで、本番AppStoreの挙動をテストできるのでは?と思われるかもしれません(私もそう思っていた時期がありました)が、リリースしていないアプリの場合、プロモコードを発行してもアプリをダウンロードできないので、「ローンチ一発目はぶっつけ本番で一般ユーザと一緒にテストするしかない」という、非常にロックンロールな仕様になっております。

      だからバグっていても仕方がない・・・とは言いませんが、テストせずにバグを全て取り除くのは困難です。

      実は、今回の対策で本当に問題対策できているかという確証がまだ無いです(本番環境でテストできていないので当然ですよね)。なので、Apple審査(現在審査待ち)に合格したら、今度こそはプロモコードを使って事前テストしたいところです。できなければ、またロックンロールするしかないですね。(審査後にテストできてまた問題が見つかったら、また審査待ちで数日を要し、その数日間がまたハラハラドキドキになってしまいますが、「治ったよ」と宣言して治ってなかったというケースと比べればまだ安心・・・なのだろうか。連日の徹夜続きで既に思考が正常ではないかも)

      心臓に悪い日々はまだまだ続きそうだ。

      合理的ではないものを作りたい

      ここ最近、実機版の東方VGSの開発が忙しくて、東方VGSの曲追加が滞っています。 東方VGS(実機版)のデザインを作りながら検討中。基本レトロUIベースですがシークバーはモダンに倣おうかな…とか pic.twitter.com/YOYprlDsYD — SUZUKI PLAN (...