検証はしていませんが、「ルビー」「エメラルド」も同様に復活させられる可能性もありますし、また、サファイアでもロットが違う製品だと違うチップが載っていて同様にいかない可能性もあります。
流石にゲームボーイアドバンスはもうレトロゲーム認定してもいいのではないかと思います。とても懐かしいですね。
久しぶりにサファイアをプレイしたくなったので、起動してみました。
おお、時計が電池切れだと。
電池切れで時計が動かなくなったから時間絡みのイベントは起きなくなるけど、それ以外のイベントは正常に動くからプレイ可能だよとのメッセージが出てきました。
ゲームボーイカラーまではセーブデータがRAM上に保存されていて、バックアップ用電池が切れるとセーブデータも吹き飛んでいましたが、ゲームボーイアドバンスからはセーブデータ自体は不揮発性メモリに書き込まれるようになったようです。しかし、時計管理はあくまでもカートリッジの責務なんですね。 DSからは時計もゲーム機本体のほうの責務になったみたいですが。
まあとりあえず、ふむふむと思いながらプレイを開始してみましたが、案外時間関係のイベントが多いことに気が付きます。
例えば、
- きのみが育たなくなる
- 毎日きのみをくれる人がくれなくなる
- レコードを混ぜると出現する友達の秘密基地で、1日1回のバトルができなくなる
- ミナモデパートで1日1回のくじが引けなくなる
- 1週間に1回わざマシンをくれる人がくれなくなる
- あさせのほらあなであさせのかいがらやあさせのしおが採れなくなる
- カイナシティのがんばりやさんが二度と頑張らなくなる
- マボロシじまが出現しなくなる(時計が動いていても出現したの見たことありませんが)
電池交換は、実はまだゲームボーイアドバンスのソフトならば任天堂がサポートしてくれています(※注)。
バックアップ電池の交換について
ちゃんと正常にゲームをプレイしたい人はまずこっちに頼んだほうが手っ取り早く確実で安いです。ですが、こういうのを分解して自分で修理するのを楽しみたい私は、自分で修理に挑戦してみることにしました。
※注:記事投稿時点。現在はバックアップ電池の交換サービスは終了しているようです。
なお、当たり前ですが、自分で分解、修理をするとその製品について二度と任天堂のサポートを受けられなくなる可能性があります。また、ゲームバランスを大幅に崩すようなゲームの改造も民事訴訟の対象になる可能性が無いことはないようです。もちろん、この記事を見て改造をして失敗した人とかがいたとしても私は責任を負えませんので、自己責任でお願いいたします。
今回の私の記事は、純粋に修理をするためというのと、あとは、まあゲームボーイアドバンスはもう枯れ果てたゲーム市場ですから、メーカーに目くじらを立てられるようなことも無いでしょうということで書くことにしました。
分解、そして電池交換
まずはカートリッジを分解します。分解はY字の特殊ドライバーを使いますが、ゲームボーイカラーまでのカートリッジに比べたら格段と入手性の高い汎用特殊ドライバーで何とかなります。ねじを外したら、筐体を少しスライドするとロックが外れてパカッと開きます。
こんな感じになります。
ボタン電池が付いていますね。これが切れた電池で、CR1616のタブ付き電池になっています。
まずはタブの根元をはんだごてで温めて古い電池を外します。
CR1616はリチウム電池なので公称3Vですが、もうすでに0.15Vまで下がっていました。完全放電状態です。
ネットをいろいろ探すと、ここまではたどり着いている方も多く、多くの紹介記事では、このタブと電池の接合部を剥がして、新しい電池をテープ等で固定している方が多いように見受けられました。
し かし、それではタブの金属疲労の問題は元より、電池との接触も悪くなることが多いのではないかと思い、私は電池に直接はんだ付けすることにしました。電池は精巧に作られた化学製品で、電池をはんだごてで熱すると中の電解液が気化するなどの化学的変化を起こし、最悪、電池が爆発することがあるので非常に危険です。なので、良い子は真似しないようにしましょう((((((
新しい電池を百円ショップで買ってきて取り付けた画像がこちらです。
マイナス電極側が上になるように取り付けていますが、プラス電極が横に回り込んできているので、接触してショートさせることがないように配線を一部ビニールで覆っています。
これで起動すると、冒頭に出てきていた電池切れのメッセージが出なくなりました。
よしこれで時間関係のイベントが復活だ!よっしゃああああああ!!!!!
まだ時間関係のイベントが発生しない!
起動時の電池切れメッセージこそ出なくなったものの、きのみを植えて1日程度待っても芽が生えてきません。きのみをくれる人もきのみをくれません。おかしいです。電池が復活しても時間関係のイベントが発生しないのです。なぜだ、なぜなのだ!ヒントは、キナギタウンにいる、1週間に1回わざマシンをくれる人のところにありました。
あと2105日!?およそ5年と9か月です。
正常ならば、ここで「あと6にち」など、1週間以内の日数を案内してくれます。
ここで僕は気が付きました。
ゲームの内部では、次にわざマシンをあげられるようになる日時を記録していたとしたらどうなるでしょう。現在の日時との差分を取って、その日数を「あと○にちたてば~」と答えることになるはずです。
現在の日時はというと、先ほど交換した電池で駆動しているRTCが管理しています。電池を交換したのでRTCのカウントはリセットされ、0日目になったはずです。
そ れ だ
すなわち、ゲームプレイ開始から2098日目に、次にわざマシンがあげられるようになる日を2105日目と記録していたため、電池を交換後でもお構いなしにRTCの日数カウンタがその日になるのを待つ必要が生まれた、ということになります。電池切れなんてそうそう起こることでもなければ、ユーザー自身での修理を想定していたわけでもないので、このような適当な実装になっているのでしょうね。
おそらく、1日に1回起きる系のイベントも同様に記録されていて正常に発生しないということなのでしょう。
このことからも、GBAソフトをメーカーに送って電池交換をしてもらった場合は、電池交換後にRTCのカウンタをずらすか、専用のツールでセーブデータを書き換えてイベントが正常に発生するようにしているものと思われます。
今回は自分で交換したためそのような書き換えができませんので、2105日待つ必要があります。
えっ
ネットで調べたところ、同じような症状になっている人もちらほら見かけました。
そして、ルビー・サファイアではRTCが1年を刻むときのみが育たなくなるバグがあったのですが、その修正プログラムを使えば1度に限ってこの状態を修正できるとの情報がありました。しかし、すでにこのパッチは当てており、リーフグリーン・ファイアレッドからのファームウェア更新を利用して修正することはできませんでした。
つまり、いよいよもって本当に2105日待つ必要があるということになります。
待てるかい!
そして、RTCのハードウェアハックへ
さて、ここまで自分でいろいろやってしまって引くに引けなくなった僕が次に考えたことは、「RTCの値を上書きして2105日分進めればいいんじゃね?」ということでした。ネットでは電池交換まではしたけど同様に時間関係のイベントが発生しない状態になっている人までは見かけましたが、さすがにRTCをいじろうとしている人までは見かけませんでした。
ですが、カートリッジの基板を見ると、ICチップの脇に丁寧に「1M/512K FLASH U2」とか「U3 RTC」などとICの説明が書いてあります。そうです、どれがRTCかも明確です。もっと言えば、脇に水晶発振子があることからも明白でした。そこで、このRTCの仕様さえわかればRTCの書き換えもできるのではないか、という発想で型番をググってみました。
S-3511A
2018年2月28日追記:リンク切れになっております。通販サイトのタイロテックのこちらからダウンロード可能です。
見事にヒットしました。当時はセイコーインスツルメンツでしたが、今は半導体部門は子会社化されてエスアイアイ・セミコンダクタになっているみたいですね。まあ何にせよSEIKOです。SEIKOが供給していたRTCモジュールが僕のサファイアには搭載されているみたいです。すでにディスコンになっているようですが、データシートは問題なく読めます。よっしゃああああああ!!!!!
まずはプロトコルの確認です。まあRTCだとI2Cかなと思って眺めましたが、ACKとかスレーブアドレスとか無さそうだな。うんうん、SPIかな…いやデータ線が1本しか無いぞこれは…
独自規格だー┗(^o^)┛WwwwWWww┏(^o^)┓ドコドコドコドコwwwwwwwww
なんてこった。独自規格で通信プロトコルを定義しています。
なので、書き換えをする場合はI2CやSPIなどマイコンに載っているハードウェアを使わずに、このプロトコルに則ったIOの制御を自前でしてや らなければなりません。
また、当たり前ですが、データを書き換えるためにはICに何かしら電気的に接続する必要があります。そして、バックアップ用電池で動作している状態(=通電した状態)で書き換えをしたうえで接続を外し、元のカートリッジの形に戻してあげなければなりません。はんだ付けとかでやるのはちょっとリスクが高すぎますよね(実は自分もやってみたりしたのですが、結構スリルがありました)。
そこで登場するのがICクリップです。
ICテストクリップ(SOP-8) [TCLIP-SOP8]これは、基板に実装されたICを挟むことで電気的に接続し、外部に配線を回してくれるツールです。ちょっと値段は高めですが、通電中に書き換えるのならば通電中はんだ付けよりもこういうツールを使ったほうが安心でしょう。
実際に買ってきてRTCを挟んだものがこれです。こんな感じで簡単に接続できます。これはいいです。
マイコンは何でもいいのですが、まあ、安さと小ささ、そしてプログラムメモリの容量からPIC16F1705を選びました。
PIC16F1705PIC16F1705は特にアナログ機能が強化されたマイコンですが、残念ながら今回はその機能は使いません。
適当に表示用のI2C液晶と操作ボタンを付けた回路を構成し、完成です。
めんどくさいので電源はCR2032を使いました。液晶バックライトを使ったらそんなに持たないでしょうが、まあ長時間運用するものでもないですし大丈夫でしょう。
I2C液晶は適当に制御プログラムを移植してきて動作を確認します。今回aitendoで買ってきた液晶は、液晶の表示向きを反転させるピンが出ていて、それに気づかずにだいぶ苦労しました。適当にそのピンをGNDかVDDに接続することで向きを固定できます。
さて、本題のRTC通信プログラムに入っていきましょう。
タイミングチャートとにらめっこしながらプログラムを書きます。各パルス幅は最低0.1us程度なので、まあゆとりをもって1usくらい取ってあげることにします。
#define sequenceDelay() __delay_us(1); void rtc_Init() { RTC_CS = 0; RTC_SCK = 1; RTC_SIO = 0; TRIS_RTC_CS = 0; TRIS_RTC_SCK = 0; TRIS_RTC_SIO = 1; } void rtc_WriteBit(bool_t data) { RTC_SCK = 0; sequenceDelay(); RTC_SIO = data ? 1 : 0; sequenceDelay(); RTC_SCK = 1; sequenceDelay(); } void rtc_WriteByte(uint8_t data) { for(uint8_t i = 0; i < 8; i++) { rtc_WriteBit(data & 0x01); //Send from LSB to MSB data >>= 1; } } void rtc_WriteSequence(uint8_t command, const uint8_t *pData, uint8_t length) { RTC_SCK = 1; RTC_SIO = 0; sequenceDelay(); RTC_CS = 1; sequenceDelay(); TRIS_RTC_SIO = 0; rtc_WriteByte(command); for(uint8_t i = 0; i < length; i++) rtc_WriteByte(pData[i]); RTC_CS = 0; sequenceDelay(); TRIS_RTC_SIO = 1; } bool_t rtc_ReadBit() { bool_t ret; RTC_SCK = 0; sequenceDelay(); RTC_SCK = 1; sequenceDelay(); ret = PORT_RTC_SIO; return ret; } uint8_t rtc_ReadByte() { uint8_t data = 0; for(uint8_t i = 0; i < 8; i++) { data >>= 1; if(rtc_ReadBit()) //Read from LSB to MSB data |= 0x80; } return data; } void rtc_ReadSequence(uint8_t command, uint8_t *pData, uint8_t length) { RTC_SCK = 1; RTC_SIO = 0; sequenceDelay(); RTC_CS = 1; sequenceDelay(); TRIS_RTC_SIO = 0; rtc_WriteByte(command); TRIS_RTC_SIO = 1; sequenceDelay(); for(uint8_t i = 0; i < length; i++) pData[i] = rtc_ReadByte(); RTC_CS = 0; }
ビットを読み書きする関数し、それをバイトデータを読み書きする関数から呼び出します。
書き込みシーケンスと読み込みシーケンスは、まずはそのデータ内容を示すコマンドを送ってからデータの読み書きをしますので、そういったことをやりやすくした関数を用意します。ここでとても重要なのが、データはLSBからMSBに向かって送りますが、コマンドはMSBからLSBに向かって送る点です。なんでこんなクソ仕様にしたのかわかりませんが、コマンドとデータで送る順序が違うので、ここでは、「コマンドの値をデータシートに書いてある順序と逆順にしておく」という方法で対応し、プログラム自体はすべてLSBからMSBに向かって送るようにしました。
実際のコマンドは全部で7種類あり、リセット以外の6種類は読み書き両方ができるため、実質13種類あります。
ですが、RTCを制御するうえでは、年データからの時刻データの読み書きと、ステータスレジスタの読み書きと、リセットコマンドの送信さえできれば問題ないので、その分だけ実装しました。
#define RTC_COMMAND_FULL_READ 0b10100110 #define RTC_COMMAND_FULL_WRITE 0b00100110 #define RTC_COMMAND_STATUS_READ 0b11000110 #define RTC_COMMAND_STATUS_WRITE 0b01000110 #define RTC_COMMAND_RESET 0b10000110 #define RTC_DATETIME_LENGTH 7 typedef union _tagRTC_STATUS { uint8_t value; struct _tagBits { unsigned bit0 : 1; unsigned INTFE : 1; unsigned bit2 : 1; unsigned INTME : 1; unsigned bit4 : 1; unsigned INTAE : 1; unsigned Is24 : 1; unsigned POWER : 1; } bits; } RTC_STATUS; void rtc_WriteDateTime(const DATE_TIME *pdt, bool_t Is24) { uint8_t data[RTC_DATETIME_LENGTH]; bool_t IsPM = pdt->hour >= 12; uint8_t hour = pdt->hour; if(IsPM && !Is24) hour -= 12; data[0] = ByteToBCD(pdt->year); data[1] = ByteToBCD(pdt->month); data[2] = ByteToBCD(pdt->day); data[3] = pdt->weekday; data[4] = ByteToBCD(hour) | (IsPM ? 0x80 : 0x00); data[5] = ByteToBCD(pdt->min); data[6] = ByteToBCD(pdt->sec); rtc_WriteSequence(RTC_COMMAND_FULL_WRITE, data, RTC_DATETIME_LENGTH); } void rtc_ReadDateTime(DATE_TIME *pdt) { uint8_t data[RTC_DATETIME_LENGTH]; rtc_ReadSequence(RTC_COMMAND_FULL_READ, data, RTC_DATETIME_LENGTH); bool_t isPM = data[4] & 0x80; pdt->year = BCDToByte(data[0]); pdt->month = BCDToByte(data[1]); pdt->day = BCDToByte(data[2]); pdt->weekday = data[3]; pdt->hour = BCDToByte(data[4] & 0x3F); pdt->min = BCDToByte(data[5]); pdt->sec = BCDToByte(data[6] & 0x7F); pdt->msec = 0; if(isPM && pdt->hour < 12) pdt->hour += 12; } void rtc_WriteStatus(RTC_STATUS status) { rtc_WriteSequence(RTC_COMMAND_STATUS_WRITE, &status.value, 1); } RTC_STATUS rtc_ReadStatus() { RTC_STATUS status; rtc_ReadSequence(RTC_COMMAND_STATUS_READ, &status.value, 1); return status; } void rtc_Reset() { rtc_WriteSequence(RTC_COMMAND_RESET, NULL, 0); }
こんな感じになりました。
このRTCモジュールは12時間表示モードと24時間表示モードがあり、時刻データの読み書きをするときは現在どっちのモードなのかを意識する必要があります。時刻データには現在が午後か午前かを表すフラグがあるので、データ読み込み後にはそのフラグを見てデータを処理してからBCDをbyteに変換しなければいけませんし、書き込み前にはSTATUSレジスタを読み込んで現在のモードに合わせて12時間表示化してデータを送信する必要があります。
あとは、これをいい感じに制御するインターフェースを作れば完成です。
PICのタイマー2はコンパレーターとセットで動いていて、コンパレーターで指定した値に達したら割り込みを掛けるとともに自動でタイマーをゼロにリセットしてくれます。なので、時計用途でとても使いやすいです(割り込みルーチン内でタイマー値を再設定する必要が無いため)。
時計と時計合わせ機能、そして上記の5つのコマンドの送受信機能を実装すれば、あとはRTCの時刻データを読み込み、修正し、書き込むことができるようになります。これで、ゲームの時間を今回は2105日分+α早送りしてやれば見事ゲームが正常な状態になります。
ちなみにですが、このRTCは電池を交換した後にSTATUSレジスタの読み込みと、適宜リセットコマンドの送信をしてやらなければなりません。ですが、その機能はGBAカートリッジ側でゲーム起動時に行ってくれるようなので、時刻データを読み書きする分には特に気を使う必要はなさそうです。
あと、RTCに設定する時刻は、リアルの時刻ではありません。まず、年月日データが大きな意味を持っていないことは誰にでも想像がつくでしょう(製造時に時計合わせしているだなんて考えにくいですからね)。時刻データは、ゲーム初期化時に聞かれる時刻設定がそのままオフセットになっているのか、私の環境ではRTCが0時00分00秒を示しているときにゲーム内では夕方5時過ぎになっているようでした。なので、その分を意識して時計合わせをしなければなりません。
まあ、なんにせよ、何回か「ゲームを起動して時刻を確認してから終了し、値を更新する」という作業を繰り返さなければならないことは間違いありません。少し面倒ですが、これで完全に時計を合わせられるようになります。
書き換え風景です。
このICクリップですが、やはり足元での接触が悪いことがあり、その場合はRTCのデータが上手く読み書きできなかったり、最悪RTCの中身が吹き飛びます。ですがまあ、根気よくやっていれば大丈夫でしょう…。
時刻データを書き換えるということ
ここまで読んで気づいた方もいるかもしれませんが、今回の記事は「ゲームボーイアドバンス用ソフトの時計をずらす」ということに帰着します。今回やったのは修理のための行為ですが、チートに転用しうることも容易に想像がつくでしょう。今回はRTCにアクセスし時刻を動かすツールですから、ゲームプログラムのROMの読み書きはできません。ROMの読み書きは特に海賊版のデータが出回る可能性を大いに含みますから、著作権の観点からも、利益の観点からも、メーカーも総力を挙げてそういったツールの撲滅にかかるようです。
ですが、今回はせいぜい時計をずらす程度のチートツールとしてしか使えません。著作権の侵害にあたる可能性はまず無く、せいぜい著作人格権で問題になることがあるかもしれないという程度でしょう(チートツールは作者の意図したゲーム性を破壊することで著作人格権の侵害とみなされることがあるらしいです)。しかし、ポケモンは時刻をずらしたところでそんなにゲーム性を著しく欠くほどのチートにはならないでしょうし、上にも書いた通り、ゲームボーイアドバンスはもう何世代も前のゲーム機で市場は枯れ果てていますから、メーカーも目くじらを立てて怒ってくることも無いと思っています。
というわけで、現在私はこの記事に重大な問題があるとは認識しておりません。関係者の方で、万が一何か問題があるというのならばぜひご一報ください。
改造、修理も男のロマンですからね。怒られない程度の範囲内ならばやって楽しいでしょう。というわけで、私は自分のやったことを公開することにしました。
この記事を参考に改造する方がいるのならば、くれぐれも分をわきまえて行うようにしてくださいね。
さて、いかがだったでしょうか。
ゲームソフトにハード的な寿命がある最後の世代だと言えるゲームボーイアドバンス、その過渡期だからこそ発生した「セーブデータと時計データの錯誤」が生み出した問題ですが、何とか自力で解決することができました。
組み込み制御の知識がある程度あって、かつこの時代のソフトを復活させたいのならば、ぜひやってみてはいかがでしょうか。
あ、あくまでも自己責任でお願いしますよ。