Overview
- This is an improved version of TAS Submission #4048.
- By analyzing the order of items and the RAM destruction mechanism, the time was reduced by 29 seconds.
- No playing piano is required.
- Proper nouns are based on Pixel Remaster version.
with my commentary
Alter Cave (first time)
B3
Pick up a total of two potions from each of the three goblins in the first goblin encounter. In theory, they can drop three potions, but there were no conditions available in the fastest run.
Pick up a potion and a leather shield from the treasure box. You can only pick up the second leather shield, the other one is faster to buy than to pick it up.
B2
Pick up a potion from a treasure box. At first glance, this seems like a waste of 6 steps, but it saves 177 frames per one exchange of items in battle. Picking up an item from a treasure box requires 6 steps round trip, or 96 frames, which is faster than exchanging items in battle.
A treasure box of Antarctic Wind is too far away to save time, so I didn't take it.
The Land Turtle battle was defeated in the same summation as TAS Submission #2992 (see below for details).
Alter Cave (second time)
Pick up 2000 gil to buy a lot of stuff in Ur.
Village of Ur
Initialize the script pointer
In the previous TAS, instead of playing the piano to initialize the script pointer, I talked to someone. It's better to talk to people before shopping, to simplify the button operation.
Shopping
The basic policy is shown below.
- Fill the item slots as many as possible to reduce the number of times to exchange weapons in battle.
- The quantity and whether or not it has "X" is more important than the name of the item.
The tool shop sells three kinds of items that don't have an "X", but it takes more than 177 x2 frames to get there and back, so I didn't use it. You don't actually need to go to the armor shop if you just want to fulfill the requirements, but you do for two reasons: to save time filling in the item slots, and to spend less money (leather shields are cheap).
In the TAS, the order is "armor shop->magic shop->weapon shop->everywhere in the village where enemies appear" in order to reduce the number of steps and the number of enemy encounters in Ur. If you don't have a limit on the number of steps you can take and the villagers don't obstruct you, then "Weapons shop -> Armor shop -> Magic shop -> Alter Cave (third time)" is a much easier way to organize items.
Sorting Items
In the TAS, the items are sorted in the following order before the battle.
right hand:leather shield: 1 left hand :leather shield: 1 0:Dagger :12 | 1:Leather Shield :11 2:(blank) : 0 | 3:(blank) : 0 4:(blank) : 0 | 5:(blank) : 0 6:(blank) : 0 | 7:Longsword : 1 8:(blank) : 0 | 9:(blank) : 0 10:potion : 1 | 11:(blank) : 0 12:(blank) : 0 | 13:(blank) : 0 14:(blank) : 0 | 15:(blank) : 0 16:Vest : 3 | 17:Leather Armor : 1 18:(blank) : 0 | 19:Bronze Bracers : 4 20:Leather Cap : 5 | 21:(blank) : 0 22:Nunchaku : 2 | 23:Poisona : 4 24:Staff : 7 | 25:(blank) : 0 (All fields below are blank.)
You can manipulate the necessary variables during a battle by using the following item slots. You don't have to use the same order as above if you follow the conditions.
right hand:Y: 1 left hand:Y : 1 0~15: Y : 1 16:X :3+ | 17:N : 1 18:N : 1 | 19:N :3+ 20:N :3+ | 21:N : 1 22:N : 2 | 23:Poisona :3+ 24:N : 7 | 25:N : 1 26:W : 1 | 27:N : 1 28~31:N : 1
Legend of kind of items
- Y: Items without "X".
- Equipment, or materials that can be equipped.
- X: Items with "X"
- Hand held equipment that cannot be equipped, Hand held equipment that can be equipped or magic orbs
- N: Anything is OK
- E: The ID of the item satisfies "bit7 == 0 && bit1 == 1".
- The following items are available at the beginning of the game.
- Nunchaku, staff, knife, dagger, leather cap, Vest, leather armor
- Write the battle force termination flag.
- W: The ID of the item is not 0x20 to 0x5f. (Not dakuten ゙ or handakuten ゚ as a character).
- Please note that leather shields should not be included among the items available at the beginning of the game.
- If you initialized RAM before the FF3 game, anything is fine as long as it is not blank.
- Poisona: Poisona only
Legend of item quantity
- 0, 1, 2: Set the quantity as specified.
- 3+: 3 or more. 4 is faster to buy.
- 7: 7 for TAS. If you initialized the RAM before the FF3 game, you can store 8.
- Write the high byte of the pointer
Explanation of how items are sorted in the TAS
- The position of the dagger and the leather shield is to speed up the item exchange process.
- Please equip both right and left hands. (The quantity must be 1).
- Items without "X" are less restrictive, so potions and longswords are left in the process of being swapped. If you have any other consumables, you can do the same.
- Make sure that the 16th item of Vest in the item slots has an X in it.
- After the 17th item, it does not matter whether the item has "X" or not.
- the 17th item is a leather armor that satisfies the E requirement but you can use a dagger if it works in the item exchange process.
- Buy 12 daggers and 12 leather shields. In order to reduce the number of times I have to buy items, I have bought 4 items 3 times each. As a result, I have very little money left.
- It is also possible to use a longsword as a replacement instead of a leather shield. This was not quick because it required a combination of shopping for 10 units and removing equipment from some characters.
Encount enemies
This method will not work in the outer world. You have to encounter enemies in the village or in the dungeon.
If you did not initialize RAM before the start of the FF3 game, keep holding the start button and walk after the sorting before the last step, hold down "up, down, right, start, B and A" while encountering enemies. The only direction you can move with this combination is east. The area where enemies appear in the inside of Ur is very narrow, so it is difficult to go east.
If you initialized RAM before starting the FF3 game, there is no restriction on button input before movement.
The following actions are taken in the battle.
- 1st person (first time): Use a knife as an item.
- 2nd person:
- Keep exchanging weapons to fill all the item slots.
- When finished, press B to return.
- 1st person (second time): Press the B button to cancel once and output an invalid string in the command window.
- 1st person (third time): Select the 4th command to terminate the battle.
- In the previous TAS, it outputs an invalid string twice, but this time it only needs to be done once.
After the battle
If you did not initialize RAM before starting the FF3 game, the ending will start after waiting about 30 seconds. The script pointer is increased slowly from 0x07f8 to 0x0821 to move the people in the village in order.
If you initialized RAM before starting the FF3 game, the ending will start immediately. This is because the pointer is 0x08f8 to 0x08f9 and the number of reads is small.
At any rate, after the script system loads data 0xdd, the ending is started.
RNG in the battle
It is basically the same as the explanation in FF2.
Total sum of Zeropage
When switching from the movement screen to the battle screen, the program saves the sum of the zeropage with a carry flag of address $0000-$00ff. The sum is the index to the RNG table in the battle, which determines the number of enemies and the result of the action.
If the parameters of the sum and the party are the same, the result can be easily determined regardless of the elapsed time. The RNG table is prepared in two ways with bit1 of the sum.
For the Goblin and Land Turtle battles, the reason for pressing multiple buttons just before the battle is to adjust the total sum. (The reason for the Killer Bee battle is different.)
RNG index
The RNG index is used for memory addresses $0015, $0016, $0017. The initial value is the total sum of Zeropage, and it is added after RNG calculation. In FF3, the number of variables is increased to three, but $0016 is used as the basic variable.
I have found that in battle with three Goblins, if bit1 of the sum = 0 and address $0016 is data 0xfd at the end of the battle, 3 potions will be dropped. It should be possible to adjust this depending on what you do during the fight, but I couldn't achieve it in the fastest way.
Unused techniques
Omission of conversation
I was able to skip the conversation if I could take out the enemy while he was in a narrow path to the east and west of Ur. I couldn't use it because of the number of steps and walking time.
Another sorting of items
For the undocumented portions, Y:1 is used before X, and N:1 is used after X.
14:EY : 1 | 15:Y : 1 16:Y : 1 | 17:X : 1 18:N :3+ | 19:N :3+ 20:N : 1 | 21:N : 2 22:Poisona :3+ | 23:N : 7 24:N : 1 | 25:W : 1
- EY satisfies E and Y simultaneously. Actual dagger only
- The only thing that satisfies 13:Y:3+ is the longsword, and the amount of money required is tight.
- It has a feature that the order of the item slots is one position in front of the previous one. When I tried to use it, I found it was not fast enough because it needs more operations.
6:Y : 1 | 7:Y :3+ 8:EY : 1 | 9:Y : 1 16:Y :3+ | 17:Y :3+ 18:Y : 1 | 19:X : 2 20:Poisona :3+ | 21:N : 7 22:N : 1 | 23:W : 1
- The placement of the items is quite high up, but since there are three Y:3+ items, it is necessary to go to the tool shop. So I didn't adopt it.
- It's probably also limited by the cost.
Bug description
Overflow the item inventory
- Use an item without X to temporarily remove one item from the item slots.
- Replace items that can be equipped on the next character to fill up the all item slots.
- Cancel the action and write the temporarily disappeared item ID and quantity to the memory after the item slots.
- Overwrite the item with ID 0 as an item and set the quantity to +1.
- Next to the item field is the first character's job number (address $6100) and the ID of Onion Knight is 0, so the ID of the item is placed in the job number.
Invalid long string in the command name buffer
- The string in the battle is expanded by PC:$966a. The input data is the area from $7ad7 to 0x100 bytes, and the string is \0-terminated.
- Normal strings such as "_たたかう\0" (0xff 0x99 0x99 0x8f 0x8c 0) and "_にげる\0" (0xff 0x9f 0x2c 0xb2 0xff 0) are 6 bytes including \0, but selecting an invalid job number will destroy the pre-written \0.
- Data without \0 will be connected to the buffer for the item slots (address $7af5-$7b3b).
- As the item slots fill up, there will be very little \0 inside the buffer, increasing the scope of RAM destruction.
String expansion process
The meaning of the data is as follows.
- 0: End of string
- 1: Newline
- 2: Move the specified number of characters to the right from within the line
- 3 to 0xff: Character
- 0x29-0x5f: character with dankuten ゙ or handakuten ゚
The following flow is written in simple C code.
static uint8_t *pc_95e1(uint8_t *dest, uint8_t line_par_byte) { uint8_t *const src = 0x7ad7; uint8_t x = 0, y = 0; while(src[x] != 0){ uint8_t chara = src[x]; if(chara == 1){ //new line dest += line_par_byte * 2; x += 1; y = 0; continue; }else if(chara == 2){ //move to right x += 1; y += src[x]; x += 1; continue; } if( (chara >= 0x29 && chara < 0x3d) || (chara >= 0x42 && chara < 0x57) ){ //daku-ten chara = remove_phonetic_mark(chara); //eg;ガ->カ, ば->は dest[y - line_par_byte] = 0xc0; //dakuten ゙ }else if( (chara >= 0x3d && chara < 0x42) || (chara >= 0x57 && chara < 0x60) ){ //handaku-ten chara = remove_phonetic_mark(chara); //eg;ぱ->は dest[y - line_par_byte] = 0xc1; //handakuten ゚ } dest[y] = chara; x += 1; y += 1; } return dest; } void pc_9bad(void) { uint8_t *dest = 0x7200; for(uint8_t i = 0; i < 4; i++){ misc(i); dest = pc_95e1(dest, 5); dest += 5 * 2; } }
Breakdown of string variables, item buffers, and Zeropage Images
- $7400-$7403: string IDs for command window
- Because this ID is invalid, $7ad7 and $7af5 are connected as a string.
- This area was controlled so as not to destroy it.
- $7480-$756f: zeropage image $00-$df in the battle
- Rewriting this area leads to the ending.
- $74ec: Value to be returned to $006c after battle, script pointer enable flag
- $74f3: Value to be returned to $0073 after battle, script pointer high byte
- $7570-$757f: zeropage image $f0-$ff in the battle
- $7ad7-$7adc: command name buffer + \0
- $7af5-$7afc: Right hand left hand item ID and quantity
- The right hand and left hand have four item slots before and after equipping,
- Only projectile weapons can have a quantity greater than 2.
- $7afd-$7b3c: The item ID and quantity in the item slots. Same as $60c0-$60ff.
- $7b3d-$7b40: Right hand, left hand, whether it has X or not (0:X, 1:None)
- $7b41-$7b60: Whether items in the item slots have X or not.
- $78d3: End of battle flag
Meaning to fill the item slots with exchanged weapons.
Since the exchanged weapon has ID:1, the item slots will contain one character followed by a newline. Also, since the exchanged weapon does not have an X, there will be up to 32 newlines after the item slots.
How to Control
The large amount of data 1 in the item field is treated as a newline symbol, and the RAM is destroyed with a basic unit of 10 bytes per line.
The string expansion program writes one byte every 10 bytes in a row, using one Knife (0x1f: 1) or one Leather Shield (0x58: 1) with a total of 21 slots filled by the exchange. The addresses that can be written in this way are only multiples of 10.
y = 0; //src = knife = 0x1f dest[y] = 0x1f; y += 1 //src = 1 (qty) dest += 5 * 2; y = 0; //src = leather shield = 0x58 dest[y - 5] = 0xc1; dest[y] = 0x50; y += 1 //src = 1 (qty) dest += 5 * 2; y = 0;
However, by using items whose quantity is 2, 3 or more, you can write addresses that are not multiples of 10.
//src = bronze bracers = 0x8b dest[y] = 0x8b; y += 1; //src = 4 (qty) dest[y] = 4; y += 1; //src = leather cap = 0x62 dest[y] = 0x62; y += 1; //src = 5 (qty) dest[y] = 5; y += 1;
The address and data to be written using this bug are the following 3 bytes.
- $74ec: 5 (non-zero value)
- $74f3: 7
- $78d3: 0x72 (bit7 == 0 && bit1 == 1)
This is the content of the RAM just before the invalid string was loaded.
In the process of RAM corruption by invalid strings, important variables are written in the following steps.
dest = $74e9
- $74e9: item #19 ID (Bronze Bracers, 0x8b)
- $74ea: item #19 qty (4)
- $74eb: item #20 ID (Leather Cap, 0x62)
- *$74ec*: item #20 qty (5)
- $74ed: item #21 ID
- (qty = 1, newline)
dest = $74f3
- $74f3: item #22 ID (Nunchaku, 0x06)
- (qty = 2, ID= 0xfd (Poisona) increases y to 0xfe)
- $75f1: item #23 qty (3)
- $75f2: item #24 ID (Staff, 0x0e)
- *$74f3*: item #24 qty (7), Since y is 8 bits, y = 0.
- $74f4: item #25 ID
- (qty = 1, newline)
dest = $74fd
- $74fd: item #26 ID (Dagger, 0x1f)
- If a leather shield is placed here, ID 0x58 becomes "ピ" as a character, and address 0x74f8->0x0078 contains data 0xc1 (handakuten ゚).
- address 0x0078 is a world map ID, if it is rewritten, the party will not return to Ur after the battle, the contents of address $07f8-$07ff will change, and the script pointer will not reach address $0021.
- (qty = 1, newline)
dest = $78d1
- $78d1: item #16 ID (Vest, 0x72)
- $78d2: item #16 qty (3)
- *$78d3*: item #17 ID (Leather Armor, 0x73)
- (qty = 1, newline)
After rewriting the variables
The first person's fourth command will be the end of everyone's command selection. If you choose it, the program will check the end-of-battle flag and terminate the battle.
After the battle is over, the data of address $74ec is restored to address $006c. This is valid if it is a non-zero value. The data of address $74f3 will be restored to address $0073, and $07f8 will be the reference for the script pointer, along with the data from the conversation.
The script will be executed slowly in order, and when the pointer reaches $0021, the address will contain data 0xdd. The 0xdd means to start the ending.
Other analysis data
Ruby and Lua scripts are available for your reference.
概要
- TAS submission #4048 の改善版です.
- アイテムの並びと RAM 破壊の仕組みを解析し、約29秒早くなりました.
- ピアノ演奏はしません.
- 任意コード実行とサブフレームリセットは使用していません.
祭壇の洞窟 (1回目)
地下3階
最初のゴブリン戦で3匹のゴブリンからポーションを2つ拾います. 理論上3つのポーションが落ちるようですが、最速プレイとしては利用できる条件ではありませんでした.
宝箱からポーションとかわのたてを拾います. かわのたては2個目のみです. もう1個のかわのたては購入したほうが早いです.
地下2階
宝箱からポーションを拾います. 一見6歩無駄ですが、戦闘中のアイテム交換を 1回(177 frame)削減できます. 宝箱のための往復での6歩は 96 frame なのでとります.
なんきょくのかぜは遠く時間削減ができないので取りませんでした.
ランドタートル戦は TAS submission #2992 と同じ総和(詳細は後述)で倒しました. 手動操作では運が必要です.
祭壇の洞窟 (2回目)
ウルの村で大量の物資を購入するため 2000 ギルを拾得します.
ウルの村
スクリプトポインタの初期化
前回の TAS ではピアノを弾いてスクリプトポインタを初期化していたのですが、その代わりにそこらへんの人と会話します. ボタン操作の簡略化のため、買い物前に会話するほうがいいです.
買い物
基本方針は下記です.
- 戦闘中の武器交換回数をへらすためにアイテム欄はできるだけ埋める.
- アイテムの名称より数量とXの有無のほうが重要.
道具屋は X がつかないアイテムが3種類売ってるのですが、往復に 177 x2 frame 以上かかるので採用していません. 防具屋も実はいかなくても条件を満たせるのですが、アイテム欄を埋める時間の節約と消費金額が少なさ(装備できるかわのたてが安い)の2つの理由で行きます.
TAS では歩数の都合でウルの村で敵と遭う都合とアイテム整理の操作削減のため「防具屋->魔法屋->武器屋->村内の敵が出る場所」の順番に行きます. 歩数の制限と村民たちの妨害がなければ、「武器屋->防具屋->魔法屋->祭壇の洞窟(3回目)」のほうがアイテム整理は楽です.
アイテム整理
TAS では戦闘前に下記の並びにしています.
右手:かわのたて: 1 左手:かわのたて: 1 0:ダガー :12 1:かわのたて :11 2:(空欄) : 0 3:(空欄) : 0 4:(空欄) : 0 5:(空欄) : 0 6:(空欄) : 0 7:ロングソード : 1 8:(空欄) : 0 9:(空欄) : 0 10:ポーション : 1 11:(空欄) : 0 12:(空欄) : 0 13:(空欄) : 0 14:(空欄) : 0 15:(空欄) : 0 16:ふく : 3 17:かわよろい : 1 18:(空欄) : 0 19:どうのうでわ : 4 20:かわのぼうし : 5 21:(空欄) : 0 22:ヌンチャク : 2 23:ポイゾナ : 4 24:つえ : 7 25:(空欄) : 0 (以下すべて空欄)
戦闘中にアイテム欄を下記にすると必要な変数をうまく操作できます. 条件を守れば上記の並びと同じにしなくていいです.
右手:Y : 1 左手:Y : 1 0から15: Y : 1 16:X :3+ 17:N : 1 18:N : 1 19:N :3+ 20:N :3+ 21:N : 1 22:N : 2 23:ポイゾナ:3+ 24:N : 7 25:N : 1 26:W : 1 27:N : 1 28から31:N : 1
アイテム種類の見方
- Y: X がつかないもの
- 装備できる手につける装備品, または消耗品
- X: X がつくもの
- 装備できない手につける装備品, 手につけない装備品, 魔法のオーブ
- N: どれでも可能
- E: ID の bit7 == 0 && bit1 == 1 のもの
- 序盤で手に入るものは下記.
- ヌンチャク,つえ,ナイフ,ダガー,かわのぼうし,ふく,かわよろい
- 戦闘強制終了フラグを書き込む
- W: ID で 0x20 から 0x5f ではないもの. (文字として濁音か半濁音ではないもの)
- 序盤で手に入るものはかわのたてをいれてはいけないので注意
- FF3 のゲーム前に RAM を初期化しているのなら条件外
- ポイゾナ: ポイゾナのみ可
数量の見方
- 0,1,2: 指定通りの数量をいれてください.
- 3+: 3個以上. 4個だと早く買えます.
- 7: TAS では 7 個. FF3 のゲーム前に RAM を初期化しているのなら 8 個.
- ポインタの上位バイトを書き込む
TAS での並びの解説
- ダガーとかわのたてはアイテム交換を早くすませるためです.
- 右手左手両方に装備をしてください. (数量は1が必須)
- X がつかないアイテムは位置の制限が緩いので、ポーションとロングソードは位置交換の過程でそのままにしてます. もしほかに消耗品があれば同様の処置で構いません.
- 16番のふくはかならず X がつくものをおいてください.
- 17番以降は X の有無は関係ありません.
- 17番では E の条件をみたすかわよろいをおいていますが、アイテム交換の過程でうまくいくならダガーで構いません.
- ダガーとかわのたては12個ずつ買います. アイテム購入操作回数をへらすために 4 個単位で3回買っています. そのため残金がわずかです.
- かわのたてのかわりにロングソードを交換品にすることも一応可能です. 10単位での買い物を組み合わせた上に、複数人から装備品を外す必要があり、早くありませんでした.
敵に遭う
今回の方法は外では成功しません. 村の中かダンジョンで遭ってください.
FF3 のゲーム前に RAM を初期化していない場合はアイテム整理後スタートボタンを押しながら移動して、最後の1歩は"上下右スタートBA" を押して敵に遭ってください. この組み合わせで移動できる方向は東だけです. ウル内部の敵がでてくる場所はせまく、東に移動しづらいです.
FF3 のゲーム前に RAM を初期化している場合は移動前のボタン入力の制限はありません.
戦闘では下記の行動です.
- 1人目(1度目): アイテムでナイフを使う
- 2人目:
- アイテム欄をすべて埋めるために武器を交換し続ける
- 終わったら B ボタンで戻る
- 1人目(2度目): B ボタンで一旦キャンセルし、コマンド欄に不正な文字列を出す
- 1人目(3度目): 4番目のコマンドを選び戦闘強制終了
- (前回のTASでは2度不正な文字列を出してましたが、今回は1度でいいです)
戦闘後
FF3 のゲーム前に RAM を初期化していない場合は 30 秒程度待つとエンディングが始まります. これは pointer が 0x07f8 から 0x0821 まで順番に村内の人をゆっくり動かしているのが原因です.
FF3 のゲーム前に RAM を初期化している場合はすぐにエンディングが始まります. こちらは pointer が 0x08f8 から 0x08f9 で読み込み数が少ないためです.
どちらの場合でもスクリプトとして data 0xdd を読み込めばエンディングが始まります.
戦闘時の乱数
基本的に FF2 での説明と同じです.
Zeropage 総和
移動画面から戦闘画面に切り替わるときにプログラムは address $0000-$00ff の zeropage の carry 込みの総和を保存します. 総和は戦闘時の乱数 table への index となり、敵の数や行動の結果が決まります.
総和とパーティのパラメータが同じであれば、経過時間は関係なしに容易に結果が確定できます. 乱数 table の中身は総和の bit1 で 2 通り用意されています.
ゴブリン戦とランドタートル戦は戦闘直前にボタンを複数押すのは総和の調整のためです. (キラービー戦は別の理由)
乱数 index
乱数 index は memory address $0015, $0016, $0017 に利用され、初期値は総和で、乱数計算後に都度加算されていきます. FF3 では変数が3つに増えましたが、基本は $0016 を利用しているようです.
ゴブリン3匹では総和 bit1 = 0 で、戦闘終了時に address $0016 が data 0xfd の場合3個のポーションが落ちることがわかっています. 戦闘中の行動次第で調整できるはずですが、最速ではその実現は不可能でした.
その他見つかったが実用化されなかったもの
会話の省略
ウルの東西の細い道にある程度入った状態で敵を出せれば会話は省略できました. 歩数調整や徒歩時間の都合で使えませんでした.
別のアイテムの並び
未記載分は X 以前は Y:1, X 以後は N:1
14:EY : 1 15:Y : 1 16:Y : 1 17:X : 1 18:N :3+ 19:N :3+ 20:N : 1 21:N : 2 22:ポイゾナ:3+ 23:N : 7 24:N : 1 25:W : 1
- EY は E と Y を同時に満たすもの. 実質ダガーのみ.
- 13:Y:3+ がロングソードのみで必要金額が厳しい.
- アイテム欄の並びが1つ手前に来るのが特徴. 実際に使ってみたところは手数が多いらしく早くなかった.
6:Y : 1 7:Y :3+ 8:EY : 1 9:Y : 1 16:Y :3+ 17:Y :3+ 18:Y : 1 19:X : 2 20:ポイゾナ:3+ 21:N : 7 22:N : 1 23:W : 1
- アイテム欄がかなり手前に来るのだが、 Y:3+ が3種類いるので道具屋に行くことが必須となったので不採用.
- おそらく金額の制限にもひっかかる.
バグの解説
アイテム欄を溢れさせる
- Xがつかないアイテムを使用してアイテム欄から一時的にアイテムを1つ消す
- 次のキャラが装備できるアイテムをつけかえてアイテム欄を満タンにする
- 行動をキャンセルして一時的に消えたアイテムIDと数量をアイテム欄以降のメモリに書き込む
- アイテムとして ID が 0 のものを上書き、数量を +1 する
- アイテム欄の次は最初のキャラのジョブ番号(addres $6100)でたまねぎ剣士の ID は 0 のため、ジョブ番号にアイテムの ID が入る
コマンド名のための不正な長い文字列
- 戦闘中の文字列は PC:$966a により展開されます. 入力データは $7ad7 から 0x100 byte の領域で、文字列は \0 終端です.
- _たたかう\0 (0xff 0x99 0x99 0x8f 0x8c 0), _にげる\0 (0xff 0x9f 0x2c 0xb2 0xff 0) などの正常な文字列は \0 を含めて 6 バイトですが、不正なジョブ番号を選ぶと、事前に書き込まれた \0 を破壊します.
- \0 がないデータはアイテム欄のためのバッファ(address $7af5-$7b3b) に繋がります.
- アイテム欄が埋まるとバッファ内部も \0 がほとんどなくなり、 RAM 破壊の範囲が広がります.
文字列の展開処理
データの意味は下記になっています.
- 0: 文字列の終了
- 1: 改行
- 2: 行内から指定数右に移動
- 3 から 0xff: 文字
- 0x29-0x60: 濁音、半濁音記号付き文字
簡単な C のコードで書くと下記の流れです.
static uint8_t *pc_95e1(uint8_t *dest, uint8_t line_par_byte) { uint8_t *const src = 0x7ad7; uint8_t x = 0, y = 0; while(src[x] != 0){ uint8_t chara = src[x]; if(chara == 1){ //new line dest += line_par_byte * 2; x += 1; y = 0; continue; }else if(chara == 2){ //move to right x += 1; y += src[x]; x += 1; continue; } if( (chara >= 0x29 && chara < 0x3d) || (chara >= 0x42 && chara < 0x57) ){ //daku-ten chara = remove_phonetic_mark(chara); //eg;ガ->カ, ば->は dest[y - line_par_byte] = 0xc0; //dakuten ゙ }else if( (chara >= 0x3d && chara < 0x42) || (chara >= 0x57 && chara < 0x60) ){ //handaku-ten chara = remove_phonetic_mark(chara); //eg;ぱ->は dest[y - line_par_byte] = 0xc1; //handakuten ゚ } dest[y] = chara; x += 1; y += 1; } return dest; } void pc_9bad(void) { uint8_t *dest = 0x7200; for(uint8_t i = 0; i < 4; i++){ misc(i); dest = pc_95e1(dest, 5); dest += 5 * 2; } }
文字列変数, アイテムバッファ, Zeropage Image の内訳
- $7400-$7403: string IDs for command window
- この ID が不正なため、 $7ad7 と $7af5 が文字列としてつながる.
- この領域は破壊しないように制御した.
- $7480-$756f: zeropage image $00-$df in the battle
- この領域の書き換えがエンディングにつながる
- $74ec: 戦闘後 $006c に復帰させる値, script pointer enable flag
- $74f3: 戦闘後 $0073 に復帰させる値, script pointer high byte
- $7570-$757f: zeropage image $f0-$ff in the battle
- $78d3: 戦闘終了フラグ
- $7ad7-$7adc: command name buffer + \0
- $7af5-$7afc: 右手左手の item ID と 数量
- 右手左手は装備前、装備後で 4 つのアイテムスロットがある,
- 数量を2以上にできるのは投擲武器のみ.
- $7afd-$7b3c: アイテム欄の item ID と数量. $60c0-$60ff と同じ.
- $7b3d-$7b40: 右手左手の X の有無 (0:X, 1:なし)
- $7b41-$7b60: アイテム欄の X の有無
制御方法
アイテム欄に存在する大量の data 1 は改行記号として扱われ、1 行 10 byte を基本単位で RAM が破壊されていきます.
文字列展開プログラムは交換によって埋められた合計21スロットのナイフ1本(0x1f, 1) またはかわのたて1枚(0x58, 1)によって 10 byte ごとに 1 byte を連続して書くことになります. この方法で書き込めるアドレスは 10 の倍数だけです.
y = 0; //src = knief = 0x1f dest[y] = 0x1f; y += 1 //src = 1 (qty) dest += 5 * 2; y = 0; //src = leather shield = 0x58 dest[y - 5] = 0xc1; dest[y] = 0x50; y += 1 //src = 1 (qty) dest += 5 * 2; y = 0;
しかし数量 2 や 3 以上を利用することによって 10 の倍数ではないアドレスも書き込むことができます.
//src = bronze bracers = 0x8b dest[y] = 0x8b; y += 1; //src = 4 (qty) dest[y] = 4; y += 1; //src = leather cap = 0x62 dest[y] = 0x62; y += 1; //src = 5 (qty) dest[y] = 5; y += 1;
今回のバグ利用で書き込みたいアドレスとデータは下記の 3 bytes です.
- $74ec: 5 (0 以外)
- $74f3: 7
- $78d3: 0x72 (bit7 == 0 && bit1 == 1)
これは不正な文字列を読み込む直前の RAM の内容です.
不正な文字列による破壊の過程で重要な変数は下記の手順で書き込まれます.
dest = $74e9
- $74e9: item #19 ID (どうのうでわ, 0x8b)
- $74ea: item #19 qty (4)
- $74eb: item #20 ID (かわのぼうし, 0x62)
- *$74ec*: item #20 qty (5)
- $74ed: item #21 ID
- (qty = 1, 改行)
dest = $74f3
- $74f3: item #22 ID (ヌンチャク, 0x06)
- (qty = 2, ID= 0xfd (ポイゾナ) で y が 0xfe に増加)
- $75f1: item #23 qty (3)
- $75f2: item #24 ID (つえ, 0x0e)
- *$74f3*: item #24 qty (7), y が 8 bit のため y = 0.
- $74f4: item #25 ID
- (qty = 1, 改行)
dest = $74fd
- $74fd: item #26 ID (ダガー, 0x1f)
- ここにかわのたてが入ると、ID 0x58 は文字として"ピ"となり、address 0x74f8->0x0078 に data 0xc1 (半濁音記号) が入ってしまう.
- address 0x0078 は World map ID で書き換えると戦闘後にウルの村に戻らなくなり、address $07f8-$07ff の内容が変わってしまい、script pointer が address $0021 に到達しない
- (qty = 1, 改行)
dest = $78d1
- $78d1: item #16 ID (ふく, 0x72)
- $78d2: item #16 qty (3)
- *$78d3*: item #17 ID (かわよろい, 0x73)
- (qty = 1, 改行)
変数書き換えのあと
1人目のコマンドその4が全員のコマンド選択終了になります. それを選ぶとプログラムは戦闘終了フラグを参照し、戦闘を強制終了します.
戦闘強制終了後 address $74ec の内容は address $006c にコピーされます. これは 0 でなければ有効になります. address $74f3 の内容は address $0073 にコピーされて、会話した時の data と合わせて $07f8 が script pointer の参照先となります.
script は順番にゆっくりと実行されていき、 pointer が $0021 になったとき、そのアドレスにはエンディング開始のデータ 0xdd が入り、エンディングが始まります.
その他解析資料
Ruby と Lua のスクリプトがありますのでご覧ください.
Special Thanks
Pirohiko, Mimi-Hisakaki, LunaTsukinashi
arkiandruski: Claimed.
arkiandruski: Good improvement. Accepting.
EZGames69: Processing…