Faults of the original game, fixed. Many were documented by players over thirty years; some were found while making this ROM hack. Each entry says what 1.0 did and what the game does now.
Battle rules: damage, status and commands
#1 The Sketch glitch can no longer corrupt the save file or inventory.
In 1.0
If Relm's Sketch missed and Mute was in the lead party member's 28th spell slot, the game could garble the screen, freeze, fill the inventory with random items or erase saves.
Now
A missed Sketch now just misses, leaving the inventory and saves alone.
For ROM hackers 1.0 addresses
Square's own fix, the 1.1 revision (the disassembly's ROM_VERSION=1). The Sketch animation init magic_type2f (C2/F5D2) reads the target byte at C2/F5E4 and uses it, doubled, as an index into the monster number list at $7E2001. With no valid target the byte is negative, the index lands outside the list, and garbage loads as monster graphics. In 1.1 a negative byte skips the lookup and passes X = $FFFF to LoadSketchMonsterGfx (C1/24D1); LoadSummonGfxProp (C1/24F5) reads $FFFF as graphics 0, and LoadSketchMonsterGfx then clears the graphics buffer. The revision also reorders MonstersToBG1 and the summon loaders and adds an hblank wait to animation command $77 (C1/DB50), which shifts bank C1.
#2 The Evade stat now works against physical attacks, giving the Blind status an actual effect.
In 1.0
The Evade stat did nothing, and Blind, the Beads and the statuses meant to change hit rates had no effect on hits.
Now
Evade now decides whether physical attacks miss while MBlock covers magic, and Blind, the Beads and statuses such as Muddled or Haste change hit rates.
For ROM hackers 1.0 addresses
CheckHit, C2/220D. The block at C2/233F reads the carry as the attack type: set runs Evade and the hit rate modifiers, clear runs MBlock. On the physical path the Retort test's lsr clears the carry first, so physical attacks roll against MBlock. Insert sec (1 byte) before the beq at C2/232C. The disassembly's BUGFIX_EVADE switch is Terii Senshi's longer repair of the same fault.
Credit: previously fixed by Terii Senshi, my implementation
#8 Cyan's Retort stance now clears when he dies, and when an Imp's queued Bushido becomes a plain attack.
In 1.0
Cyan's Retort stance survived a knock-out, so once revived he countered any physical attack in the battle, even those aimed at others. Turned into an Imp while waiting, he countered with plain attacks and never left the stance, and together the two made him attack without end until the battle was over.
Now
The stance now ends when Cyan is knocked out and when an Imp's queued Bushido becomes a plain attack, so the loop can no longer start.
For ROM hackers 1.0 addresses
Retort's stance is bit 0 of $3E4C (retaliation flags). Two places leave it set. setnowdead (C2/46A9), called when a status kills or petrifies, runs with 16-bit A: insert before its rts at C2/46AF (9 bytes) a clear of bit 0 of $3E4C,y. InitPlayerAction (C2/0276) turns an Imp's command that Imps cannot use into Fight after the bne at C2/02B7; insert at C2/02B9 (8 bytes, 8-bit A) a clear of bit 0 of $3E4C,x.
; setnowdead, before the rts at C2/46AF (A is 16 bits)
lda $3e4c,y
and #$fffe ; retort stance off
sta $3e4c,y
; InitPlayerAction, before the stz at C2/02B9 (A is 8 bits)
lda $3e4c,x
and #$fe ; retort stance off
sta $3e4c,x
Credit: previously found and fixed by Terii Senshi and Leet Sketcher, my implementation
#9 Rippler no longer swaps hidden statuses, so Shadow keeps Interceptor, Morph stays with Terra, and Near Fatal no longer lets a relic cast its spell at full HP.
In 1.0
The Lore Rippler, meant to swap ordinary statuses between caster and target, swapped almost everything, so Shadow could lose Interceptor for good, Morph and Rage could pass to other party members, and Near Fatal could pass to a healthy party member, so a Barrier Ring, MithrilGlove or Czarina Ring cast its spell at full HP.
Now
Rippler now leaves alone eleven states, among them KO, Petrify, Near Fatal, Morph, Rage, Dance and Interceptor, and still swaps ordinary statuses such as Poison, Haste and Slow.
For ROM hackers 1.0 addresses
TargetEffect_21 (C2/3C04), Rippler. $EE is built as the statuses the two sides do not share (the not_a at C2/3C0F for status 1 and 2, at C2/3C30 for 3 and 4), and each side's share moves to the other. Insert after each not_a an and # (3 bytes each) that keeps the fixed statuses out of the swap: $FD37 after the first (Magitek, Petrify, Wound, Near Fatal) and $84FE after the second (Dance, Rage, Frozen, Morph, Control, Hide, Interceptor). This is the GBA release's list.
; after the not_a at C2/3C0F
and #$fd37
; after the not_a at C2/3C30
and #$84fe
Credit: previously found by Terii Senshi, Leet Sketcher and Master ZED and fixed by Terii Senshi and Leet Sketcher, my implementation
#18 A Smoke Bomb that cannot work now says 'Can't escape' like a failed Warp, instead of the run command's 'Can't run away'.
In 1.0
A Smoke Bomb used in a battle that blocks escape items failed with 'Can't run away', the message for holding L and R to run, where a failed Warp says 'Can't escape'.
Now
A failed Smoke Bomb now says 'Can't escape'.
For ROM hackers 1.0 addresses
AttackerEffect_4b (C2/40C8), the Smoke Bomb's escape effect. When the party cannot run it loads battle message 9, the Run command's message, with the lda # at C2/40D4. Change the immediate to 10 ($0A), the message the Warp effect loads when it fails (C2/405A). Same size, in place.
Credit: previously found by everything8215, my implementation
#27 Vanish plus Doom no longer kills what Doom alone cannot, and Life spells and ChokeSmoke no longer land on a living target with Vanish: death protection and the undead check come first.
In 1.0
Every spell hit an enemy with Vanish and skipped the death protection check, so Vanish followed by Doom or X-Zone killed almost anything outright, including bosses immune to instant death. Life, Life 2, the Phoenix summon and ChokeSmoke also landed on a vanished living target, healing it or turning it Zombie.
Now
A vanished target now keeps its protection from instant death, and Life spells and ChokeSmoke miss it while it lives, as they do without Vanish.
For ROM hackers 1.0 addresses
CheckHit, C2/220D. On a vanished target, magic (carry clear after the lsr at C2/2229) takes the jmp at C2/222A to the hit test at C2/22B3. That skips the two tests at C2/224B: instant death protection ($11A2 bit 1 with $3AA1,y bit 2) and the undead check ($11A2 bit 2, then $3EE4,y eor $3C95,y, plus is a miss). Insert after the lsr (32 bytes with short branches; in free space the exits become jmp): carry set still goes to C2/22B3, so a physical attack misses; otherwise run both tests, going to C2/22B5 (miss) if either fails, then clc and on to C2/22B3. #397 adds a life-effect test to this path.
Credit: as in the GBA release, previously found by Master ZED and fixed by Terii Senshi and Assassin, my implementation
#36 The Wind God Gau exploit is closed: a Rage's follow-up weapon spell no longer inherits the special attack's damage bonus.
In 1.0
With the Merit Award and the Tempest equipped, when Gau's Rage used an enemy's special attack and the Tempest then cast Wind Slash, the spell was treated as that special attack and took its damage multiplier and other properties.
Now
The spell now counts as an ordinary attack, as after a plain Fight.
For ROM hackers 1.0 addresses
_c237eb (C2/37EB) sets up a weapon's follow-up spell (the Tempest's Wind Slash) with InitTarget and InitAttacker but leaves $3412, the attack type, as the swing left it. After a Rage's monster special it is still 6, so CalcAttackEffect (C2/3292) takes the monster-special path again at its test at C2/32EE, and the spell gets the special's damage multiplier and properties. Insert stz $3412 (3 bytes) after the jsr InitAttacker, before the stz $11A5 at C2/37FF.
#55 The magic damage routine now checks its final carry, so very high damage no longer wraps around.
In 1.0
Magic damage past 65,535 wrapped round to a small number.
Now
Damage now stops at the maximum, so the strongest spells hit at full strength.
For ROM hackers 1.0 addresses
CalcMagicDmg (C2/2B69) ends by adding its second term to $11B0 (the adc at C2/2B94) and never tests the carry. Insert before the sta at C2/2B97 (4 bytes): bcc over tdc and dec, so the sum saturates at $FFFF.
Credit: previously found and fixed by Assassin, my implementation
#56 The Genji Glove's quarter damage cut now applies only when a weapon is held in each hand, as intended.
In 1.0
The Genji Glove lets a party member hold a weapon in each hand, with a quarter cut to physical damage meant for dual wielding, but the cut hit a wearer holding one weapon or none, and never one holding two.
Now
The quarter cut now applies only when a weapon is held in each hand.
For ROM hackers 1.0 addresses
UpdateEquipBattle (C2/286D). $11CF is $10 only when both hands hold a weapon (UpdateEquip_03 sets it). The trb at C2/28BD clears the Genji Glove bit in $11D8 with that value, so the bit, which the three-quarter cut at C2/2C0B tests, survives exactly with one weapon or none. Insert eor #$10 (2 bytes) after the lda at C2/28BB, so the bit is cleared unless two weapons are equipped.
Credit: previously found and fixed by Assassin, my implementation
#57 The Row command's display now matches the row actually used in damage math after a pincer attack.
In 1.0
A pincer attack puts the whole party in the front row. Using Row there showed no change on screen, yet it still switched the row damage used, so a party member could take and deal damage for a row they were not standing in.
Now
Row now changes nothing in a pincer, so damage always matches the front row.
For ROM hackers 1.0 addresses
Row command at C2/194C: tyx, then an lda, eor and sta that flip $3AA1 bit 5, the row the damage maths reads. In a pincer the row-change step tables _c2a86f (C2/A86F, leaving the back row) and _c2a87f (C2/A87F, leaving the front row) hold 0, so the sprite does not move, but the bit flips anyway. Replace the eight bytes at C2/194D with jsr and five nop. The routine (42 bytes in bank C2) flips the bit only when the table entry for this battle type ($201F) and character is nonzero. #530 does the same for R. Polarity.
jsr MyRow ; at C2/194D, then 5 nop
MyRow: tya
lsr ; character 0 to 3
pha
lda $201f ; battle type
asl
asl
clc
adc 1,s
tax
pla
lda $3aa1,y
and #$20 ; row it stands in
beq @front
lda f:_c2a86f,x
bra @test
@front: lda f:_c2a87f,x
@test: beq @same ; no step: keep the row
lda $3aa1,y
eor #$20
sta $3aa1,y
@same: tyx
rts
Credit: previously found and fixed by Assassin, my implementation
#58 Suplex no longer has its damage halved as if it hit multiple targets.
In 1.0
Sabin's Suplex throws a single enemy, but the game treated it as a multi-target attack and halved its damage whenever more than one enemy could be picked.
Now
Suplex now hits for full damage.
For ROM hackers 1.0 addresses
Suplex, attack 95 ($5F) in MagicProp (C4/6AC0, 14 bytes a record). It is declared multi-target, so the split-damage step divides the damage before the special effect narrows the attack to one monster. Set bit 6 (no damage split) of the record's first flags byte, at C4/6FF4.
Credit: previously found and fixed by Assassin, my implementation
#59 With the Offering, the Valiant Knife's missing-HP bonus is now halved along with the rest of each swing.
In 1.0
With the Offering, the Valiant Knife's bonus for lost HP was not halved with each of the four swings, so a badly hurt wielder struck four times at close to full strength.
Now
The bonus is now halved along with the rest of each swing.
For ROM hackers 1.0 addresses
CalcDmg halves an Offering wearer's swing before the attacker effect runs, so the Valiant Knife's missing-HP bonus (AttackerEffect_0a) is added whole. Replace the jsr DoAttackerEffect at C2/33A6 with a jsr to a routine (about 50 bytes in bank C2) that pushes $11B0, calls DoAttackerEffect, and then, when $B2 bit 1 is set (an Offering wearer's weapon swing), $3414 is nonzero (the effect added to the figure instead of building its own) and the attacker's $3C58 bit 0 (Offering) is set, and $11B0 grew, stores the old figure plus half the growth.
Credit: previously found and fixed by Leet Sketcher, my implementation
#60 The Dice weapons are now halved by the Offering like other weapons.
In 1.0
Setzer's Dice and Fixed Dice skipped the Offering's halving, so with the Offering every dice attack hit at full strength.
Now
Dice damage is now halved by the Offering, like every other weapon's.
For ROM hackers 1.0 addresses
AttackerEffect_09 (C2/4158), the Dice effect, builds $11B0 from the dice and clears $3414, so no damage modifier, the Offering's halving included, reaches it. Insert before the lda at C2/41DB (12 bytes): lda $3C58,y, lsr, bcc over lsr $11B1 and ror $11B0. This halves the figure when the attacker (Y) wears the Offering (bit 0).
Credit: previously found and fixed by Leet Sketcher, my implementation
#62 The undead branch now flips the heal-or-damage flag instead of clearing it, so damage can become healing too.
In 1.0
Seizure hurt undead enemies like anyone else, while most undead absorbed Poison, Bio, Acid Rain and Virite, which were meant to hurt them.
Now
Seizure now heals the undead, and Poison, Bio, Acid Rain and Virite hurt undead enemies that absorb poison.
For ROM hackers 1.0 addresses
CalcTargetDmg (C2/0B83). For an attack that inverts on undead ($11A2 bit 3) against an undead monster, the stz $F2 at C2/0BBB clears the heal flag outright, then a bra goes to the drain flip at C2/0BC6. Healing becomes damage, but damage never becomes healing. Insert 4 bytes there: the stz and the bra (4 bytes) become lda $F2, eor #$01, sta $F2 and a bra to C2/0BD3 (8 bytes), past the drain flip, which would undo the eor for a drain.
Credit: previously found and fixed by Master ZED, my implementation
#63 Launcher and Super Ball no longer leave battle stuck when everyone is airborne from Jump, nor a Super Ball when Palidor, its caster Muddled after choosing it, lifts every enemy.
In 1.0
If Launcher or Super Ball was used while every target was in the air from Jump, or a Super Ball while every enemy was lifted by a Palidor whose caster was Muddled after choosing it, the game would soft lock.
Now
The attack now ends properly and the battle carries on.
For ROM hackers 1.0 addresses
CalcReflectDmg (C2/3483) also runs Launcher and Super Ball: $3405 holds the hits left and is counted down to $FF as targets are found. When ChooseTarget finds nobody (every target in the air from Jump, or every enemy lifted by a Palidor whose caster was Muddled after choosing it), the loop ends through the dey2 at C2/350F with $3405 still positive, so the battle waits for hits that never come. Insert before the pla at C2/3513 (9 bytes): sep #$20, lda #$FF, sta $3405, rep #$20.
Credit: previously found by Assassin and Master ZED and fixed by Assassin, my implementation
#64 A drain on an invincible target no longer heals the attacker.
In 1.0
Draining an invincible target, with Osmose or a draining weapon, did no damage, yet the attacker still regained HP or MP.
Now
The attacker now gains nothing when the target loses nothing.
For ROM hackers 1.0 addresses
The drain block at C2/0C5D (CalcMaxDmg, C2/0C2D) calls FixDrainDmg (C2/0DED) and books the attacker's healing before the damage to invincible targets ($3A3C) is zeroed. Insert before the jsr FixDrainDmg at C2/0C64 (12 bytes): rep #$20, lda $3018,y, bit $3A3C, sep #$20, bne to C2/0C75, which skips the drain when the target is invincible.
Credit: previously found and fixed by Assassin, my implementation
#65 The battle list now reads Step Mine's cost correctly instead of a value the menu can overwrite.
In 1.0
Step Mine's MP cost grows with play time, but in battle the game read the time from the main menu's own copy of the clock, so the cost could stay low or drop to zero.
Now
The cost in battle now follows the real play time.
For ROM hackers 1.0 addresses
InitSpellList (C2/5551) sets Step Mine's cost to play time / 30 from $1864 (minutes) and $1863 (hours), the field menu's copy of the clock, which the menu also fills with the event timer while timer 0 runs. Change the operand of the lda at C2/5673 to $021C and of the lda at C2/5678 to $021B, the live play-time minutes and hours. Same size.
Credit: previously found and fixed by Assassin, my implementation
#77 The Offering and Genji Glove cuts now apply only to weapon attacks, not to Jump, Blitz, Bushido or Tools.
In 1.0
The Offering's halving and the Genji Glove's quarter cut fell on every physical attack, so a wearer's Jump, Blitz, Bushido and Tools were cut too.
Now
The cuts now fall only on the swings of the Fight command and Capture.
For ROM hackers 1.0 addresses
CalcDmg (C2/2B9D) applies the Offering's halving and the Genji Glove's 3/4 cut to every physical attack by a character, whatever the command. Mark the Fight command's own swings instead: in FightAttack (C2/161B) insert lda #$08, trb $B3 (4 bytes) before the jsr at C2/1629. $B3 is $FF at the head of every action and nothing else uses its bit 3. In CalcDmg insert before the lda at C2/2C02: lda $B3, and #$0008, bne to C2/2C1F (7 bytes, A is 16-bit), which skips both cuts for any other command.
Credit: previously found and fixed by Leet Sketcher, my implementation
#78 The row used for damage now updates when the formation changes mid-battle, matching what is drawn.
In 1.0
When a new wave of enemies arrives partway through a battle, it can attack from behind, and the party is redrawn in the matching rows, yet damage was still worked out from the rows they stood in before.
Now
The rows used for damage now change along with the rows on screen.
For ROM hackers 1.0 addresses
The battle-change action (action command $20, C2/5072) loads the new formation and calls ChooseBattleType at C2/50B6, but not InitBattleType (C2/2E68), which applies the battle type to the rows: a back attack toggles $3AA1 bit 5, the row the damage maths reads, a pincer clears it, and its closing loop copies the bit to the character sprites. Insert jsr InitBattleType (3 bytes) after the jsr ChooseBattleType, before C2/50B9.
Credit: previously found and fixed by Assassin, my implementation
#80 Changing gear mid-battle or leaving a Rage now correctly rebuilds the immunity masks.
In 1.0
Gear and Rages make a status permanent by granting immunity to it, and taking gear off mid-battle or leaving a Rage never lifted that immunity for Regen, Slow, Haste, Stop, Shell, Safe, Reflect or Float, so those statuses stuck for the rest of the battle and swapping gear or Rages could pile them up.
Now
Changing gear or leaving a Rage now resets all immunities properly.
For ROM hackers 1.0 addresses
UpdateEquipBattle (C2/286D) rebuilds a character's status permissions after a gear change or when a Rage ends. The sta at C2/2917 stores $FFFF to the status 1/2 permissions ($331C,x), which the next store overwrites anyway, and the status 3/4 permissions ($3330,x) are never reset, so immunities written into them by gear or a Rage stay for the rest of the battle. Change that sta's operand from $331C to $3330 (same size).
Credit: previously found and fixed by Assassin, my implementation
#81 Zombie and Near Fatal can no longer be applied to, or trapped on, an immune party member.
In 1.0
Near Fatal, which comes on when HP falls below an eighth, and Zombie, which a party member hit by Overcast gets on dying, slipped past immunity, so an immune party member could catch either one and never lose it.
Now
Immunity now keeps both out.
For ROM hackers 1.0 addresses
InitStatusVars (C2/450D) filters the statuses to set through the permissions at $331C,y, then adds two after the filter: Near Fatal (the tsb $FC at C2/4548) and Zombie from the overcast flag (the ora at C2/455A). An immune target takes them, and the same closed permission then blocks their removal. Insert and $331C,y (3 bytes) before the tsb at C2/4548. Insert and $331C,y, beq to C2/4566 (5 bytes) before the ora at C2/455A. A is 16-bit in both places.
Credit: previously found and fixed by Assassin, my implementation
#82 Removing a timed status early now stops its hidden timer too.
In 1.0
Removing Stop, Reflect, Sleep or Freeze early, with Dispel for example, left its hidden timer running, so a cured Stop went on pausing Regen and the other timers until it would have ended, and a timer could later end a status that was already gone.
Now
A timer now stops the moment its status is removed.
For ROM hackers 1.0 addresses
Sleep, Stop, Reflect and Freeze have hidden counters ($3CF9, $3AF1, $3F0C, $3F0D) that the set handler loads and DecCounters (C2/5A83) counts down. Ending the status another way (a thaw, a blow, Rippler) leaves the counter running, and at zero it queues a status-expired action for a status that is gone. Insert jsr MyFix (3 bytes) before the jsr ResetStatusMod at C2/43FC in UpdateStatus (C2/4391), where the status word has just been settled. MyFix zeroes each counter whose status is not set; needs 45 bytes of free space in bank C2.
jsr MyFix ; inserted before C2/43FC, y = target
MyFix: php
sep #$20
lda $3ee5,y ; status 2
and #STATUS2::SLEEP
bne :+
sta $3cf9,y ; a is 0 after a failed and
: lda $3ef8,y ; status 3
and #STATUS3::STOP
bne :+
sta $3af1,y
: lda $3ef8,y
and #STATUS3::REFLECT
bne :+
sta $3f0c,y
: lda $3ef9,y ; status 4
and #STATUS4::FROZEN
bne :+
sta $3f0d,y
: plp
rts
Credit: previously found and fixed by Assassin, my implementation
#83 Statuses held by Stop no longer get an early tick once Stop wears off.
In 1.0
When Stop wore off, the Reflect, Freeze and Sleep timers it had been holding still also ticked down once, so each ran a little short and a Reflect one tick from its end vanished with Stop.
Now
Only Stop ends now, and the other timers resume where they were held.
For ROM hackers 1.0 addresses
_c25b06 (C2/5B06) counts down the Reflect, Freeze and Sleep counters. DecCounters enters it with A = 1 on the tick Stop wears off (C2/5AAD) and with A = 0 otherwise, and on the Stop tick it counts the other three down too, although Stop had held them still. Insert lsr, bcs to C2/5B32 (3 bytes) after the sta at C2/5B06, before C2/5B08. On the Stop tick it goes straight to the report, where $B8 bit 0 still says Stop wore off.
Credit: previously found and fixed by Assassin, my implementation
#84 Becoming a Zombie no longer strips a status that the party member's gear makes permanent.
In 1.0
Becoming a Zombie removed Dark, Poison, Near Fatal, Sleep, Muddled and Berserk, even a Muddled or Berserk made permanent by gear such as the Cursed Shld, which the gear's immunity then kept from coming back for the rest of the battle.
Now
A status the gear makes permanent now stays.
For ROM hackers 1.0 addresses
_c24585 (C2/4585) writes the finished status 1/2 word and, when Zombie is in it, first clears Blind, Poison, Near Fatal, Sleep, Muddle and Berserk, past the permissions. A status the target's gear grants is locked by a closed permission, so it is stripped and can never come back. Change the beq at C2/458A to bra (same size) to skip the strip. Ask for the clear through the list instead: insert jsr MyFix (3 bytes) at C2/43DE in UpdateStatus, between the two remove loops, where an addition to the status 1/2 clear list $F4 runs no handlers. Needs 18 bytes of free space in bank C2. #121 adds Imp's case to the same routine.
jsr MyFix ; inserted at C2/43DE, a is 16-bit
MyFix: lda $f8 ; status 1/2 carried
ora $fc ; or being set
bit #STATUS12::ZOMBIE
beq @done
lda $331c,y ; what this target may lose
andflg STATUS12, {BLIND, POISON, NEAR_FATAL, SLEEP, CONFUSE, BERSERK}
tsb $f4 ; status 1/2 to clear
@done: rts
Credit: previously found and fixed by Assassin, my implementation
#85 A special used through Control now correctly takes MP from its user.
In 1.0
An enemy under Control could use its specials for free, taking no MP from the enemy or from the party member controlling it.
Now
A special used through Control now costs the enemy the MP its attack lists, and an enemy without enough MP fails.
For ROM hackers 1.0 addresses
GetMPCost sets an action's cost ($3A4C while it runs) only for the commands that read a character's spell list, so a held monster's special performed through the Control command costs nothing. The Control command at C2/1976 has a branch for an actor who already holds a monster (the Control bit of status 4). In it, insert after the jsr at C2/197D, before C2/1980 (20 bytes): read the attack's MP cost from MagicProp+5 (C4/6AC5) for the attack in $B6 and store it to $3A4C. The attack's normal spend then takes it from the monster, and a monster short of MP fails.
rep #$10 ; inserted before C2/1980
lda $b6 ; the attack
xba
lda #$0e ; 14 bytes per attack
jsr MultAB ; C2/4781
tax
lda f:MagicProp+5,x ; its MP cost
sta $3a4c
sep #$10
Credit: previously found and fixed by Assassin, my implementation
#86 A silenced Rage still keeps the traits it should grant, not just the blocked spell.
In 1.0
When Gau was under Mute or Imp and his Rage tried a spell that costs MP, the statuses the Rage grants were blocked along with the spell: with Intangir's Rage he lost Vanish, Haste, Shell, Safe and Float, and could not get the last four back that battle. A Gau killed on his first Rage turn, as by Balloon's Exploder, came back without them.
Now
The Rage's statuses are now granted as soon as Gau enters it.
For ROM hackers 1.0 addresses
The Rage command (C2/1560) sets Rage, calls SetRage, which queues the monster's statuses in the to-set lists, and performs the attack through _c21554. Only the UpdateStatus at the end of the attack carries those lists out, so a Mute or Imp that cancels the attack loses them too. Insert phy, jsr UpdateStatus (C2/4391), ply (5 bytes) before the jsr _c21554 at C2/158B, so the rage's statuses are settled when it is entered. It also keeps them through a death on the first rage action, where in 1.0 one UpdateStatus set them and an Exploder's Wound cleared them together. No code of its own: part of #526's change.
Credit: previously found and fixed by Assassin, my implementation
#87 A Smoke Bomb chosen while Muddle is being cast on its user now takes the party out instead of sending the enemies away.
In 1.0
If a party member confirmed a Smoke Bomb while Muddle was being cast on them, it went off at the enemies instead, they left, and the battle ended as a win that paid nothing; a Warp Stone used that way was drawn on the enemy.
Now
Both now go off on the party, as they do without Muddle.
For ROM hackers 1.0 addresses
Depends on #409, which replaces the living-target mask in the Item command's plain-item branch (the longa at C2/18CE) with a routine. When Muddle lands on a user whose item is queued, FixPlayerCmd empties its aim, and the re-aim at the end of its wait, Retarget's Muddle flip at C2/597E, turns it onto the monsters. At the head of that routine, when $11A9 is $96 (Smoke Bomb) or $9A (Warp Stone), keep only the part of $B8 and $B9 on the user's side, or the whole side if that part is empty: for a character on the party side, $B8 = $0F eor $3A40 and $B9 = 0; for a monster or a character in $3A40, $B8 = $3A40 and $B9 = $3F.
Credit: previously found and fixed by Leet Sketcher, my implementation
#88 Runic no longer skips its weapon check when a party member acts on its own, in the Colosseum, or while Muddled or Charmed.
In 1.0
Runic needs a weapon that allows it, and the battle menu greys it out otherwise, but a party member acting on their own, when Muddled or Charmed or fighting in the Colosseum, could pick Runic with any weapon or none.
Now
Runic is now left out of their choices without a weapon that allows it.
For ROM hackers 1.0 addresses
RandCharAction (C2/0420) picks a command for a character acting on their own (Colosseum, Muddle, Berserk, Charm). It strikes out empty slots and commands ConfusedCmdTbl forbids, but not Runic without a Runic weapon; only the battle menu checks that. Insert before the clc at C2/0468 (16 bytes): if the command is Runic ($0B) and neither hand's weapon flags ($3BA4,y, $3BA5,y) have bit 7, branch to C2/0482, which strikes the slot out.
cmp #$0b ; Runic; inserted before C2/0468
bne @ok
lda $3ba4,y ; right hand weapon flags
ora $3ba5,y ; left hand
bit #$80 ; can absorb spells
beq @0482 ; C2/0482: strike the slot out
lda 1,s ; the command again
@ok:
Credit: previously found and fixed by Assassin, my implementation
#117 The game no longer loses track of whether an item was consumed, which could make items vanish.
In 1.0
An item chosen for Item or Throw that was never used, for example because the party member was killed first or turned into an Imp, was lost for good if that party member then took or stole another item.
Now
An item whose use never happens now goes back to the bag.
For ROM hackers 1.0 addresses
ExecAction, C2/00F9. $32F4,x is the item an actor's Item or Throw took from the bag (the sta at C2/4DB0 stores it when queued); $3A8C marks the actors the give-back pass _c262c7 (C2/62C7) must repay. Only the command itself marks it, so an action that never runs (the actor is knocked out, or the command becomes Fight) leaves the item in the byte until a later write loses it. Mark the debt when an actor's last pending action is taken up: insert the code below at C2/0183 (11 bytes). At the head of RemoveAllActions (C2/0A0F), which throws a character's actions away, insert lda $3018,x / tsb $3A8C (6 bytes).
@0183: lda $32cc,x ; next pending action, $ff if none
bpl :+
lda $3018,x ; actor's bit
tsb $3a8c ; the bag is owed its held item
: ; the original lda $3aa0,x follows
Credit: previously found and fixed by Lenophis, Imzogelmo and Kejardon, my implementation
#118 Mantra used while Muddled or Charmed no longer always heals a lone enemy for a flat 9999.
In 1.0
If a Muddled or Charmed caster's Mantra hit the enemies, a lone enemy was healed for 9999 and two enemies each got the caster's full HP.
Now
A turned-round Mantra now shares the caster's HP among the targets it really reaches.
For ROM hackers 1.0 addresses
AttackerEffect_15 (Mantra), C2/4263. After the trb $A4 that takes the caster out of the target mask, the divisor is the number of targets ($3EC9) minus 1: the ldx at C2/4272 and the dex after it. That assumes the caster was a target. A Muddled or Charmed caster aiming at one enemy gets a divisor of 0, and the heal comes out as 9999. Insert lda $A4 / jsr CountBits (C2/520E) (5 bytes) in place of the ldx and dex (4 bytes); CountBits leaves in X the number of targets left after the caster is removed.
Credit: previously found and fixed by Assassin, my implementation
#119 A Muddled or Charmed party member's Palidor no longer lifts the enemies out of reach, nor leaves the caster unseen.
In 1.0
If a party member became Muddled or Charmed before their queued Palidor went off, it lifted the enemies, which could not be hit until each came down, and the caster vanished from view while still acting and open to attack.
Now
A turned Palidor now lifts nobody, so the enemies can be hit and the caster stays in view.
For ROM hackers 1.0 addresses
AttackerEffect_13 (Palidor), C2/41F6. The effect lifts every slot in the target masks $A2 and $A4. A Confused or Charmed caster's aim holds the monsters instead, so they are lifted and land later with the Jump TargetEffect_13 queues, while the caster, not lifted, is hidden by Palidor's sprite script (hide_attacker_char) and no landing shows them again. Insert jsr MyFix (3 bytes) before the longa at C2/41FB. MyFix (15 bytes of free space in bank C2) takes every monster slot and every character fighting on the enemy side ($3A40) out of both masks. #213 (a controlled monster) is covered by the same routine.
jsr MyFix ; inserted before the longa at C2/41FB
MyFix: rep #$20
lda #$ff00 ; every monster slot
ora $3a40 ; and characters fighting as enemies
trb $a2
trb $a4
sep #$20
rts
Credit: previously found and fixed by Assassin, my implementation
#120 Imp status turning a queued Esper summon into a Fight no longer burns the once-per-battle summon flag.
In 1.0
If a party member was turned into an Imp after choosing an Esper summon, the summon became a plain Fight but the once-per-battle chance was spent, leaving the Esper greyed out for the rest of the battle.
Now
The party member can now still summon later in that battle.
For ROM hackers 1.0 addresses
InitPlayerAction, C2/0276. FixPlayerAttack spends the once-per-battle summon when the action is queued, setting the caster's bit in $3F2E. When Imp status then turns a queued Summon into Fight, nothing clears that bit. Insert after the plx at C2/02D9, the end of the imp rewrite (16 bytes): if the queued command (still on the stack at 1,s) differs from the command to run ($B5) and was Summon ($19), clear the caster's mask from $3F2E. It falls into the pla at C2/02DA.
lda 1,s ; command that was queued
cmp $b5 ; command that will run
beq @done
cmp #$19 ; Summon
bne @done
lda $3018,x ; caster's mask
trb $3f2e ; esper not spent
@done: ; pla at C2/02DA
Credit: previously found and fixed by Leet Sketcher, my implementation
#121 A Rage or Dance replaced by a Fight under Imp status no longer keeps acting on its own.
In 1.0
A Gau already in a Rage, or a party member already Dancing, who turned into an Imp kept acting by themselves and could not be given commands.
Now
Turning into an Imp now ends the Rage or Dance, so the party member takes commands again.
For ROM hackers 1.0 addresses
UpdateStatus, C2/4391. Imp takes nothing away, so a character under Rage or Dance keeps acting on its own while its commands become Fight. Insert jsr MyFix (3 bytes) at C2/43DE, before the status 3/4 clear list $F6 is masked and walked: when Imp is carried ($F8) or arriving ($FC), add any Dance or Rage the target carries or is getting ($FA, $FE) to $F6. The Rage clear handler then runs and rebuilds the immunities and command list. MyFix needs 19 bytes of free space in bank C2 and runs with a 16-bit accumulator.
jsr MyFix ; inserted at C2/43DE
MyFix: lda $f8
ora $fc ; status 1/2 carried or arriving
bit #$0020 ; Imp
beq @done
lda $fa
ora $fe ; status 3/4 carried or arriving
and #$0101 ; Dance, Rage
tsb $f6 ; clear them
@done: rts
Credit: previously found and fixed by Leet Sketcher, my implementation
#128 Atma Weapon's special attack can no longer chip damage off a Petrified party member.
In 1.0
A Fight with the Atma Weapon against a Petrified party member, who should take no damage, still did 1 point, and a stone party member brought to 0 HP this way was knocked out.
Now
A Petrified party member now takes nothing from the Atma Weapon.
For ROM hackers 1.0 addresses
CalcTargetDmg, C2/0B83. A Petrified target (and a nulled element) takes the branch to C2/0BFA, which zeroes the damage and then jumps with the bra at C2/0BFE to C2/0C1E, where the Atma Weapon's UltimaEffect runs and its closing inc turns 0 into 1. Change the bra at C2/0BFE to target C2/0C28 (jsr CalcMaxDmg), skipping the Atma Weapon step. Same size.
Credit: previously found and fixed by Imzogelmo and Leet Sketcher, my implementation
#129 Tapir curing a target who isn't asleep no longer shows a misleading Miss message.
In 1.0
Tapir, from Mog's Love Sonata Dance, cured a target who was not asleep, but the game showed Miss, as if nothing had happened.
Now
Tapir now cures such a target without showing Miss.
For ROM hackers 1.0 addresses
TargetEffect_17 (Tapir), C2/3CA2. The effect restores HP and MP only to a sleeping target; for an awake one the bpl at C2/3CA5 goes to C2/3C75, which makes the attack miss, although the dance's status cure has already landed. Point that bpl at the rts at C2/3CB7 instead. Same size.
Credit: previously found and fixed by Assassin, my implementation
#130 Gau, Kefka and Shadow fighting as enemies no longer misbehave when giving or receiving True Knight protection.
In 1.0
Gau coming back from the Veldt, Kefka in two scripted fights and Shadow in the Colosseum fight, all on the enemy side, were treated by True Knight as party members: a party member could leap in to guard one of them, and one of them could be picked to guard a party member.
Now
True Knight now counts them with the enemies, so neither happens.
For ROM hackers 1.0 addresses
CoverEffect, C2/123B. The possible bodyguards are built from the target's slot alone: $000F for a character, $3F00 for a monster. A character fighting on the enemy side ($3A40: Gau back from the Veldt, the two Kefkas, Colosseum Shadow) is treated as party. Insert the 20 bytes below in place of C2/1272 through the sta $f0 at C2/127C (12 bytes): the enemy side is $3A40 plus every monster slot, and a target outside it draws its guards from the party less those characters.
lda $3a40 ; characters fighting as enemies
ora #$3f00 ; and every monster slot
sta $f0
and $3018,y ; target's mask
bne @side ; target is on the enemy side
lda #$3f0f
eor $f0 ; the party, less those characters
sta $f0
@side:
Credit: previously found and fixed by Assassin, my implementation
#156 An Air Anchor now kills its target only when the target acts, not on a Poison, Seizure or Regen tick, a Life 3 revival or a status wearing off.
In 1.0
An Air Anchor makes its target die after its next action, but a Poison, Seizure or Regen tick, a Life 3 revival or a status such as Stop wearing off counted as that action, so the target could die without having acted.
Now
Only an action the target takes now sets it off.
For ROM hackers 1.0 addresses
AirAnchorEffect, C2/3838, kills the anchored actor on its next action when $3205 bit 2 is clear. It does not look at the action, so the engine's own actions (Poison, Seizure and Regen ticks, a Life 3 revival, a status wearing off: commands $1F and up in $3A7A) also set it off. Insert at the head (11 bytes): only a chosen command (below $1F) or the Seize drain ($2D) spends the anchor; anything else goes to the rts at C2/3849.
lda $3A7A ; command being run
cmp #$1F
bcc @spend ; one an actor can choose
cmp #$2D ; the Seize drain
bne @3849 ; the rts at C2/3849
@spend: ; on to the original test of $3205
Credit: previously found by Imzogelmo, Lenophis and Master ZED and fixed by Imzogelmo and Lenophis, my implementation
#157 A Condemned party member whose Doom timer expires mid-Jump can no longer end the battle alive at 0 HP.
In 1.0
If a Condemned party member's Doom landed during a Jump and the battle ended before they came down, they were left alive with 0 HP.
Now
They now leave the battle knocked out.
For ROM hackers 1.0 addresses
A kill on an actor in the air is deferred: AfterAction1 zeroes the hit points, CheckJump takes Wound off again and clears $3205 bit 2 so the actor dies on landing. A battle that ends first leaves it at 0 HP. In CheckBattleEnd (C2/47ED), insert jsr MyFix at C2/47F2 (48 bytes of free space in bank C2): every slot with the anchor armed, in the air ($3F2C), at 0 HP and not a Zombie gets Wound, then UpdateDead (C2/4AB9).
MyFix: ldx #$12
@loop: lda $3205,x
bit #$04
bne @next ; anchor not armed
rep #$20
lda $3018,x ; target mask
bit $3F2C ; in the air?
sep #$20
beq @next
lda $3BF4,x
ora $3BF5,x
bne @next ; hit points left
lda $3EE4,x
bit #$02 ; Zombie
bne @next
ora #$80 ; Wound
sta $3EE4,x
@next: dex
dex
bpl @loop
jmp UpdateDead
Credit: previously found and fixed by Leet Sketcher, my implementation
#159 Terra's Morph gauge pauses while she is Stopped, during another's Quick or out of battle; if it empties while she is Asleep, Frozen or Seized, she reverts once free.
In 1.0
Terra's Morph gauge kept draining while she was Stopped, during someone else's Quick, and after she had left the battle. If it ran out while she was Asleep, Frozen or Seized, she stayed in Morph for the rest of the battle unless the player chose Revert.
Now
The gauge pauses in the first three cases, and in the others she reverts as soon as she can act again.
For ROM hackers 1.0 addresses
UpdateBattleTime, C2/111B. The morph tick at C2/1143 (8 bytes: jsr DecMorphCounter when X is the morphed slot in $3EE2) runs before the loop's own tests, so it ignores absence and $3AA0 bit 4, which Stop and another's Quick set. Remove it, point the bpl at C2/1186 to C2/114B, and insert before the bit at C2/1156 (14 bytes): bit #$08, bne past, cpx $3EE2, bne past, pha, jsr DecMorphCounter, pla. Rewrite DecMorphCounter (C2/1211): on expiry store 0 in $3F30, clear $3B04,x, SaveMorphCounter, queue Revert; at 0, queue it again while an enemy lives (code). Sleep, Freeze and Seize set only bit 6, the gauge runs on, and ExecRetal drops the Revert then.
sep #$20 ; $3F30 is 0; a was 16-bit
lda $3EF9,x
and #$08 ; still morphed?
beq @done
lda $32CD,x
bpl @done ; something is queued
lda $3AA0,x
and #$50
bne @done ; held, or not free to act
lda $3A77
beq @done ; no enemy left: the battle ends
lda #$04 ; Revert
sta $3A7A
jmp CreateRetalAction
@done: rts
Credit: previously found and fixed by Leet Sketcher, my implementation
#160 Safety Bit and Memento Ring now clear Condemned status instead of leaving it stuck.
In 1.0
With a Safety Bit or Memento Ring, a Condemned party member survived the Doom but stayed Condemned for the rest of the battle. An enemy's Roulette landing on such a party member did the same and also threw the countdown away.
Now
Condemned now comes off when the countdown runs out, including after an enemy's Roulette.
For ROM hackers 1.0 addresses
DecCounters (C2/5A83): when the Condemned count ends, the jsr CondemnDeath at C2/5AC6 casts Doom (C2/5BC7) and leaves the status to it; a Safety Bit or Memento Ring blocks the Doom, so Condemned stays with no count. Insert before that jsr (8 bytes): lda $3EE5,x, and #$FE, sta $3EE5,x. Roulette route: an enemy Roulette's Doom asks StopCondemn (C2/09CE) to zero its target's count. Insert at its head (7 bytes): lda $3EE5,x, bit #$01, bne to the rts at C2/09CD, so a target still Condemned keeps its count.
Credit: previously found and fixed by Leet Sketcher, my implementation
#161 An Undead enemy revived by an instant-death weapon no longer gets killed again by a pending Life 3.
In 1.0
An undead enemy revived by an instant-death attack was killed again if it carried Life 3, since it cast Life on itself.
Now
An undead enemy no longer uses Life 3 when it dies, so the revival stands.
For ROM hackers 1.0 addresses
AfterAction1, C2/069B. For a target with Wound (test at C2/06F1), the code at C2/06F6 runs ReraiseEffect when Life 3 is set. On an undead monster an instant-death kill is a revival and a raise is a kill, so the Life 3 raise undoes the revival. Insert before C2/06F6 (5 bytes): lda $3C95,x, bmi to C2/0700, skipping the raise for an undead target ($3C95 bit 7). Life 3 stays on it, unspent.
Credit: previously found and fixed by Leet Sketcher, my implementation
#164 Betting an item you hold 99 of at the Colosseum and winning that same item back no longer leaves you with 98.
In 1.0
If you bet an item you held 99 of at the Colosseum and won that same item back, you ended with 98.
Now
You now end with 99.
For ROM hackers 1.0 addresses
UpdateSRAM (C2/4936) takes the bet out of the field inventory at C2/499E, after the prize was merged into the battle's item list and clamped at 99, so 99 plus the prize minus the bet is 98. Skip that block: change the beq at C2/49A1 to bra ($80). Take the bet out of the copy the battle starts from instead: in InitInventory's bag loop, replace the three instructions at C2/54A1 (9 bytes) with jsr MyFix and six nop (38 bytes of free space in bank C2).
.a16 ; entered with 16-bit A and X
MyFix: lda $1969,x ; quantity, as before
sta $2E75
shorta
lda $3A97 ; colosseum battle?
beq @item
lda $1869,x
cmp $0205 ; the item wagered
bne @item
dec $2E75 ; one fewer to the battle
bne @item
longa
lda #$00FF ; the last one: slot empty
rts
@item: longa
lda $1869,x ; item number, as before
rts
Credit: previously found by Master ZED and fixed by Lenophis, my implementation
#182 A controlled enemy's attack list now follows its own MP, so Control no longer offers attacks it can't afford.
In 1.0
If a controlled enemy's MP dropped after you took control, from Rasp or Osmose for example, Control's attack list still offered attacks it could no longer afford, and they failed when picked.
Now
The Control list now updates whenever the enemy's MP changes.
For ROM hackers 1.0 addresses
The Control list is built from the monster's MP by SetControlCmd (C2/372F), but an MP change only sets $3204 bit 7, and AfterAction2 (C2/083F) then runs UpdateEnabledMagic (C2/5763), which returns at once for a monster. Insert after that jsr at C2/086C (13 bytes, 8-bit X): if the target's controller ($32B9,x) is valid, set bit 3 ($08, rebuild commands) in the controller's $3204. In UpdateCmdList (C2/527D), insert before the plp at C2/52E6 (18 bytes): if the character controls a monster ($32B8,y valid), call SetControlCmd with X = the character and Y = the monster, high byte clear, saving X and Y.
Credit: previously found and fixed by Assassin, my implementation
#183 Turning Banon to stone or into a zombie during the escort battle is now a game over, matching his death.
In 1.0
During Banon's escort down the Lete River, if he was turned to stone, by Break for example, or into a zombie, the "Banon died" game over never came, though nobody could bring him back and the battle ran on with no way to lose.
Now
Stone or Zombie on Banon now ends the battle with the "Banon died" game over.
For ROM hackers 1.0 addresses
AfterAction1 sets the Banon-died end of battle event ($3A6E = 6) at C2/06D4, inside the branch for Wound only. Every actor who leaves the fight passes through _c207c8 (C2/07C8), for death, Petrify and Zombie alike. Insert at its head (23 bytes): if X is below 8, $3ED8,x is Banon ($0E) and $3EE4,x has Dead, Petrify or Zombie ($C2), store 6 in $3A6E. The status test keeps a run away an escape. The old test at C2/06D4 becomes redundant; this ROM hack removes it.
Credit: previously found and fixed by Novalia Spirit, my implementation
#186 Control now breaks when a controlled enemy is blown out by Sneeze, instead of staying linked to nobody.
In 1.0
An enemy under Control ordered to use Sneeze on itself left the battle, but the Control link stayed pointing at an enemy that was no longer there.
Now
Being blown out by Sneeze now ends Control.
For ROM hackers 1.0 addresses
ValidateControl (C2/07AD) ends a Control link when either side has one of the statuses in its two pea masks. Sneeze removes the target with status 4 Hide, which is missing from the second mask. Set bit 5 ($20, Hide) of the byte at C2/07B2, the high byte of that pea's operand (in place). Palidor gives every rider Hide at the cast, so on its own this also ends a controlling rider's link there, and UpdateSeize then throws her landing away and leaves her in the air; #587 says how to exempt a side in the air or about to be lifted.
Credit: previously found and fixed by Leet Sketcher, my implementation
#187 Gogo can no longer Mimic Colosseum Shadow's moves.
In 1.0
If you sent Gogo into the Colosseum against Shadow (the Striker bet) and Shadow had acted last, Mimic copied Shadow's move and Gogo used it on himself.
Now
Mimic no longer copies Shadow's moves in that fight.
For ROM hackers 1.0 addresses
ExecAction (C2/00F9) and ExecRetal (C2/4B7B) call SaveMimicAction (C2/021E), which refuses a monster attacker by slot, so a character on the enemy side ($3A40: Colosseum Shadow, the scripted Kefka, Gau returning on the Veldt) is recorded for Mimic. Point the jsr at C2/01A3 and the one at C2/4BE0 (in place) at the gate below (12 bytes of free space in bank C2), which returns for an actor whose character mask is in $3A40.
MimicGate:
lda $3018,x ; character mask, 0 for a monster
bit $3a40 ; characters fighting as enemies
bne :+
jmp SaveMimicAction
: rts
Credit: previously found and fixed by Leet Sketcher, my implementation
#190 Removing both Soul Savers with X-Zone while Wrexsoul hides no longer wins the fight without actually beating the boss.
In 1.0
In the Wrexsoul fight in Cyan's dream, removing both SoulSavers at once with X-Zone while Wrexsoul hid inside a party member counted as a win, and the fight ended with Wrexsoul never beaten.
Now
The fight can no longer be won while Wrexsoul hides inside a party member.
For ROM hackers 1.0 addresses
CheckBattleEnd (C2/47ED) declares a win when $3A77, the monsters still alive, is zero. Wrexsoul hiding inside a character is not counted there; the engine's one record of such a monster is $33F8, the Zinger attacker, negative when there is none. Insert after the bne at C2/4836 (5 bytes): lda $33F8 / bpl to the rts at C2/47EC.
Credit: previously found and fixed by Leet Sketcher, my implementation
#191 In a battle that forbids Joker Doom, a Joker Doom that comes up anyway no longer kills enemies immune to instant death.
In 1.0
In battles where the reels never land on Joker Doom, choosing Slot, Muddling Setzer so his reels spun at random, and curing him let Joker Doom go off anyway and kill bosses meant to be immune to instant death.
Now
Immune enemies now survive a Joker Doom that comes up anyway in such a battle.
For ROM hackers 1.0 addresses
ShowAttackName_07 (C2/36A6) turns the slot result into Joker Doom and doctors the spell's record: the trb $11A2 at C2/36AB clears the instant-death flag ($02) so no immunity applies, and $11A4 gets can't dodge. Insert after the tsb at C2/36A8 (9 bytes): lda $2F49 / and #$04 (the battle's disable-Joker-Doom bit) / bne to C2/36B3 / lda #$02. Where the battle forbids the combo both edits are skipped, and L.5 Doom's own instant-death flag lets an immune monster survive.
Credit: previously found and fixed by Lenophis, my implementation
#192 Capturing an enemy no longer erases the special effect of an equipped weapon such as the Atma Weapon or Fixed Dice.
In 1.0
Capture, which a Thief Glove gives in place of Steal, wiped the equipped weapon's special effect for that hit: the Atma Weapon did plain damage, the Drainer drained nothing, instant-kill weapons never killed, and the Dice and Fixed Dice barely connected.
Now
The weapon's effect and the steal now both happen on a Capture hit.
For ROM hackers 1.0 addresses
ExecAttack (C2/317B) makes a Capture steal by writing effect $52 ($A4) into $11A9, the slot that holds the weapon's own special effect. Skip that block: replace the lda zb5 at C2/31A8 with a bra to C2/31B3 (in place). In CalcAttackEffect (C2/3292) point the jsr DoTargetEffect at C2/345C (in place) at the routine below (39 bytes of free space in bank C2). It reads zb5 before the weapon's effect, since the thrown-weapon effect rewrites it.
CaptureEffect:
php
sep #$20
lda zb5
cmp #BATTLE_CMD::CAPTURE
beq @steal
jsr DoTargetEffect
plp
rts
@steal: jsr DoTargetEffect ; the weapon's own effect
lda $11a9
cmp #$a4
beq @done ; that was the steal already
pha
lda #$a4 ; effect $52, steal
sta $11a9
jsr DoTargetEffect
pla
sta $11a9
@done: plp
rts
Credit: previously found and fixed by Assassin, my implementation
#194 Stealing more than one item in a single turn now gives you all of them, not just the last one.
In 1.0
Stealing more than once in a single action, such as Capture with the Offering, which strikes four times, kept only the last item stolen, and the earlier ones were lost.
Now
You now receive every item stolen in one action.
For ROM hackers 1.0 addresses
Steal, TargetEffect_52 (C2/399E). The stolen item goes to $32F4,x, one byte per character, and the character's bit in $3A8C marks it owed; the give-back pass _c262c7 (C2/62C7) runs only at the end of the action, so a second steal in the same action overwrites the first. Replace the sta at C2/39EC with a jsr to the routine below (17 bytes of free space in bank C2): it stores the item, marks it and runs the pass at once, so the byte is free for the next strike. The tsb $3A8C that follows becomes redundant. Metamorph's store at C2/3A7C takes the same jsr.
jsr MyFix ; replaces sta $32f4,x at C2/39EC
MyFix: phy
phx
sta $32f4,x ; item obtained
lda $3018,x ; character mask
tsb $3a8c ; owed to the bag
jsr _c262c7 ; hand it over now
plx
ply
rts
Credit: previously found and fixed by SilentEnigma, my implementation
#195 A Runic user who is asleep, stopped, frozen, hidden, petrified or dead no longer loses the Runic stance to a spell they could not absorb.
In 1.0
A Runic user who was asleep, stopped, frozen, hidden, petrified or dead still lost the stance to the next spell cast, which went off as normal.
Now
The stance is now spent only on a spell it absorbs.
For ROM hackers 1.0 addresses
RunicEffect, C2/352B. The target poll clears each target's Runic stance ($3E4C bit 2) with the and/sta at C2/3545 before CheckStatus rules out a target that is dead, petrified, asleep, stopped, frozen or hidden, so such a target loses the stance and absorbs nothing. Remove that and/sta (5 bytes). Insert before the phx at C2/3568, where the absorb list in $EE is known to be non-empty, a loop that clears the stance of every target on the list (27 bytes).
phy
ldy #$12
@loop: longa
lda $3018,y ; target mask
and $ee ; on the absorb list?
shorta
beq @next
lda $3e4c,y
and #$fb ; clear the character Runic bit
sta $3e4c,y
@next: dey2
bpl @loop
ply
Credit: previously found and fixed by Lenophis and Imzogelmo, my implementation
#196 The Tapir dance now restores HP when curing Zombie, instead of only when the target was also asleep.
In 1.0
Tapir, from Mog's Love Sonata dance, cured Zombie along with other ailments but restored HP only to a target that was asleep, leaving a cured zombie at 0 HP.
Now
A zombie cured by Tapir, or by any other way of removing Zombie, now comes back with an eighth of its maximum HP.
For ROM hackers 1.0 addresses
Same routine as #210. The Tapir effect, TargetEffect_17 (C2/3CA2), restores HP only for a sleeping target, but its Zombie cure clears the dead flag through the Zombie clear handler, so a cured zombie stood at 0 HP. The #210 change to that handler gives the target an eighth of its maximum HP whenever it is cured at 0 HP, which covers the Tapir; TargetEffect_17 stays as it is.
Credit: previously found and fixed by Leet Sketcher, my implementation
#203 Roulette used through Sketch or Rage now shows its spinning pointer and can target anyone, not only enemies.
In 1.0
When a Roulette came through Sketch or Rage, no spinning cursor appeared and only enemies could be hit, instead of anyone in the battle, party members included.
Now
The cursor now spins and the victim can be anyone on the field.
For ROM hackers 1.0 addresses
Two places. ExecAttack at C2/31F2 gives Roulette its free-for-all targets when the queued command ($3A7C) is Roulette, which a Roulette reached through Sketch or Rage is not: replace that lda/cmp (5 bytes) with lda $B6 / cmp #$8C, attack 140, plus one nop. _c21554 (C2/1554), where Sketch and Rage pick the command for their attack, skips the lore-to-roulette correction every queued action gets: insert jsr FixRoulette (C2/03B9, 3 bytes) after the jsr GetCmdForAI at C2/1556.
Credit: previously found and fixed by Leet Sketcher, my implementation
#209 Curing Petrify now restores the Near Fatal warning and kneeling pose if the party member's HP was already critical.
In 1.0
If Petrify was cured while the party member's HP was still critical, the Near Fatal warning and kneeling pose stayed gone until someone in the battle next acted.
Now
The warning and the kneeling pose now come back as soon as the stone comes off.
For ROM hackers 1.0 addresses
InitStatusVars, C2/450D. Petrify is re-asserted on every pass (the and #PETRIFY at C2/452C, tsb $FC at C2/452F), even on the pass that cures it, so the petrify set handler runs one last time and strips the Near Fatal status that the HP check has just set. Insert bit $F4 / bne over the tsb (4 bytes) between the two: $F4 holds status 1/2 to clear on this pass.
Credit: previously found and fixed by Leet Sketcher, my implementation
#210 Curing Zombie now always brings the party member back with some HP, including through Rippler or while petrified.
In 1.0
Some ways of curing Zombie, such as Rippler trading it away or curing it while the party member was also petrified, did not heal at all, so the party member stood up at 0 HP.
Now
Curing Zombie now always gives a party member at 0 HP an eighth of maximum HP back.
For ROM hackers 1.0 addresses
The Zombie remove-status handler at C2/45AE (jsr _c2469c, then bra at C2/45B1). A zombie stands at 0 HP, and routes such as Rippler or a cure while petrified clear Zombie without healing, so the target is left up with no HP. Insert after the jsr, before the bra (14 bytes, A is 16-bit): if the current HP is 0, set it to max HP / 8. Every cure passes through this handler.
lda $3bf4,y ; current hp
bne :+
lda $3c1c,y ; max hp
lsr3 ; / 8
sta $3bf4,y
:
Credit: previously found and fixed by Leet Sketcher, my implementation
#213 A Muddled or Charmed party member's Palidor no longer lifts an enemy under Control, which then stayed in the air, out of reach of every attack, for the rest of the fight.
In 1.0
When a Muddled or Charmed party member's Palidor lifted an enemy under Relm's Control, the enemy stayed in the air for the rest of the fight, out of reach of every attack but still attacking whenever Relm commanded it.
Now
Palidor now lifts only party members, so a controlled enemy stays on the ground.
For ROM hackers 1.0 addresses
Palidor's attacker effect, AttackerEffect_13 (C2/41F6). It lifts every target in $A2/$A4. Only a Muddled, Charmed or zombie caster's aim puts monsters there, and a controlled monster lifted that way never lands: while it is controlled its time stands still ($3AA0 bit 6), so its landing wait never runs. Keep only the party's own slots: insert after the rep at C2/41FB (10 bytes, A 16-bit) lda #$FF00 / ora $3A40 / trb $A2 / trb $A4. $3A40 is the characters fighting on the enemy side.
Credit: previously found and fixed by Assassin, my implementation
#214 Equipment that grants a status now applies it when worn mid-battle and removes it when taken off, as it should.
In 1.0
Putting on a weapon or shield that carries a status during a fight did not grant it, and taking the gear off did not remove it.
Now
Swapping equipment in battle now gives the new gear's status and removes that of gear taken off.
For ROM hackers 1.0 addresses
The battle's equipment change, the loop at C2/2095, reruns the equipment stats but never queues the status a weapon or shield grants ($3C6C). Insert before the lda at C2/20C8: rep #$20 / lda $3C6C,x / pha / sep #$20 (8 bytes); before the jsr UpdateImmuneStatus at C2/20D5: rep #$20 / pla / eor $3C6C,x / sta $F4 / sep #$20 / beq over / jsr MyFix (15 bytes). MyFix (66 bytes of free space, bank C2) queues gains to set, losses to clear, lifts the lost bits' immunity and runs UpdateStatus (C2/4391); status 3 bit 0, flying, goes to Float (status 4 bit 7). A Rage's end raises the flag too (C2/45ED): make that inc an ora #$80, and skip MyFix for $80 alone. Keep #523's grant mask (C2/28BF).
MyFix: rep #$20
lda $f4 ; bits that changed
and $3c6c,x
sta $f6 ; gained
eor $f4
sta $f8 ; lost
sep #$20
lda $f6
ora $3dd5,x ; status 2 to set
sta $3dd5,x
lda $f7
ora $3de8,x ; status 3 to set
sta $3de8,x
lda $f8
ora $3dfd,x ; status 2 to clear
sta $3dfd,x
lda $f8
ora $331d,x ; allow it to change
sta $331d,x
lda $f9 ; same for status 3:
... ; $3e10,x and $3330,x
jmp UpdateStatus
Credit: previously found by Master ZED and Keltainentoukokuu via the Final Fantasy Wiki, my implementation
#216 Five Jump fixes: each Dragon Horn strike uses fresh data, the swinging weapon is drawn, and spear bonus and Ogre Nix breaks follow that hand.
In 1.0
With the Dragon Horn, each Jump strike after the first reused what the strike before had left. An Ogre Nix breaking mid-jump left the jumper landing bare-handed, and with two weapons the landing drew the right-hand one whichever hand swung, a spear in either hand doubled damage, and a right-hand Ogre Nix never broke.
Now
Each strike now uses fresh data for the hand that swings, the spear bonus and Ogre Nix breaks follow that hand, and the landing shows the weapon that swung, even one that just broke.
For ROM hackers Method
Hooks: the Jump command (C2/17F6), ExecAttack's per-strike rebuild (C2/318E), the Ogre Nix break in AttackerEffect_0e (C2/3EF9) and the landing pose (C1/BA32). In 1.0 the command builds the strike once, so Dragon Horn strikes reuse spent data; it applies SpearEffect for both hands; the break takes the hand from the parity of $3A70, always left on a Jump; the landing draws whichever hand can jump. The rewrite: the command only rolls the hand (RandCarry when both are armed), keeps it in bit 7 of $B6 with ror, asks SpearEffect of that hand, rolls the horn and stores the command in $3413 so every strike is rebuilt. For a Jump the rebuild reloads the Battle attack, shifts the hand out of $B6 for _c2299f (C2/299F), sets can't dodge and keeps the targeting. The break tests bit $B6 / bpl, in place. The landing reads the weapon from the strike's record, (z76) byte 3.
Credit: previously found and fixed by Assassin, my implementation
#239 Rippler no longer shows Miss when it actually works against a target that blocks every status.
In 1.0
If a Rippler target already had, or was immune to, every status on Rippler's list, the game showed Miss, yet the trade still went through.
Now
Rippler no longer shows Miss in that case.
For ROM hackers 1.0 addresses
Spell data MagicProp (C4/6AC0, 14 bytes a record), spell 158 ($9E, Rippler). Its misc flags byte, the record's eighth byte at C4/736B, has bit 0 set, the status-only bit that makes an attack miss when the target blocks every status in it, but Rippler swaps statuses rather than inflicting them. Clear bit 0: $03 becomes $02, show message alone.
Credit: as in the GBA release, previously found by Master ZED, my implementation
#246 When two enemies Charm the same party member, the first one's Charm no longer stops working once that party member dies.
In 1.0
If two enemies Charmed the same party member, only the second was recorded, so when that party member died the first enemy stayed tied to them and its Charm did nothing for the rest of the battle.
Now
When a charmed target dies, every Charm and Love Token bond aimed at it is now cleared, freeing each caster.
For ROM hackers 1.0 addresses
_c207c8 (C2/07C8), run when an actor leaves the fight. At C2/07F5 it breaks only the pairs the dying actor recorded in its own $336C/$336D (Love Token target, attacker) and $3394/$3395 (Charm target, attacker). A target holds one Charm attacker, so with two charmers the first keeps pointing at the dead target. Replace the four tests from C2/07F5 up to C2/081F with a jsl to a routine that walks all ten actors (Y = $12 down to 0, step 2), writes $FF to any of those bytes equal to the dying actor's index (X), then to the actor's own four; 74 bytes of free space, 8-bit index registers. RemoveLoveToken (C2/082D) and RemoveCharm (C2/0836) fall unused.
Credit: previously found by Master ZED, my implementation
#250 Rolling Joker Doom on Setzer's slot no longer teaches Strago the L.5 Doom Lore.
In 1.0
When Setzer's Slot came up 7-7-7 for Joker Doom, it was cast as the L.5 Doom spell, which is also a Lore, so a Strago in the battle learned L.5 Doom from it.
Now
Strago no longer learns a Lore from anything Setzer's Slot casts.
For ROM hackers 1.0 addresses
LearnLore, C2/36D6. Setzer's Joker Doom (7-7-7) is cast as attack L.5 Doom, which is a Lore, so Strago learns it from his own party's slot. The command is still in $B5 here. Insert before the ldy at C2/36E1: lda $B5, cmp #$0F (Slot), beq to the exit at C2/3708 (6 bytes).
Credit: previously found by deathslinger and Master ZED, my implementation
#251 An enemy finished off by an ailment while airborne now pays experience and gil like any other kill.
In 1.0
An enemy in the air from a Jump, its own or after a muddled Palidor summon, could be killed by Poison or Condemned before it landed. If the battle ended first, it paid no experience and no gil.
Now
It now counts as killed and pays its experience and gil.
For ROM hackers 1.0 addresses
CheckBattleEnd, C2/47ED. A kill on an actor in the air is deferred: the HP reach 0, then CheckJump takes Wound off again and clears bit 2 of $3205, the anchor that kills on landing (the flags there are active when clear). If the battle ends first, the monster carries no Wound and WinBattle pays nothing for it. Insert jsr MyFix (3 bytes) before the lda $3A6E at C2/47F2. MyFix (48 bytes of free space in bank C2) gives Wound to every actor in the air ($3F2C) with the anchor armed, 0 HP and no Zombie, then jumps to UpdateDead (C2/4AB9). #157 uses the same routine for a character.
jsr MyFix ; inserted before lda $3a6e at C2/47F2
MyFix: ldx #$12
@loop: lda $3205,x
bit #$04
bne @next ; landing anchor not armed
rep #$20
lda $3018,x ; actor's mask
bit $3f2c ; in the air?
sep #$20
beq @next
lda $3bf4,x
ora $3bf5,x
bne @next ; hit points left
lda $3ee4,x
bit #$02 ; zombie keeps its status
bne @next
ora #$80 ; wound
sta $3ee4,x
@next: dex
dex
bpl @loop
jmp UpdateDead
Credit: previously found by Master ZED, my implementation
#252 A weapon that breaks between choosing Runic and acting no longer leaves the party member stuck in the stance with nothing to absorb with.
In 1.0
With Palidor, Celes or Gogo could choose Runic, jump and break the Ogre Nix on the landing strike, and Runic still went off with no blade to absorb spells.
Now
A party member whose weapon has just broken no longer takes the stance.
For ROM hackers 1.0 addresses
Runic's action, C2/195B (the Runic entry of the battle command table). The Runic blade test runs only when the command is chosen. A RAND_BREAK weapon such as the Ogre Nix can shatter before the action, most easily during a Palidor jump, and the action still sets the stance bit, $3E4C bit 2. Replace the lda/ora/sta at C2/195C (8 bytes) with jsl MyFix and four nop. MyFix (17 bytes of free space) sets the bit only when either hand's weapon flags ($3BA4, $3BA5) still have bit 7, Runic.
jsl MyFix ; replaces lda/ora/sta at C2/195C
nop
nop
nop
nop
MyFix: lda $3ba4,x ; right hand weapon flags
ora $3ba5,x ; left hand
bpl @done ; bit 7: runic
lda $3e4c,x
ora #$04 ; runic stance
sta $3e4c,x
@done: rtl
Credit: previously found by Master ZED, my implementation
#253 A party member who starts a battle KO'd or petrified gets their equipment's status back once revived or cured.
In 1.0
A party member who entered a battle KO'd or turned to stone lost the status their gear grants, and reviving or curing them during the battle did not bring it back.
Now
Reviving or curing them now restores their equipment's status.
For ROM hackers 1.0 addresses
The Petrify and Wound clear handler, C2/45F2. A character who starts the battle wounded or petrified has every other status stripped and the to-set queue emptied, the equipment's statuses among them, and a revival never adds them back. Insert jsl MyFix (4 bytes) before the lda #$0040 at C2/45FB. MyFix (19 bytes of free space, 16-bit A, Y = the target) ORs the equipment statuses at $3C6C,y into the to-set lists: the low byte (status 2) into the high byte of $FC, the high byte (status 3) into the low byte of $FE. It needs #523's grant mask at C2/28BF, or a revived Ribbon wearer gets back what the Ribbon blocks.
jsl MyFix ; inserted before lda #$0040 at C2/45FB
MyFix: lda $3c6c,y ; equipment status 2 (low), 3 (high)
and #$00ff
xba
tsb $fc ; status 1/2 to set
lda $3c6c,y
xba
and #$00ff
tsb $fe ; status 3/4 to set
rtl
Credit: previously found by Master ZED, my implementation
#261 Runic now refunds only the MP the caster actually paid for a spell, so an Economizer can no longer turn it into free MP.
In 1.0
Runic gave its user the spell's full listed MP cost, even when an Economizer or a Gold Hairpin had cut the caster's cost.
Now
Runic now gives back exactly the MP the caster spent.
For ROM hackers 1.0 addresses
RunicEffect, C2/352B. The MP a Runic user absorbs is loaded at C2/358C from $11A5, the spell's list price, and then divided among the Runic users. What the caster actually paid, after a Gold Hairpin or Economizer, is in $3A4C. Change that lda $11A5 to lda $3A4C (same size, 3 bytes).
Credit: previously found by Master ZED, my implementation
#265 Immunity to Seizure no longer blocks Regen, and immunity to Slow no longer blocks Haste, for Umaro, Air Force and Ribbon wearers.
In 1.0
Immunity to Seizure also blocked Regen and immunity to Slow also blocked Haste, so Umaro, when fought, never got its Regen, Air Force never gained Haste, and Ribbon, Thornlet and Cursed Shld wearers could never get Regen.
Now
Only the harmful half of each pair is now blocked.
For ROM hackers 1.0 addresses
_initstatus2, C2/2675, which builds each actor's status immunity. When the actor is blocked from either Seizure or Regen, the and at C2/26B2 blocks both; when blocked from either Slow or Haste, the and at C2/26C3 blocks both. Keep only the harmful half, in place: change the operand's high byte at C2/26B4 from $FD to $FF (Seizure only), and the operand at C2/26C4 from $F3 to $FB (Slow only).
Credit: previously found by Fenrir9 via the Final Fantasy Wiki, my implementation
#266 L? Holy (L? Pearl in 1.0) no longer misses everyone when the party's gil ends in zero; a zero now counts as ten.
In 1.0
L? Pearl hit every target whose level divides evenly by the last digit of the party's gil, but when that digit was zero it hit nobody and its MP was wasted.
Now
A zero now counts as ten.
For ROM hackers 1.0 addresses
AttackerEffect_1d, C2/40A1 (L? Pearl). It stores the last digit of the party's gil in $11A8 as the number a target's level must divide by. A last digit of 0 gives a divisor of 0, which no level passes. Insert before the stx $11A8 at C2/40B7 (5 bytes): txa, bne past the next instruction, ldx #10, so 0 counts as ten. The rts after the stx is also a branch target of the effects either side, so keep one there.
txa ; the last digit
bne @store
ldx #10 ; zero counts as ten
@store: ; the stx $11a8 at C2/40B7 follows
#304 Runic's MP refund from an absorbed spell no longer changes with the user's elemental gear, so a Cursed Shld can't double or reverse it.
In 1.0
Runic's MP refund from an absorbed spell was changed by the Runic user's elemental gear: with the Cursed Shld, a Fire that cost 4 MP gave Celes back 8, and gear that absorbs the element could take MP away.
Now
The refund now ignores elements.
For ROM hackers 1.0 addresses
RunicEffect, C2/352B. The MP refund keeps the absorbed spell's element in $11A1, so CalcTargetDmg applies the Runic user's elemental weakness, resistance or absorption to it. Clear the element when the flags are set: replace shorta, lda #$60, sta $11A2 at C2/3584 (7 bytes, A 16-bit before it) with lda #$6000, sta $11A1, shorta: insert 1 byte. Same routine as #261, which bases the refund on the MP the caster paid.
lda #$6000 ; A 16-bit: $11a2 = $60, element $11a1 = 0
sta $11a1
shorta
Credit: previously found by Master ZED and fixed by Lenophis and Imzogelmo, my implementation
#316 Scripted enemy fights at the Imperial Camp and Sealed Gate can no longer be escaped using Warp or Smoke Bomb.
In 1.0
In the fights against Kefka at the Imperial Camp and the Sealed Gate, running away was blocked, but Warp or a Smoke Bomb still took the party out.
Now
Warp and Smoke Bomb are now blocked in both fights too.
For ROM hackers 1.0 addresses
UpdateMonsterGfxBuf, C2/5C73. When enemy characters are alive ($3A42 nonzero), the lda #$02 at C2/5D21 and the tsb after it set only bit 1 of the battle flags at $B1, can't run. Bit 2 ($04), which blocks Warp and Smoke Bomb, stays clear. Change the operand at C2/5D22 from $02 to $06.
Credit: previously found by Novalia Spirit, my implementation
#321 While Quick holds everyone else, the Quick user's own Reflect, Poison, Regen and Condemned timers no longer keep running.
In 1.0
While Quick held everyone else, the caster's own Reflect, Poison, Regen and Condemned timers kept running through the free turns.
Now
They now stand still through Quick's free turns, as the others' do. A Stop still wears off during Quick, as in the original.
For ROM hackers 1.0 addresses
DecCounters, C2/5A83. Quick holds everyone but its caster by setting $3AA0 bit 4, which the test at C2/5AB1 skips, so the caster's own Condemned, Reflect and other status counters keep running through the free turns. $3404 holds the Quick user, or $FF when no Quick runs. Insert at C2/5AB1, where the Stop branch joins (5 bytes, A 8-bit): lda $3404, bpl to the rts at C2/5AE9. No counter moves while a Quick lasts.
@5ab1: lda $3404 ; quick user, $ff when none
bpl _5ae9 ; quick running: no counter moves
; the original lda $3aa0,x follows
Credit: previously fixed by RoSoDude, my implementation
#322 A defending party member no longer loses their Defend stance just because someone else casts Quick.
In 1.0
When a party member cast Quick, every other defending party member dropped Defend.
Now
A defending party member now keeps Defend.
For ROM hackers 1.0 addresses
AfterAction2, C2/083F. After CheckPlayerAction and _c208c6 it calls ClearDef for any actor with $3AA0 bit 4 or 6 set, and Quick sets bit 4 on everyone it holds, so their Defend ends. Insert lda $3404, bpl to C2/08BB (5 bytes, A 8-bit) before the lda at C2/08B1, so the test is skipped while a Quick runs ($3404 is $FF when none does).
Credit: previously fixed by RoSoDude, my implementation
#324 Quick with X-Magic no longer wastes a free turn on the second spell, and Quick no longer ends with the gauge still full, so the caster can't keep acting turn after turn.
In 1.0
Quick cast first in an X-Magic lost a free turn to the second spell, and X-Magic on the last free turn left the gauge full, so the caster kept acting turn after turn. The gauge could also stay full when the caster's own Regen tick or Revert came just before the second free turn.
Now
Quick now gives both free turns, and X-Magic on the last free turn ends Quick with the gauge reset as usual.
For ROM hackers 1.0 addresses
Two places. CheckPlayerAction, C2/091F, gives the Quick user $3AA0 bits 3 and 7 (full gauge, menu may open) after every action, also between the two spells of an X-Magic, so X-Magic as the last free turn ends Quick with the menu already open. Extend the 9 bytes at C2/0938 to the first block: insert 5 bytes. AttackerEffect_43, C2/42CA: with Quick as an X-Magic's first spell, the second spell counts off a free turn. Replace lda #2, sta $3402 at C2/42D2 (5 bytes) with jsl QuickStart and a nop; QuickStart needs 15 bytes of free space.
; CheckPlayerAction, C2/0938
cpx $3404
bne @0941
lda $32cc,x ; x-magic's second spell still queued
bpl @0941
lda #$88
tsb $ee
; AttackerEffect_43, C2/42D2
jsl QuickStart
nop
QuickStart: ; y = the caster
lda $32cc,y
bmi @two ; nothing more to run this turn
lda #3
bra @set
@two: lda #2
@set: sta $3402
rtl
Credit: previously found by Master ZED, my implementation
#325 An enemy or party member whose turn came up during Quick's cast animation no longer loses that turn or freezes up entirely.
In 1.0
An enemy, or a party member run by the computer, whose gauge filled while Quick was being cast lost that turn, and a very fast party member who chose a command during Quick's animation could stop acting for the rest of the fight.
Now
The waiting enemy or party member now takes its turn once Quick's free turns are over, and the fast party member keeps acting.
For ROM hackers 1.0 addresses
_c208c6, C2/08C6. An actor whose gauge fills while Quick is being cast meets Quick's hold in _c22188 (C2/2188), which sets $3AA1 bit 7 and drops the turn. _c208c6 gives it back only when $32CC,x holds an action, so a monster or AI-run character loses the turn. Replace lda $32CC,x and inc at C2/090D (4 bytes) with a jsl to the routine below (30 bytes of free space): a full gauge ($3AA0 bit 3) also counts, and $3AA0 bit 5 is cleared on the way to the advance wait queue, or a very fast character is skipped by the gauge code for good.
jsl MyFix ; at C2/090D, the beq after it reads z
MyFix: lda $32cc,x
inc
bne @back ; an action is waiting
lda $3aa0,x
and #$08
beq @done ; gauge not full: nothing dropped
@back: lda $3aa1,x
lsr
bcs @set ; straight to the action queue
lda $3aa0,x
and #$df ; clear bit 5
sta $3aa0,x
@set: lda #1 ; z clear
@done: rtl
Credit: previously found by Master ZED, my implementation
#326 A command chosen during Quick now respects a Muddle cast in the free turns, instead of a held Jump or Fight landing on the old target.
In 1.0
A command chosen during Quick ignored a Muddle cast on that party member in the free turns, so a Muddled jumper still came down on the enemy picked before.
Now
A Muddle cast in the free turns now counts, so a held Jump picks a new target as any Muddled action does.
For ROM hackers 1.0 addresses
An action chosen while Quick runs waits for QueueAction until Quick ends, and QueueAction clears the actor's bit in $3A4A (Muddle or Zombie changed) at C2/0344, so a Muddle from the free turns is forgotten and the old targets stay. Clear it when the command is read instead. In GetPlayerActions (C2/4D1F), replace the lda and xba at C2/4D41 (4 bytes) with jsl MyRead (11 bytes of free space). Replace the 6 bytes at C2/0344 with jsl MyQueue and two nops (MyQueue, 17 bytes).
MyRead: lda $3018,x ; actor's mask
trb $3a4a
lda wPlayerActionBuf::Attack,y
xba
rtl
MyQueue:
lda $3255,x ; ai script pointer, high byte
bpl @clear ; an ai script chooses now
lda $32cc,x
bpl @keep ; a menu action is waiting
@clear: lda $3018,x
trb $3a4a
@keep: rtl
Credit: previously found by Master ZED, my implementation
#397 Life and Fenix Down no longer destroy undead enemies immune to instant death, such as Hidon; they now miss like Doom.
In 1.0
Life, Life 2 and a Fenix Down turn deadly against undead enemies, and that also killed undead enemies immune to instant death, such as Hidon.
Now
Against those enemies the revival now misses, the way Doom does.
For ROM hackers 1.0 addresses
CheckHit (C2/220D). At C2/224B, instant death protection ($3AA1,y bit 2) is tested only for an attack with $11A2 bit 1. A Life or Fenix Down that MagicStatusEffect turns into Wound on an undead monster carries no such flag. Lift the four tests at C2/4420 (undead $3C95,y bit 7, $11A2 bit 3, $11A4 bit 2, $11AA Wound or Zombie) into a routine returning carry set (29 bytes of free space in bank C2), called there with jsr and bcc to C2/443D. Replace the beq at C2/2250 with a bne to the protection test and insert jsr UndeadLife, bcc to C2/2259 (5 bytes). #27 calls it on the vanished-target path too.
bne @prot ; at C2/2250, was beq @2259
jsr UndeadLife
bcc @2259
@prot: ; lda $3aa1,y as before
jsr UndeadLife ; at C2/4420
bcc @443d
; then the lda #$80, tsb $fc as before
UndeadLife: ; the 25 bytes from C2/4420,
; each beq/bpl now to @no
sec
rts
@no: clc
rts
Credit: previously fixed by Assassin, my implementation
#399 A party member cured of Confuse before their action fires now has that action go where it was actually aimed.
In 1.0
If a party member was Muddled and then cured before their chosen action went off, the game still threw away the target they had picked, so an item was used up on nobody or a spell went to a random target.
Now
The action now goes to the target the player chose.
For ROM hackers 1.0 addresses
InitPlayerAction (C2/0276). When an actor's Muddle or Zombie changed after the action was chosen ($3A4A), the targets are cleared at C2/0296 so a new one is rolled. A Muddle cured again counts as a change, and then nothing re-aims, so an Item hits nobody and a spell goes to a random target. Insert before the stz at C2/0296 (14 bytes): clear only if Zombie ($3EE4,x bit 1) or Muddle ($3EE5,x bit 5) is still set; otherwise go to C2/029A.
lda $3EE4,x ; before stz $b8 at C2/0296
and #$02 ; zombie
bne :+
lda $3EE5,x
and #$20 ; muddled
beq @029a ; cured: keep the aim
: ; stz $b8, stz $b9 as before
Credit: previously found by Fenrir9 via the Final Fantasy Wiki, my implementation
#400 A multi-target Dance used by an enemy under Relm's Control now hits the enemies instead of the party.
In 1.0
When an enemy's multi-target Dance, such as Wind Slash, Sand Storm, Surge or El Nino, was used under Relm's Control, it hit the party even though the cursor pointed at the enemies.
Now
Those Dances now hit the enemy side, where the cursor points.
For ROM hackers 1.0 addresses
Magic, summon and Control share the handler at C2/1765. A controlled monster's attack runs as the monster's own Magic, and InitCmdTarget takes the spell's random-target flag ($40 in $BA), which rolls a target on the monster's side, the party. Insert before the lda $B5 at C2/1771 (11 bytes): if the queued command $3A7C is Control ($0E), trb $40 from $BA.
lda $3A7C ; before lda $b5 at C2/1771
cmp #$0E ; control
bne :+
lda #$40 ; random target
trb $BA
:
Credit: previously found by Master ZED, my implementation
#401 An enemy's Roulette no longer restarts another enemy's wheel already spinning; each now waits its turn.
In 1.0
If an enemy started Roulette while another enemy's wheel was still spinning, the new wheel restarted the old one, so only the last wheel landed.
Now
Each wheel now waits until the one before it has landed and its Doom has struck.
For ROM hackers 1.0 addresses
InitMonsterRoulette (C1/B3B0) starts a wheel even while $62B5 says one is turning, and restarts it, so only the last lands. Insert at its head (16 bytes): wait a frame at a time while $62B5 is set, at most $300 frames, since a wheel with nobody to land on never stops. In UpdateMonsterRoulette (C1/B3CE), before the ldx at C1/B417 that sets the target, insert (10 bytes): while $2F43 bit 7 is clear (the last wheel's target has not had its Doom), inc $62B7 and branch to C1/B439 as the bne above does, so the landed wheel holds a tick at a time.
phx ; head of InitMonsterRoulette
ldx #$0300
@wait: lda $62B5
beq @go
jsr WaitFrame
dex
bne @wait
@go: plx
lda $2F43 ; before ldx $62bb at C1/B417
bmi :+
inc $62B7
bra @b439 ; as the bne above
:
Credit: previously found by Master ZED, my implementation
#402 A party wiped out while leaving by a sneezed or engulfed Zombie, Warp, a Warp Stone or Gau's Leap now gets a Game Over.
In 1.0
A wiped-out party could still leave at 0 HP when a Zombie was blown away by Sneeze or swallowed by Engulf, or when Warp, a Warp Stone or Gau's Leap went ahead before anyone was counted.
Now
Each of these now ends in a Game Over.
For ROM hackers 1.0 addresses
CheckBattleEnd (C2/47ED). The pending exit in $3A6E (2 Warp or Warp Stone, 4 Leap, 8 and $0A AI end battle) is taken at C2/47F2 before the living are counted, and at C2/4822 any character in $3A39 (Sneeze, Engulf), dead or zombie, turns a wipe into an escape. Make the lda $3A39 at C2/4822 a jsl (1 byte more) to a routine returning those masks whose status 1 at $3EE4,x has none of $C2, minus $3A40. Make the lda $3A6E at C2/47F2 a jsl too, returning 0 for those four exits when $3A74 (characters alive) and that mask are both 0. About 88 bytes of free space in all.
Credit: previously found by Master ZED and Novalia Spirit, my implementation
#404 A Smoke Bomb used during a Quick free turn while a partner was mid-Jump no longer freezes the battle.
In 1.0
If a Quick caster escaped with a Smoke Bomb on a free turn while a partner was in mid-Jump, the battle soft locked.
Now
The jumper now lands and the battle carries on.
For ROM hackers 1.0 addresses
ExecCmd, C2/13D3. After the command, AfterAction2 (C2/083F) at C2/141D works out every actor's hold ($3AA0). Only then does _c2144f (C2/144F) remove the escaped actors, and its call to _c207c8 ends Quick at C2/081F ($3404 = $FF). So a Smoke Bomb used on a Quick turn leaves the jumper and the monsters held for good. Insert around the jsr _c2144f at C2/1420 (13 bytes): keep $3404 before it, and if it changed, run AfterAction2 again.
lda $3404 ; the Quick actor, before the jsr at C2/1420
pha
jsr _c2144f
pla
eor $3404
beq :+ ; Quick did not end here
jsr AfterAction2 ; the Quick user left: release the holds
:
Credit: previously found by Novalia Spirit and Assassin, my implementation
#405 Shadow no longer leaves the party after a battle won while he was still mid-Jump.
In 1.0
Shadow could walk out on the party after a battle won while he was still in mid-Jump, and his farewell then played with him never on screen.
Now
He now stays if the battle is won while he is in the air.
For ROM hackers 1.0 addresses
CheckBattleEnd, C2/47ED. The check for Shadow leaving after a won battle tests the battle type ($201F) at C2/486D but not $3F2C, the characters jumping or seized, so he can leave while in the air and his farewell plays with him off screen. Replace that lda $201F with a jsr to the routine below (same size; 10 bytes of free space in bank C2). X holds Shadow's slot; a nonzero result takes the bne to C2/488C, where he stays.
jsr MyFix ; replaces lda $201F at C2/486D
MyFix: lda $3F2C ; characters jumping or seized
and $3018,x ; Shadow's bit
ora $201F ; or not a normal battle
rts
Credit: previously found by Novalia Spirit, my implementation
#406 Air Anchor and the Runic stance now end at the party member's death instead of surviving to hit them after revival.
In 1.0
If a party member under Air Anchor died, the anchor stayed armed, so once revived they turned to dust at their first action. A party member in the Runic stance who died also kept the stance.
Now
Death now clears both the anchor and the stance.
For ROM hackers 1.0 addresses
_c246a9, C2/46A9, the Death set handler (16-bit A, Y = the target). It marks the target in $3A56 and nothing else, so the Runic stance ($3E4C bit 2 set) and an armed Air Anchor ($3205 bit 2 clear) outlive death and carry over to a revived character. Insert before the rts at C2/46AF (20 bytes): clear $3E4C,y bit 2 and set $3205,y bit 2.
php ; before the rts at C2/46AF
sep #$20
lda $3E4C,y
and #$FB ; Runic stance off
sta $3E4C,y
lda $3205,y
ora #$04 ; Air Anchor not armed
sta $3205,y
plp
Credit: previously found by Novalia Spirit, my implementation
#407 A party member killed by Air Anchor now actually dies even if their queued spell failed for lack of MP.
In 1.0
If a party member under Air Anchor tried a spell without enough MP, the spell failed and the anchor was used up, but they lived.
Now
The anchor now takes effect and the party member dies.
For ROM hackers 1.0 addresses
CalcAttackEffect, C2/3292. AirAnchorEffect (C2/3838) spends the anchor and marks the attacker to die ($11A3 bit 6) before the MP test. A spell that fails for MP leaves by the jmp at C2/32DD, which skips the death test at C2/3424 (asl, bpl, then _c23852 at C2/3852 sets Dead). Insert the same test before that jmp (16 bytes; A is 16-bit there).
sep #$20 ; before the jmp at C2/32DD
lda $11A3
asl ; bit 6: the attacker dies
bpl @done
phy
txy
jsr _c23852 ; set Dead, as at C2/3424
ply
@done: rep #$20
Credit: previously found by Novalia Spirit, my implementation
#408 An enemy under Relm's Control no longer counterattacks a spell cast at it.
In 1.0
An enemy under Relm's Control could still counterattack when a spell was cast at it, and its counter struck a party member.
Now
It now holds back and stays under Control.
For ROM hackers 1.0 addresses
ExecAIRetal, C2/4BF4. Before a counter, the unresponsive test sends a stopped, frozen or Charmed monster to C2/4C30, which allows no counter, but has no test for Control. Insert after the Charm test's bpl at C2/4C24 (5 bytes; A is 16-bit): lda $32B8,x / bpl to C2/4C30. The high byte read, $32B9,x, is the slot controlling this monster, $FF when none.
Credit: previously found by Novalia Spirit, my implementation
#409 An item aimed at a party member who becomes Muddled before it fires is now re-aimed the way a Muddled spell is.
In 1.0
An item chosen before its user became Muddled went off at nobody, and a Warp Stone ended the battle with no mosaic effect and no sound.
Now
Such an item is now re-aimed the way a Muddled spell is, except a Warp Stone, which like a Smoke Bomb keeps aiming at the party, and its escape plays the Warp sound again.
For ROM hackers 1.0 addresses
ExecCmd, items with no spell: the 18 bytes from C2/18CE to the jmp at C2/18E0 cut the targets ($B8) to the living and clear $B3 bit 2 (no re-aim), so an item queued before its user was Muddled, which arrives with no targets, goes off at nobody. Replace them with a jsr to a routine (76 bytes of free space in bank C2). If $B8/$B9 are empty and $11A9 is $96 (Smoke Bomb) or $9A (Warp Stone), aim at the user's side: $0F eor $3A40 for a party member ($3A40 bit clear), else $3A40 and $3F. Cut to the living; clear $B3 bit 2 only if $B8/$B9 were nonzero.
Credit: previously found by Novalia Spirit, my implementation
#410 Poison, Regen and Seizure kills now refresh the enemy list immediately instead of leaving stale "can't run" and run-difficulty flags.
In 1.0
Damage from Poison, Regen and Seizure, and counterattacks, did not update the enemy list, so after an enemy died that way the game still counted it when judging whether the party could run and how hard running was, until someone's next ordinary action.
Now
These now update the enemy list too, so running is judged on the enemies actually left.
For ROM hackers 1.0 addresses
ExecRetal, C2/4B7B, which runs counterattacks and the Poison, Regen and Seizure ticks. ExecAction sets $B0 bits 7 and 5 at C2/01A6; bit 5 asks for the monster refresh after the action (names, can't-run flags, run difficulty). ExecRetal never sets it. Insert lda #$20 / tsb $B0 (4 bytes) after the jsr SaveMimicAction at C2/4BE0, before C2/4BE3.
Credit: previously found by Novalia Spirit, my implementation
#412 Gogo mimicking a stumbled Dance now picks its own target instead of aiming at the dancer.
In 1.0
When a Dance stumbled, the game recorded the dancer as its target, so Gogo's Mimic of a dance that keeps its target, such as Plasma, struck the party.
Now
Gogo's copy now picks its own target.
For ROM hackers 1.0 addresses
SaveMimicAction, C2/021E. For a Dance that stumbled, the targets saved to $3F22 at C2/0251 are the dancer's own, so Gogo's Mimic dances at the party. Insert before the bra at C2/0254 (24 bytes; A is 16-bit there): if the command ($3A7C) is Dance ($13) and the actor has lost Dance status ($3EF8 bit 0), clear $3F22, and the copy picks its own target. X was reused, so the actor comes from the stack, pushed by the phx at C2/021E: 2,s inline, 4,s when reached with jsr.
sep #$20 ; before the bra at C2/0254
lda $3A7C
cmp #$13 ; Dance
bne @done
lda 2,s ; the actor (4,s under a jsr)
tax
lda $3EF8,x ; status 3
lsr ; Dance bit into carry
bcs @done ; still dancing: the dance went off
stz $3F22 ; a stumble: no target
stz $3F23
@done:
Credit: previously found by Novalia Spirit, my implementation
#414 Blitz codes now read from the acting party member's own controller, with their Config button layout, instead of both controllers at once.
In 1.0
Blitz codes were read from both controllers at once and ignored the button layout set in Config.
Now
Codes are now read from the controller of the player running that party member, with the Config layout applied.
For ROM hackers 1.0 addresses
The Blitz input handler (menu input BLITZ_INPUT, C1/6FF8) and InitBlitzInput (C1/776B) read $0A, the buttons of both pads without the Config button map, and take A and B from $6268 and $6269, built from the same. Change the $0A operands to $06, the active character's pad with the map applied, at C1/7000, C1/7004, C1/701C and C1/776B. Replace lda $6268 at C1/703E and lda $6269 at C1/707C with lda $0008 and lda $0009 in absolute form (newly pressed, mapped). All same size.
Credit: previously found by Novalia Spirit, my implementation
#439 True Knight covering a multi-strike attack no longer redirects later strikes to the bodyguard; each strike keeps its original target.
In 1.0
When True Knight sent a bodyguard to take the first strike of a multi-strike attack, the next strike went for the bodyguard, or for a random target if the first strike had killed him, and Mimic repeated the attack on the bodyguard.
Now
Every strike, and a Mimic of the attack, keeps the target the attacker chose, and True Knight steps in again for each strike.
For ROM hackers 1.0 addresses
CoverEffect (C2/123B) puts the bodyguard into the strike's targets, and $3A30 keeps them after it. ExecAttack copies $3A30 back to $B8 for the next strike at C2/3243, and the Mimic saves at C2/024E and C2/025E record it, so later strikes and Mimic aim at the bodyguard. Keep the targets as chosen in a free word of RAM: in CalcAttackEffect insert a 16-bit store of $B8 to it before the jsr CountBits at C2/32A1 (3 bytes). Change the three reads of $3A30 to read the word (same size). In _setupoldtarget, insert the same store after the sta at C2/3870 (3 bytes), so a follow-up spell keeps its target.
Credit: previously found by Novalia Spirit, my implementation
#470 A Bushido queued after a jump that broke its weapon now falls back to a plain Fight instead of swinging with no blade.
In 1.0
If Cyan or Gogo held only an Ogre Nix, it could break as they landed from a Palidor jump, and a Bushido they had queued behind the jump still went off afterwards with no sword in hand.
Now
With no blade left, the Bushido now becomes a plain Fight.
For ROM hackers 1.0 addresses
Bushido command, C2/1847. The blade is tested only when the command is chosen, and an Ogre Nix can break before the action runs. Insert after the tyx (9 bytes): jsl Armed, bcs back to the Bushido code, else jmp C2/1610, the Fight path. Armed (37 bytes plus the Mimic test) returns carry set for a monster, a Mimic copy or a blade in either hand; otherwise it turns the action into a Fight. Mark a Mimic copy with a free bit per character (this ROM hack: #278's item flags), set where Mimic loads the action (C2/01F3), cleared where GetPlayerActions reads a menu action (C2/4D41).
; free space, 8-bit A and index, x = y = the actor
Armed: cpx #$08
bcs @go ; a monster
; Mimic mark set: bne @go
lda near wTargetProp2::RHandWeaponFlags,x
ora near wTargetProp2::LHandWeaponFlags,x
bit #WEAPON_FLAG::BUSHIDO
bne @go
stz zb5 ; command: Fight
stz near w7e3a7c ; and what Mimic records
stz near w7e3a7d
lda zb8_L
and near w7e3a40 ; no allies, as for an imp
sta zb8_L
lda #$ff
sta zbb ; the Fight's own targeting
clc
rtl
@go: sec
rtl
Credit: previously found by Master ZED and Fenrir9 via the Final Fantasy Wiki, my implementation
#471 A Love Token bond holder no longer covers their own attacks, and a self-cast Love Token no longer bonds the caster to themself.
In 1.0
A party member bound by Love Token covered the one they were bound to even against their own attacks, so a Fight they aimed at that party member hit themselves, and a Love Token cast on oneself bound the caster to themself.
Now
The bond holder now never covers their own attack, and a self-cast Love Token makes no bond.
For ROM hackers 1.0 addresses
CoverEffect, C2/123B. The ldx of the bond holder (wTargetProp1::LoveTokenTarget) at C2/1254 sends the bond holder in front of any attack on the bound character, their own included. Change that ldx (3 bytes) to a jsl (4 bytes, so 1 byte inserted) to a routine (19 bytes of free space) that returns X = $FF when the bond holder is the attacker; the bmi after it then skips the cover. TargetEffect_2d (Love Token), C2/3D37: insert cmp 5,s and beq to the rts at C2/3D42 before the sta at C2/3D3B (4 bytes), so a self-cast makes no bond.
; free space, 16-bit A, 8-bit index, y = target
LoveCover:
ldx near wTargetProp1::LoveTokenTarget,y
bmi @done ; no bond
txa
shorta
cmp 4,s ; the attacker (CoverEffect's phx)
longa
bne @keep
ldx #$ff ; the bond holder is the attacker
@keep: inx
dex ; n from x, for the bmi
@done: rtl
; TargetEffect_2d, before the sta at C2/3D3B
cmp 5,s ; target is the caster?
beq @3d42 ; no bond
Credit: previously found by Novalia Spirit, my implementation
#472 Holding L and R to run no longer builds escape progress while running is blocked, and the run counter no longer wraps past its maximum.
In 1.0
Holding L and R while running was blocked still built up escape progress, so the party got away sooner once the block lifted, and on a long hold the counter could pass its maximum and roll back to a small number.
Now
Holding L and R while running is blocked no longer counts toward escaping, and the progress stops at its maximum instead of rolling back.
For ROM hackers 1.0 addresses
CheckRunAway, C2/5C1B. While L and R are held, each tick adds a random amount to the run counter $3D70,x even while running is blocked ($B1 bit 1), and the add can wrap past $FF. Replace clc, adc and sta at C2/5C41 (7 bytes) with a jsl to a routine (24 bytes of free space): with $B1 bit 1 set it only loads the counter; otherwise it adds and stores $FF on a carry. The compare that follows is unchanged.
; free space, 8-bit A, a = amount, x = character
RunAdd: xba
lda zb1
bit #$02
bne @held ; running blocked: add nothing
xba
clc
adc near wTargetProp2::w7e3d70,x
bcc @store
lda #$ff ; stop at the top
@store: sta near wTargetProp2::w7e3d70,x
rtl
@held: lda near wTargetProp2::w7e3d70,x
rtl
Credit: previously found by Novalia Spirit, my implementation
#473 Bio Blaster and Flash can now hit a party member fighting on the enemy side, as can Espers' summons and desperation attacks with the same rule.
In 1.0
Edgar's Bio Blaster and Flash, and Espers' summons and desperation attacks, carry a rule that they cannot target party members, which kept them off a party member fighting on the enemy side too.
Now
The rule now spares only the party, so they can hit such a party member.
For ROM hackers 1.0 addresses
_c258fa (C2/58FA), the target mask step of ChooseTarget. When the targeting byte $BA has bit 7 (can't target characters, as on Bio Blaster and Flash), the stz at C2/5909 clears every character from $B8, including those fighting on the enemy side ($3A40). Replace it with lda $B8, and $3A40, sta $B8, then lda $BA again for the lsr at C2/590B (9 bytes for 2).
lda zb8_L
and near w7e3a40 ; keep characters on the enemy side
sta zb8_L
lda zba
Credit: previously found by Novalia Spirit, my implementation
#474 A failed death-weapon kill, blocked by instant death protection or a refused jump-strike kill, no longer revives the undead target and erases the damage dealt.
In 1.0
Against an undead enemy, a death weapon (Assassin, Striker, Wing Edge, Trump) whose kill failed, against instant death protection or on a Dragon Horn jump strike before the last, still revived it at full HP and wiped out the damage the hit had shown.
Now
A failed kill now leaves the enemy with the damage it took, and only a kill that lands revives it.
For ROM hackers 1.0 addresses
TargetEffect_03 (C2/3D43), the X-kill weapon effect, calls ScimitarEffect (C2/3890) and falls into TargetEffect_35 (C2/3D49), which revives an undead target, even when ScimitarEffect refused the kill. It refuses for instant death protection ($3AA1 bit 2) and, in the US game, for a jump strike that is not the last ($3A70 nonzero with command $B5 = Jump). Insert after the jsr at C2/3D46 (7 bytes): jsl a routine (22 bytes of free space) that repeats those two tests, bcs TargetEffect_35, rts. After a kill ScimitarEffect has made $B5 Magic, so the jump test does not match.
jsl Killed
bcs TargetEffect_35
rts
; free space, 8-bit A, y = target
Killed: lda near wTargetProp2::w7e3aa1,y
bit #$04
bne @no ; instant death protection
lda near w7e3a70
beq @yes
lda zb5
cmp #BATTLE_CMD::JUMP
beq @no ; a jump strike with more to come
@yes: sec
rtl
@no: clc
rtl
Credit: previously found by Master ZED, my implementation
#475 A Runic stance is no longer used up when the absorbed spell fails for lack of the caster's MP.
In 1.0
When a spell failed because its caster no longer had the MP for it, a party member in Runic stance still lost the stance to that spell and absorbed nothing.
Now
Runic now lets such a spell pass unabsorbed, and the stance holds.
For ROM hackers 1.0 addresses
RunicEffect, C2/352B, runs before CalcAttackEffect's MP test (C2/32B1), so a spell its caster cannot pay for spends the Runic stance and then fails. Replace the absorb test at C2/352B (lda $11A3, bit #$08, beq: 7 bytes) with jsl and bcc to C2/35AC (6 bytes). The routine (20 bytes of free space) returns carry clear when $11A3 bit 3 is clear or when the caster's current MP is below the cost $3A4C.
; free space, 8-bit A, x = caster
CanAbsorb:
lda $11a3
bit #$08
beq @no ; can't be absorbed
longa
lda near wTargetProp2::CurrMP,x
cmp near w7e3a4c ; carry clear: cannot pay
shorta
rtl
@no: clc
rtl
Credit: previously found by Novalia Spirit, my implementation
#490 A Fenix Down aimed at an undead enemy that died first now redirects to another living enemy instead of being wasted on nothing.
In 1.0
A Fenix Down aimed at an undead enemy that died before the item landed was wasted: the user stepped forward and the sounds played, but no feathers showed and no enemy was hit.
Now
The revive now goes to another living enemy at random, as do Revivify and the Life spells.
For ROM hackers 1.0 addresses
ChooseTarget, C2/587E. At C2/58A5 the lda $B8 / ora $B9 test keeps an aim already chosen, and a revive ($BA bit 3) is never re-aimed, so aimed at an undead monster that has died it does nothing. Replace those 4 bytes with a jsl to a routine (145 bytes) that ends with the same test. For a revive aimed at monsters only, it clears from $B9 each undead one ($3C95 bit 7) that is Dead ($3EE4), out of the fight ($3AA0 bit 0 clear) or hidden ($3EF9). If none is left, $B9 takes the monsters not Dead, Stone, Zombie or gone, and $2F46, $3A79, $3409, less $3F2D unless self-aimed, less the attacker, and $BB loses its multi-target and half bits so one is rolled. If still empty, the aim is restored.
Credit: previously found by Novalia Spirit, my implementation
#496 Multiple steals from one Capture action now each name their own stolen item, instead of all naming the last one taken.
In 1.0
With the Offering and a Thief Glove, Capture could strike up to four times and steal on each strike, but every "Stole" message named the last item taken.
Now
Each message now names the item its own strike took.
For ROM hackers 1.0 addresses
The attack message handler reads message variable 0 ($2F35) when it draws, and by then every strike of the action has run. The queue writer _c2629e (C2/629E) writes four bytes an entry, and the attack message command reads only bytes 0 and 1. Insert a jsl before the inx4 at C2/62B4 (Stamp, 22 bytes of free space) that copies $2F35 into byte 2. At the handler's start, C1/9855, insert a jsl (PopItem, 16 bytes) that puts byte 2 back in $2F35 for the Stole message only; Scan's line sets its own variable.
jsl Stamp ; insert before inx4 at C2/62B4
jsl PopItem ; insert at C1/9855
Stamp: lda $3a28 ; 16-bit A, 8-bit X
and #$00ff
cmp #$0002 ; attack message command
bne @done
sep #$20
lda $2f35 ; message variable 0 now
sta $2d70,x ; byte 2 of the entry
rep #$20
@done: rtl
PopItem:
ldy #1
lda ($76),y ; message index
cmp #ATTACK_MSG::STEAL_MSG + 2
bne @done
iny
lda ($76),y
sta $2f35
@done: rtl
Credit: previously found by Assassin and fixed by SilentEnigma, my implementation
#505 Taking Retort's stance no longer clears Vanish from its target, since the stance deals no real magic damage.
In 1.0
Taking Retort's stance removed Vanish from whoever it was aimed at, enemy or party member.
Now
Taking the stance no longer removes Vanish from anyone.
For ROM hackers 1.0 addresses
EquipStatusMod, C2/4490. The Vanish clear skips physical attacks with lda $11A2 / lsr / bcs at C2/44A7. Retort's stance runs as a Bushido attack with the physical bit clear and marks itself with $B6 = $80, so it clears its target's Vanish. Replace the lda and lsr (4 bytes) with a jsl to the routine below (29 bytes of free space); the bcs stays.
Credit: previously found by Field of Dreams, my implementation
#508 A spell an MP-critical weapon casts at random no longer inherits the triggering swing's critical hit.
In 1.0
A spell cast at random by an MP-critical weapon such as the Illumina took the critical hit of the swing that cast it, doing double damage and flashing the screen.
Now
The spell now deals normal damage.
For ROM hackers 1.0 addresses
CalcAttackEffect (C2/3292), critical test at C2/33F2. $B2 bit 1 means no critical; $B3 bit 1 clear means an automatic critical, which an MP-critical weapon's swing leaves in place for the spell it casts. 1.0 tests $B3 first, at C2/33F4, so the proc spell doubles and flashes. Swap the two test pairs in place, same size: bit $B2 / bne to C2/3414 at C2/33F4, then bit $B3 / beq to C2/340C. A stays #$02 for both.
Credit: previously found and fixed by Assassin and Terii Senshi, my implementation
#510 Gil stolen from an enemy no longer gets doubled by a Cat Hood and the Config gil rate on top of the enemy's own reward.
In 1.0
Gil an enemy stole came back when it was killed, but a Cat Hood doubled it along with the enemy's own reward.
Now
Stolen gil now comes back once, as taken; only the enemy's own reward is doubled.
For ROM hackers 1.0 addresses
TargetEffect_52 (C2/399E), a monster's gil steal, adds it to the thief's own MonsterGil (adc at C2/3A2A, sta at C2/3A31). WinBattle (C2/5D57) sums dead monsters' MonsterGil into $2F3E, then doubles it for a Cat Hood. Keep stolen gil in 16 zeroed free bytes of battle RAM: a word per monster, a 3-byte total, and a spare byte its 16-bit inc touches. Point that adc and sta at the words (same size; x is the target index, 8 and up, so use their address minus 8). After the inc at C2/5DDB, with the bcc before it pointed there, insert an add of the dead monster's word into the total (15 bytes); before C2/5F78, after the doubling, add the total into $2F3E (20 bytes).
Credit: previously found by gcgx.games, my implementation
#511 Illumina, Ragnarok, Punisher, Rune Edge and Ogre Nix can now land MP critical hits for a party member with no spells or espers.
In 1.0
At the start of a battle, a party member who knows no spell and has no esper equipped lost their MP, so with the Illumina, Ragnarok, Punisher, Rune Edge or Ogre Nix they had none for the weapon to spend and never landed MP critical hits.
Now
That MP is now kept while one of these weapons is equipped, so MP critical hits can land.
For ROM hackers 1.0 addresses
InitCmdList (C2/532C) zeroes current and maximum MP (the longa and two stz from C2/5400) for a character who knows no spell and wears no esper, so an MP-critical weapon (attacker effect $07, the high nibble of WeaponSpecial; $0E and $0F act the same) has nothing to spend. Insert before C2/5400 a jsr to a test of both hands' WeaponSpecial high nibble ($70, or $E0 and up; 25 bytes of free space in bank C2), with bcs to C2/5408 when it matches. For a weapon put on mid-battle, insert before the jsr UpdateImmuneStatus at C2/20D5 in UpdateCharProp (C2/2095) a jsr that, when MaxMP is 0 and the test matches, reloads current MP from $160D and maximum MP from $160F through CalcMaxHPMP, capped at 999.
Credit: previously found by god-bird, my implementation
#520 An Interceptor or Golem block on a Zombie or Magitek target is no longer downgraded to a plain miss.
In 1.0
An Interceptor or Golem block on a Zombie or Magitek target was turned into a plain miss, with no dog or wall, and Golem's wall took nothing from the blow.
Now
The dog or the wall now appears, and Golem's wall takes the blow.
For ROM hackers 1.0 addresses
Same routine as #519. CheckHit (C2/220D), miss path at C2/22B5: the Vanish, Zombie and Magitek test on the target's status 1 comes first and branches past the block animation, so a Golem or Interceptor block already chosen ($FE nonzero) on such a target shows as a plain miss and the wall takes nothing. Insert at C2/22B5, ahead of the status test (4 bytes): lda $FE, bne to C2/22BC, where the block animation code starts. A physical attack on a vanished target misses earlier, so in practice this is Zombie and Magitek.
Credit: previously found and fixed by Bropedio, my implementation
#521 A weapon's random follow-up spell no longer rolls when the strike had no target left.
In 1.0
When a Fight had no target left, a weapon that casts a spell as it strikes still rolled for the spell, which could go off at nobody or hit another enemy.
Now
The spell now rolls only when the strike reached a target.
For ROM hackers 1.0 addresses
ExecAttack (C2/317B) calls CheckWeaponSpellCast (C2/363E) at C2/324D after every strike, and its one-in-four roll can cast the weapon's spell when the strike reached no target. Insert before that jsr (5 bytes): lda $3EC9 (the number of targets, counted in CalcAttackEffect), beq over the jsr to C2/3250. CheckWeaponSpellCast itself stays as in 1.0.
Credit: previously found and fixed by Bropedio, my implementation
#522 A spell cast by breaking a rod now removes Vanish from its target, as the same spell cast from the Magic list does.
In 1.0
A spell cast by breaking a rod with the Item command did not remove Vanish from its target, as the same spell from the Magic list does.
Now
It now removes Vanish.
For ROM hackers 1.0 addresses
Item command, C2/1897. It clears $B3 bit 7 at C2/189A before it knows what the item is, and the branch that runs an item's own spell (a broken rod) never sets it back, so Vanish stays out of that spell's clear list. Replace the lda #$80 / trb $B3 at C2/189A with four nop. On the plain-item path, insert before the cmp #$02 at C2/18BF (10 bytes): cmp #$01 (the Item command), bne past the rest, lda #$80, trb $B3, lda $B5 to restore A. Throw and the other commands that share this code keep the bit.
Credit: previously found and fixed by Seibaby and Bropedio, my implementation
#523 A party member who starts a battle dead keeps the statuses their equipment grants, and a Fenix Down can restore them.
In 1.0
A party member who began a battle dead lost every status their equipment grants, and a Fenix Down could not restore them.
Now
The statuses are now kept, and a Fenix Down restores them.
For ROM hackers 1.0 addresses
UpdateStatus (C2/4391) and InitStatusVars (C2/450D). The toggle permissions ($331C, $3330) are worked out after the opening statuses are set, so at the bell Death's handler strips equipment statuses unopposed and the lock keeps them off. Rule: a status the target is permanently granted may always be set and is never cleared. Replace the four permission filters with a jsr each (about 80 bytes of free space in bank C2). To-set at C2/4517 and C2/451F: (permission OR grant) AND list. To-clear at C2/43C6 and C2/43DE: NOT grant AND permission AND list. The grant is $3C6C (status 3 bit 0 as Float), without the six statuses UpdateImmuneStatus (C2/2675) never locks and without what the target also blocks (code: C2/28BF, C2/2E10).
; UpdateEquipBattle, the gear's status 2 grant (C2/28BF, 8-bit A)
lda <$11d3 ; the gear's blocked status 2
eor #$ff
and <$11bc ; the gear's granted status 2
sta $3c6c,x
; LoadRageProp at C2/2E10 (16-bit A): move the four instructions
; of the block (MonsterProp+20) ahead of the grant, then
sep #$20
lda $3330,y ; permitted status 3
ora #$01 ; Float in the grant: always passes
xba
lda $331d,y ; permitted status 2
rep #$20
and f:MonsterProp+28,x ; the grant, less the block
ora $3c6c,y
sta $3c6c,y
Credit: previously found and fixed by Bropedio, my implementation
#524 An enemy a script brings in mid-battle now gets the statuses its own data gives it, such as Float.
In 1.0
An enemy a battle's script brings in after the fight has started never got the statuses its own data gives it, such as Float, Safe, Shell or Haste.
Now
Those statuses now apply when the enemy arrives.
For ROM hackers 1.0 addresses
Same rule as #523, which covers it with no further code. The battle start loads every slot's data, but its status pass skips a monster that is not on the field while the permission pass (UpdateImmuneStatus, C2/2675) still closes that monster's granted statuses, so they wait in the to-set lists ($3DD4, $3DE8) and never land. With #523's set filter, permission OR grant, they land through UpdateStatus (C2/4391) the turn the monster arrives, each with its own set handler.
Credit: previously found and fixed by Bropedio, my implementation
#525 A second poisoning in the same battle now starts its rising damage from the beginning.
In 1.0
Poison damage climbs with every tick, and curing Poison never reset the climb, so a second poisoning in the same battle could open at full strength.
Now
A new bout of Poison now starts the climb from the beginning.
For ROM hackers 1.0 addresses
Every poison tick reads the multiplier in $3E24,y and raises it by 2, and nothing resets it. In SetStatusTbl (C2/46B0) the Poison entry (STATUS_ID 2, the pointer at C2/46B4) points to the shared no-effect return. Point it at a new handler (16 bytes of free space in bank C2) that zeroes $3E24,y when the target does not already carry Poison ($F8 bit 2). The test matters because the zombie attack's poison reaches the to-set list without the carried-status filter.
PoisonSet: ; SetStatusTbl's Poison entry points here
php
sep #$20
lda $f8 ; status 1 as it stands
and #$04 ; already poisoned?
bne @keep
lda #$00
sta $3e24,y ; poison damage multiplier
@keep: plp
rts
Credit: previously found by Synchysi and fixed by Bropedio, my implementation
#526 A Rage now cures the statuses it grants immunity to, so a poisoned Gau is no longer stuck with the Poison for the whole fight.
In 1.0
Once Gau took a Rage that gives immunity to a status he already had, such as Poison, Blind, Imp, Sleep, Slow or Stop, nothing could cure it for the rest of the battle; a Near Fatal kept this way at full HP made a True Knight wearer guard him.
Now
The Rage now cures those statuses when it is entered.
For ROM hackers 1.0 addresses
LoadRageProp (C2/2DC1) ANDs the raged monster's blocked statuses (MonsterProp +20, +22) into the toggle permissions ($331C, $3330), which stops cure and affliction alike (Near Fatal's hp-rise clear too). Insert before the plp at C2/2E38 a jsl (about 70 bytes of free space): for a character with Rage in $3EF9, blocked AND carried ($3EE4, $3EF8) AND NOT to-set ($3DD4, $3DE8) is ORed into the to-clear lists ($3DFC, $3E10) and into the permissions. In the Rage command (C2/1560), insert after the jsr FixImmuneStatus at C2/1588: phy, jsr UpdateStatus, ply, a jsl that closes those permissions again from MonsterProp for the monster in $33A8,y (about 55 bytes), tyx, jsr FixImmuneStatus (C2/2650).
Credit: previously found and fixed by Think and Bropedio, my implementation
#527 In a side attack or pincer, an action the game aims by itself can now pick a single target anywhere on the field.
In 1.0
In a side attack or pincer, an action the game aims by itself, such as a Rage, a muddled party member's action or a reflected spell, could pick a single target only in one half of the party, or from only one of the two enemy groups in a pincer.
Now
A single target can now be anywhere, as with the player's cursor.
For ROM hackers 1.0 addresses
ChooseTarget (C2/587E). The jsr CheckTargetsBattleType at C2/58C8 narrows the mask to one side or group before C2/58DE decides spread or single, so an auto-aimed single target is drawn from half the field. Decide first: replace that jsr with a jsr to the routine below (44 bytes of free space in bank C2), which returns carry set for a spread. Nothing from C2/58CB to the bcs at C2/58EB touches the carry, so replace the 13 bytes from C2/58DE with nop and keep that bcs. A single target enters CheckTargetsBattleType (C2/59AC) at C2/5A35, past the side and pincer narrowing; a reflected action and a spread take the whole routine as before.
jsr MyFix ; replaces jsr CheckTargetsBattleType at C2/58C8
MyFix: lda $bb ; targeting byte
bit #TARGET::INIT_HALF
bne @spread
bit #TARGET::MULTI_TARGET
beq @single
jsr RandCarry ; 50% chance to spread
bcs @spread
@single: lda $ba
bit #$10 ; reflected: the whole filter
bne @whole
jsr @one
clc
rts
@spread: jsr CheckTargetsBattleType
sec
rts
@whole: jsr CheckTargetsBattleType
clc
rts
@one: lda $bb
and #TARGET::INIT_MASK
pha ; what C2/59DA pushes
jmp $5a35 ; C2/5A35, past the narrowing
Credit: previously found and fixed by Bropedio, my implementation
#530 R. Polarity no longer changes a party member's row in a pincer attack, where nobody can move.
In 1.0
In a pincer attack, where nobody can change row, R.Polarity still flipped a party member's row for damage, with nothing on screen to show it.
Now
In a pincer R.Polarity now leaves the row alone.
For ROM hackers 1.0 addresses
TargetEffect_2b, C2/3980, special effect $2B (R. Polarity), flips the target's row bit ($3AA1 bit 5) with no test. In a pincer the row-change distance tables at C2/A86F (leaving the back row) and C2/A87F (leaving the front row), indexed by battle type ($201F) times 4 plus the character, hold 0, so nobody moves but the damage row still changes. Uses the test routine of #57 (RowChangeMoves: carry set when the entry for the row the character stands in is not 0). Replace the effect's 9 bytes with a jsl to the routine below and an rts (20 bytes of free space). Monsters flip as before.
jsl MyFix ; at C2/3980, then rts
MyFix: tya
cmp #$08
bcs @flip ; a monster
jsl RowChangeMoves ; #57
bcc @done ; nobody moves in this battle
@flip: lda $3aa1,y
eor #$20
sta $3aa1,y
@done: rtl
#531 A party member who goes down before a queued summon goes off can summon that esper again once revived.
In 1.0
Choosing a summon uses up a party member's one Esper summon per battle at once, so if they were knocked out, turned to stone or put to sleep before it went off, the summon was thrown away and the Esper stayed greyed out in the Magic list all fight.
Now
The Esper is now given back and can be summoned once the party member is up again.
For ROM hackers 1.0 addresses
RemoveAllActions, C2/0A0F, throws away a character's pending actions when Wound, Stone or Sleep takes them. FixPlayerAttack sets the actor's bit in $3F2E (esper spent) when a Summon is queued, and nothing clears it when the summon is thrown away unrun. Insert a jsl at the head of RemoveAllActions, before its jsr RemoveAction (4 bytes), to the routine below (42 bytes of free space). For a character it walks the pending list ($32CC,x the first entry, $3184 the chain, the last entry pointing at itself) and clears the actor's bit in $3F2E for a command $19 (Summon) in $3420.
MyFix: cpx #$08 ; jsl here, inserted at C2/0A0F
bcs @done ; a monster reserves no esper
phy
ldy $32cc,x ; first pending action
bmi @pull ; none
@next: tya
asl
phy
tay
lda $3420,y ; its command
ply
cmp #$19 ; Summon
bne @more
lda $3018,x
trb $3f2e ; the esper is unspent
@more: tya
cmp $3184,y
beq @pull ; the last points at itself
lda $3184,y
tay
bra @next
@pull: ply
@done: rtl
#545 Earrings no longer make Poison and Seizure ticks hurt their wearer more.
In 1.0
Earrings made Poison and Seizure ticks hurt their wearer more.
Now
Poison and Seizure ticks now do the same damage with or without Earrings.
For ROM hackers 1.0 addresses
RelicDmgEffect, C2/0D4A, applies the Earring and Atlas Armlet bonuses. The Poison and Seizure ticks run as a spell the character casts on themselves, marked with command $12 (Mimic) in $B5, so the Earring bonus to the wearer's spells raises them. Insert after the php at C2/0D4A: lda $B5, cmp #$12, beq to the plp at C2/0D85 (6 bytes).
Credit: previously found and fixed by the Brave New World 2.1 team, my implementation
#556 A spell a weapon casts as a Jump lands, such as the Tempest's Wind Slash or the Holy Lance's Holy, no longer takes the Jump's extra damage.
In 1.0
A spell a weapon cast as a Jump landed, such as the Tempest's Wind Slash or the Holy Lance's Holy, kept the Jump's damage bonus.
Now
The spell now does the same damage as after a Fight.
For ROM hackers 1.0 addresses
_c237eb, C2/37EB, sets up the spell a weapon casts on a strike. ExecAttack copies the command's damage multiplier $BD into $BC at its entry (C2/317C), and Jump raises it, so the spell cast on the Jump's blow ran with that multiplier. Insert stz $BC (2 bytes) after the jsr InitAttacker at C2/37FC, before C2/37FF. $BD is left alone, so a later strike of the same action copies it afresh.
Credit: previously found by Master ZED and Assassin, my implementation
#557 A weapon's spell, such as Ragnarok's Flare or the Flame Sabre's Fire, no longer does half damage when its wielder stands in the back row; the swing itself still does.
In 1.0
A weapon's spell, such as Ragnarok's Flare, the Flame Sabre's Fire, the rods' spells and the Tempest's Wind Slash, did half damage when its wielder stood in the back row.
Now
The spell now does the same damage from either row.
For ROM hackers 1.0 addresses
_c237eb, C2/37EB. $B3 bit 5 set means the attack ignores the attacker's row. The swing's _c2299f (C2/299F) clears it unless the weapon has the back row property, and InitAttacker leaves it as it is, so the spell a weapon casts on that swing was halved from the back row. Insert lda #$20 / tsb $B3 (4 bytes) after the jsr InitAttacker at C2/37FC, before C2/37FF. The next swing of the same action clears the bit again for its own weapon, and this one site also covers the Tempest's Wind Slash.
Credit: previously found by Assassin and Omnia Crystallis, my implementation
#558 A party member who uses Tools on their own, Muddled, Charmed or in the Colosseum, no longer sets off a NoiseBlaster when the party owns no tools.
In 1.0
A party member who picked Tools on their own, while Muddled, Charmed or fighting in the Colosseum, set off a NoiseBlaster even when the party owned no tools at all.
Now
With no tools owned, they now pick another command.
For ROM hackers 1.0 addresses
RandTools, C2/058D, picks the tool for an automatic Tools turn from the owned-tools mask $3A9B. With the mask zero, RandBit and GetBitNum return bit 0, which is NoiseBlaster. Insert beq RandCmdInvalid (C2/054F, 2 bytes) after the lda $3A9B at C2/058E, before the jsr RandBit at C2/0591. Tools is then struck out of the pick, as RandLore (C2/04F6) strikes out Lore when none is known.
Credit: previously found by Lapse Pointer, my implementation
#559 Once Cyan has been knocked out and revived, his Retort answers only an attack on him again, not every physical attack in the battle.
In 1.0
Once Cyan had been knocked out and revived, his Retort stance answered any physical attack for the rest of the battle, not only one aimed at him, and struck a random enemy.
Now
It now answers only an attack on him.
For ROM hackers 1.0 addresses
setnowdead, C2/46A9, marks every target that goes down in $3A56 so its counter script can run a dying pass; ExecAIRetal (C2/4BF4) spends the mark. A character has no counter script ($3268,x stays $FFFF), so its mark was never spent, and CheckRetal (C2/4C5B) let that character past both "was I the target" and "no counter to a counter" for the rest of the battle. Insert lda $3268,y / bmi to the rts (5 bytes, 16-bit A) at the head of setnowdead, so the mark is set only for an actor with a counter script. Monsters, and characters that run a script (the Sealed Gate Kefka, Gau from the Veldt), keep it.
Credit: previously found by Lenophis, Assassin and the Final Fantasy Wiki, my implementation
#561 Changing a weapon or shield through the battle Item menu now applies its Speed bonus to the action gauge at once, not after the party member's next action.
In 1.0
Changing a weapon or shield through the battle Item menu changed Speed at once, but the action gauge kept filling at the old pace until the party member's next action.
Now
The gauge now fills at the new pace at once.
For ROM hackers 1.0 addresses
UpdateCharProp, C2/2095, applies an equipment change made in battle: it runs UpdateEquip, UpdateEquipBattle, UpdateCmdList and UpdateImmuneStatus, but not CalcSpeed (C2/09D2), which turns Speed and Haste or Slow into the gauge constant. The constant was refreshed only at the character's next action. Insert jsr CalcSpeed (3 bytes) after the jsr UpdateImmuneStatus at C2/20D5, before the ply at C2/20D8; X already holds the target index.
Credit: previously found by Assassin, my implementation
#562 A Gem Box caster who is Muddled or turned into a Zombie after choosing two X-Magic spells now has both spells aimed anew, not only the first.
In 1.0
A Gem Box caster who was Muddled or turned into a Zombie after choosing two X-Magic spells had only the first spell aimed anew; the second still went at the enemy the menu had picked.
Now
Both spells are now aimed anew.
For ROM hackers 1.0 addresses
InitPlayerAction, C2/0276. At C2/028E each starting action does trb $3A4A ("Muddle or Zombie changed") and clears the targets $B8 if the bit was set, so the first queued action took the bit and a second X-Magic spell kept its menu aim. Leave the bit for all but the actor's last queued action: its command list entry points at itself, the test ExecAction makes at C2/014E. Replace the 12 bytes from C2/028E with the jsl and bra below; the routine needs 32 bytes of free space.
jsl MyFix ; at C2/028E
bra @029a
MyFix: lda $3018,x ; actor's mask
bit $3a4a ; changed since the menu? (read, not taken)
beq @done
phy
ldy $32cc,x ; this action's command list entry
lda $3184,y ; next entry, itself when last
ply
cmp $32cc,x
bne @clear ; more actions wait: leave the bit
lda $3018,x
trb $3a4a ; the last action takes it
@clear: stz $b8 ; target chosen at random
stz $b9
@done: rtl
Credit: previously found by Assassin, my implementation
#563 In a Genji Glove Fight, the second strike can land a critical hit and be covered by a True Knight even when the first strike missed or cast its weapon's spell.
In 1.0
In a Fight with the Genji Glove, if the first strike missed or cast its weapon's spell, such as the Flame Sabre's Fire, the second strike could not land a critical hit and a True Knight would not step in front of it.
Now
Each strike now has its own chance of a critical hit and of being covered.
For ROM hackers 1.0 addresses
ExecAttack, C2/317B. $B2 bit 1 means "no critical, True Knight ignores". A missed strike or a weapon's spell sets it, and the Fight command clears it once per action, so the Genji Glove's second strike inherited it. Replace lda $3A70 / inc / lsr at C2/3198 (5 bytes) with jsl MyFix / nop; MyFix does lda #$02 / trb $B2, then the three replaced instructions, and rtl (10 bytes of free space). The carry still carries the hand into _c2299f (C2/299F), which sets the bit again for the Offering.
Credit: previously found and fixed by Assassin, my implementation
#566 A fight that forbids running can no longer be fled in the moment one enemy hands over to another, such as Ifrit giving way to Shiva.
In 1.0
In a fight that forbids running, the party could run away while one enemy handed over to another, as when Ifrit and Shiva swap, Master Pug and the last of the Pugs go back to their places, or Ultros sinks into the ground in the third fight with him.
Now
Running now stays blocked all through a hand-over.
For ROM hackers 1.0 addresses
UpdateMonsterGfxBuf, C2/5C73, clears can't run and no warp ($B1 bits 1 and 2) and sets them again from the monsters shown. A hand-over is kill_monsters_wait (AI $F5 mode 4) then a show, two actions, so the refresh between them found no monster forbidding a run. The hide sets $3A95, which the show clears. Replace lda #$06 / trb $B1 at C2/5C85 (4 bytes) with jsl MyFix: lda $3A95 / bne skip / lda #$06 / trb $B1 / skip: rtl (10 bytes of free space). The run difficulty is rebuilt as before.
Credit: previously found by Novalia Spirit, Assassin, Lenophis and Speed Demos Archive and fixed by Lenophis, my implementation
#570 Shadow no longer leaves the party after a won battle he had already run away from, which played his farewell with him off the field.
In 1.0
Holding L and R makes the party run one at a time, and a Shadow who had already run away could still walk out on the party once the others won, so his farewell played with him nowhere on the field.
Now
A Shadow who has run from a battle now stays for it.
For ROM hackers 1.0 addresses
CheckBattleEnd, C2/47ED, rolls 1 in 16 for Shadow leaving after a win and never asks whether he has already left the fight ($3A39: ran away, Sneeze, Escape, Smoke Bomb). Same routine as #405, which replaces the lda $201F at C2/486D with a jsl (1 byte more) to lda $3F2C / ora $3A39 / and $3018,x / ora $201F / rtl (13 bytes of free space), and a nonzero result keeps him. This entry is the ora $3A39 (3 bytes).
Credit: previously found by エディ, my implementation
#573 When two casters Charm the same target, the first is now set free instead of staying tied to it, so its later Charms no longer miss.
In 1.0
If a second caster Charmed a target the first caster had Charmed, the first caster stayed tied to it and every Charm it cast after that missed.
Now
The first caster is now set free, so its next Charm works.
For ROM hackers 1.0 addresses
TargetEffect_34, C2/3C91 (Charm). It sets the attacker's charm target $3394,x and the target's charmer $3395,y, but never frees the target's earlier charmer, whose $3394 still points at the target and blocks its next Charm. Replace the 8 bytes from C2/3C99 with jsl MyFix and rts; MyFix, 22 bytes of free space, 8-bit A and index:
MyFix: lda $3395,y ; the target's charmer so far
bmi @new ; none
phx
tax
lda #$ff
sta $3394,x ; that charmer is free again
plx
@new: tya
sta $3394,x ; attacker holds the target
txa
sta $3395,y ; target answers to it
rtl
Credit: previously found by Master ZED and Djibriel and fixed by Lenophis, my implementation
#574 An item waiting for Item or Throw, and anything stolen in that battle, is no longer lost when Gau's Leap or Gau joining on the Veldt ends it.
In 1.0
An item waiting to be used with Item or Throw was lost when Gau's Leap, or Gau joining on the Veldt, ended the battle, and so was anything stolen since a command menu last opened.
Now
Both endings now give waiting items back and keep what was stolen.
For ROM hackers 1.0 addresses
BattleEnd_05, C2/48EB, which BattleEnd_02 (C2/48E0, the Leap) falls into. Every other ending runs _c24903 (C2/4903), whose tail from C2/4926 sets all four characters' bits in $3A8C, has _c262c7 (C2/62C7) put each one's held item ($32F4) in the obtained list, merges that list into the bag with UPDATE_INVENTORY and ends with jmp UpdateCharProp. These two skip it, so a waiting Item or Throw and anything stolen were lost. Insert jsr C2/4926 (3 bytes) at C2/48EB, before the jsr LearnRage.
Credit: previously found and fixed by Leet Sketcher, my implementation
#577 A Muddled or Zombie party member's action that waits behind another party member's Quick is now aimed anew when it goes off, instead of where the menu aimed it.
In 1.0
A party member who was Muddled or turned into a Zombie after choosing an action, and whose action waited behind another party member's Quick, still had it go where the menu had aimed it.
Now
The action is now aimed anew when it goes off.
For ROM hackers 1.0 addresses
Same routine as #562. ExecAction runs InitPlayerAction before its hold test at C2/016E ($3AA0 bits 4 and 6, set on everyone but the caster while Quick runs), so a held pass took the $3A4A bit and the resumed pass kept the menu's aim. In MyFix, insert lda $3AA0,x / bit #$50 / bne @clear (7 bytes) before the lda $3018,x / trb $3A4A, so a held pass leaves the bit to the pass that runs.
#578 The Morph time Terra used in a battle ended by Gau's Leap, or by Gau joining on the Veldt, is now taken off her Morph gauge, as other endings do.
In 1.0
If Terra was still Morphed when Gau's Leap, or Gau joining on the Veldt, ended the battle, the Morph time she had used was not saved, and her next Morph lasted as if the battle had never happened.
Now
Both endings now save it.
For ROM hackers 1.0 addresses
Same spot as #574. _c24903 (C2/4903), which every other ending runs, starts with jsr SaveMorphCounter (C2/0B36), writing Terra's remaining Morph supply back. The Leap and END_VELDT endings skip it. Insert jsr SaveMorphCounter (3 bytes) at the head of BattleEnd_05, C2/48EB, before #574's call.
Credit: previously fixed by Leet Sketcher, my implementation
#579 In the Fanatics' Tower, a Gogo with Magic but no Fight among his commands keeps his Magic instead of being left with only Mimic and Item.
In 1.0
The Fanatics' Tower allows only Magic, and a Gogo who had Magic but no Fight among his three commands lost his Magic in every tower battle and was left with Mimic and Item.
Now
He now keeps his own Magic.
For ROM hackers 1.0 addresses
InitCmdList, C2/532C. With the tower's magic-only flag ($3EBB bit 0), the command loop turns Fight into Magitek and, in the block from C2/5382, turns every Magic into None and every Magitek into Magic. A list with no Fight row, which only Gogo can have, lost its Magic. Delete that block (C2/5382 up to the dex at C2/539A) and insert the step below after the loop, before C2/539D; 18 bytes more in all.
lda $3ebb
lsr
bcc @539d ; not magic only
ldx #$03
@scan: lda $fc,x
cmp #$1d ; a Fight row made Magitek?
beq @swap
dex
bpl @scan
bra @539d ; none: keep Magic
@swap: ldx #$03
@next: lda $fc,x
cmp #$02 ; Magic
bne @mtek
lda #$ff ; becomes None
bra @put
@mtek: cmp #$1d
bne @skip
lda #$02 ; Magitek becomes Magic
@put: sta $fc,x
@skip: dex
bpl @next
Credit: previously found by Lapse Pointer, my implementation
#582 Everyone Palidor lifts now comes down after one Jump's wait at their own Speed, instead of the summoner landing late and some party members staying up seconds longer.
In 1.0
Palidor's summoner landed later than the party members it lifted, and a carried party member whose gauge was not full at the lift stayed up much longer than meant.
Now
Everyone Palidor lifts now comes down after one Jump's wait at their own Speed.
For ROM hackers 1.0 addresses
QuetzEffect (C2/0B4A) gives every lifted actor a landing wait of $E0 in $322C. The summoner's own action then ends at ExecAction C2/01C6, whose store at C2/01C8 writes $FF there. And a lifted actor's ATB gauge keeps running: when it fills, the code from C2/11D4 in _c211bb clears the wait counter $3AB5 and stores $FF, before the $3AA0 bit 3 test at C2/11DC. At C2/01C6 insert lda $3018,x / and $3F2C / bne (8 bytes) past the $FF store for an actor still in the air, which there is only the summoner. In _c211bb move that bit 3 test, whose bne goes to C2/11EA, ahead of C2/11D4, and load #$08 again before the jsr SetFlag0 it guarded (2 bytes more).
Credit: previously found by Assassin and fixed by Bropedio, my implementation
#583 A command chosen for a party member during Palidor's animation no longer adds to their wait to land; the command still runs straight after the landing.
In 1.0
A command chosen for a party member while Palidor's animation played was added on top of that party member's wait to land, so they came down later than the other carried party members.
Now
Their wait to land now ends with the other carried party members'.
For ROM hackers 1.0 addresses
QuetzEffect, C2/0B4A. It takes each lifted actor out of the action queue ($3820, $3A66 to $3A67) but not out of the advance wait queue ($3720, $3A64 to $3A65). A command confirmed during Palidor's animation waits there, is taken up after the lift, and QueueAction (C2/031C) adds the landing Jump's delay and the command's to the $E0 wait, capped at $FE. After the action queue loop, before the lda $3205,x at C2/0B72, insert the loop below (22 bytes), which clears the actor's entries as RemoveAllActions (C2/0A0F) does. It tests before reading, since an empty queue has no entry at $3A64. The command stays behind the landing and runs after it.
ldy $3a64 ; advance wait queue start
@loop: cpy $3a65
bcs @done ; end, or empty
txa
cmp $3720,y
bne @next
lda #$ff
sta $3720,y ; take the entry out
@next: iny
bra @loop
@done:
Credit: previously found by Assassin and fixed by Bropedio, my implementation
#584 A raging Gau, a dancing Mog, Umaro, or a Berserk or Muddled party member carried by Palidor keeps the turn earned in the air; its action is queued soon after landing.
In 1.0
A raging Gau, a dancing Mog, Umaro, or a Berserk or Muddled party member carried by Palidor lost the turn earned in the air and stood idle for a whole new gauge.
Now
They now keep the turn; their action is queued soon after landing and runs once the actions ahead of it have played.
For ROM hackers 1.0 addresses
ExecAction clears $3AA0 bit 3 at C2/0183, so the step at C2/01B7 empties the gauge. At a Palidor landing a controllable character's menu sets bit 3 again, but one on Rage, Dance, Berserk or Muddle goes through CancelAction (C2/0986), which leaves it clear, and CheckPlayerAction returns at once for Umaro. Replace the 5 bytes from C2/0183 with the first block (10 bytes more): bit 3 stays when $3205 bit 7 is clear (only QuetzEffect clears it) and $3AA0 bit 7 is set (the gauge filled in the air). Aim Umaro's beq at C2/0928 at CancelAction (same size). In #583's loop, after its sta $3720,y, insert the second block (18 bytes): an entry taken out with only the landing Jump in the command list owes a turn.
lda $3205,x
asl ; carry: not a Palidor landing
lda $3aa0,x
bcs @clear
bmi @keep ; gauge filled in the air
@clear: and #$f7 ; clear bit 3
@keep: and #$df ; clear bit 5
; (the ora #$40 and sta follow)
phy ; in #583's loop
ldy $32cc,x ; head of the command list
lda $3184,y
cmp $32cc,x ; links to itself: only the landing
bne @none
lda #$80
jsr SetFlag0 ; $3aa0 bit 7: turn owed
@none: ply
Credit: previously found and fixed by Bropedio, my implementation
#585 A jumper whose landing another party member's Quick holds back now comes down when Quick ends, instead of flying up for a second whole Jump.
In 1.0
A jumper whose landing came due while another party member's Quick was being cast stayed up through Quick, then went up again for a second whole Jump when it ended.
Now
The jumper now comes down as soon as Quick ends.
For ROM hackers 1.0 addresses
ExecAction, C2/00F9. For a Jump it clears the actor's $3F2C bit with the trb at C2/0147 before its hold test at C2/016E ($3AA0 bits 4 and 6, which another character's Quick sets). A held landing goes back without the bit, and when Quick ends the landing Jump finds it clear and takes off again at _00e4 (C2/00E4). Replace that trb with jsr MyFix (same size). MyFix, 21 bytes of free space in bank C2, entered with 16-bit A holding the actor's mask, does the trb and puts the bit back for a landing that will be held:
MyFix: trb $3f2c
beq @done ; not in the air: a take-off
pha
lda #$0050 ; $3aa0 bits 4 and 6: held
and $3aa0,x
beq @land
lda 1,s
tsb $3f2c ; held: stays in the air
@land: pla ; z clear: land
@done: rts
#587 A Relm controlling an enemy no longer stays unseen in the air for the rest of the fight when her Control ends while Palidor carries her.
In 1.0
When Palidor carried a Relm who controlled an enemy and the Control ended before she landed, she stayed up, unseen and out of reach of every attack, for the rest of the fight, attacking from the air on her own.
Now
She now comes down when her wait runs out and strikes as she lands.
For ROM hackers 1.0 addresses
UpdateSeize, C2/0783, which RemoveControlAttacker (C2/075B) runs for both sides of a Control bond that ends. It marks the side for RemoveAllActions ($3204 bit 6) and DisableATB ($3AA1 bit 6), so a side in the air loses its landing Jump and the wait it runs on, and nothing else lands it. Replace the 15 bytes at C2/0783 with jsl MyFix / bra ClearFlag0 (C2/0792); MyFix needs about 100 bytes of free space (8-bit A and index). For a side in $3F2C, or with $3204 bit 0 (Palidor's lift is due), MyFix keeps the landing and removes what follows it as RemoveAction (C2/0301) does. With #186 applied, ValidateControl (C2/07AD) must also ignore Hide for such a side.
jsl MyFix ; at C2/0783, then bra ClearFlag0
MyFix: lda $3018,x ; a character in the air,
and $3f2c
bne @air
lda $3019,x ; a monster in the air,
and $3f2d
bne @air
lda $3204,x
and #$01 ; or Palidor's lift is due
bne @air
... ; UpdateSeize's own two marks
lda #$7f ; ClearFlag0 clears $3aa0 bit 7
rtl
@air: ... ; unlink the landing ($32cc,x), remove
... ; the rest as RemoveAction does, relink it
lda #$ff ; ClearFlag0 changes nothing
rtl
Credit: previously found by 윈드크로우 and Lapse Pointer, my implementation
#588 With the Gauntlet, a party member at level 94 or higher with high Vigor and a strong two-handed weapon no longer hits far weaker than without it.
In 1.0
With the Gauntlet, a party member at level 94 or higher with high Vigor and Illumina, Ragnarok or the Imp Halberd held in both hands hit far weaker than without it.
Now
The Gauntlet's extra battle power now holds at every level.
For ROM hackers 1.0 addresses
CalcDmg (C2/2B9D), physical path. The first Mult24 (C2/47B7) leaves attack x level in $E8-$EA, 24 bits. The code then stores the low byte in $EA and the level in $E8 and feeds only the low 16 bits to the second Mult24, so a product above 65,535 loses 65,536 x level / 256 of the level term. After the sta $E8 at C2/2BDF, A is the level and B still the top byte. Replace the 11 bytes from C2/2BE1 to C2/2BEB (longa to sta $11B0) with jsr MyFix and 8 bytes of padding (bra over 6 nop); MyFix needs 21 bytes of free space in bank C2.
MyFix: jsr MultAB ; level x top byte
xba ; x 256
longa
pha
lda $e9 ; 1.0's second multiply
xba
jsr Mult24
clc
adc 1,s ; add back the part it dropped
sta $11b0
pla
rts ; A stays 16-bit, as after C2/2BE1
Credit: previously fixed by Leet Sketcher, my implementation
#593 Mute now stops Strago's Reflect??? like every other Lore, even when he chose it before Mute landed.
In 1.0
If Strago chose Reflect??? before Mute landed on him, he still cast it while Muted, and a Muted Gogo could Mimic it.
Now
Mute stops Reflect??? like every other Lore.
For ROM hackers 1.0 addresses
ExecAttack (C2/317B). The Mute and Imp check at C2/3201 runs only when the attack's MP cost $11A5 is above 0, otherwise it branches to C2/3225, so Reflect???, a Lore that costs nothing, is cast by a caster Muted after choosing it. Replace the lda $11A5 at C2/3201 with lda $11A3, and #$04 (the record's Lore flag), ora $11A5: insert, 5 bytes more. The flag is on the 24 Lores and nothing else, and Reflect??? is the only one with no cost, so only it changes, for monsters too.
lda $11a3 ; C2/3201
and #$04 ; a Lore
ora $11a5 ; or an MP cost
beq @3225 ; neither: no Mute or Imp check
Credit: previously found by Lenophis, my implementation
#595 With the Genji Glove, a Tempest Wind Slash after another weapon's random spell in the same Fight hits every enemy again, not just one.
In 1.0
With the Merit Award and the Genji Glove, a party member holding a weapon that casts a spell at random (the Blizzard, say) beside the Tempest: when that weapon's spell went off first in a Fight, the Tempest's Wind Slash struck a single enemy.
Now
The Wind Slash strikes every enemy, as it does on its own.
For ROM hackers 1.0 addresses
A weapon's random spell clears $B2 bit 4 (trb at C2/3660, in CheckWeaponSpellCast), which holds the follow-up spell to the struck target. _c237eb (C2/37EB) sets up every follow-up spell and reads the bit with bit $B2 at C2/380D, but nothing sets it again before the action's next strike, so a later Wind Slash (the Tempest's attacker effect $0B) is held to one target too. Change that bit $B2 (24 B2) to tsb $B2 (04 B2): one byte, the same Z flag for the branch after it, and the bit is set once read.
Credit: previously found and fixed by Assassin, my implementation
#611 With a Genji Glove, a Blood Sword strike on an undead enemy the other hand has just killed no longer drains its wielder.
In 1.0
With a Genji Glove, a Blood Sword strike that landed on an undead enemy the other hand had just killed drained its wielder, which could kill him.
Now
A dead target gives and takes nothing.
For ROM hackers 1.0 addresses
CalcMaxDmg, drain branch at C2/0C5D. A drain on a dead target still runs FixDrainDmg, and with an undead corpse at 0 HP the reversed drain became the target's whole max HP, taken from the attacker. Insert lda $3EE4,y and bmi to C2/0C75 (5 bytes, 8-bit accumulator) before the jsr at C2/0C64, so a target with Wound (status 1 bit 7) gives and takes nothing.
lda $3EE4,y ; status 1
bmi NoDrain ; Wound; NoDrain: the clc at C2/0C75
#612 An enemy under Relm's Control is set free when she runs from the battle, instead of standing still for the rest of the fight.
In 1.0
When Relm ran from the battle, the enemy under her Control stood still for the rest of the fight.
Now
The enemy is set free when she runs.
For ROM hackers 1.0 addresses
_c207c8, C2/07C8, clears the Zinger, Love Token and Charm bonds of an actor that leaves; _timeenable2, C2/144F, calls it for every actor that leaves the fight. Control bonds ($32B8 and $32B9, bit 7 set when broken) are broken only by ValidateControl, C2/07AD, when it finds one of its statuses, and a controller who runs has none, so the controlled monster waits for good. Insert jsr C2/07B9 (3 bytes), the bond-breaking half of ValidateControl, at C2/082C ahead of the rts. Novalia Spirit's Remote Control Fix instead calls ValidateControl from _timeenable2, also 3 bytes; either form works.
Credit: previously found and fixed by Novalia Spirit, my implementation
#617 A party member who is an Imp no longer wastes turns on spells an Imp cannot cast when the game picks the action, as in the Colosseum or under Muddle or Charm.
In 1.0
When the game chose a party member's action, as in the Colosseum or under Muddle or Charm, an Imp could pick a spell an Imp cannot cast, and the turn did nothing.
Now
An Imp casts Imp if the party member knows it, or the turn goes to another command.
For ROM hackers 1.0 addresses
RandMagic, C2/051A, picks the automatic Magic and X-Magic of the Colosseum, Muddle and Charm, and X-Magic's second spell: a random known spell from the caster's list. It never reads the list's Disabled marks, which CheckMagicEnabled (C2/57AA) leaves clear only on Imp for an Imp, so the cast gate in ExecAttack turns any other pick into nothing. Insert the test below (10 bytes) after the bne at C2/051F; MyFix needs 31 bytes of free space in bank C2. $3084 + $23 gives the Imp spell's entry in the list ($302C points at it, 4 bytes an entry). Imp is cast if that entry holds it; otherwise RandCmdInvalid (C2/054F) strikes the command, as an empty list does.
; after the bne at C2/051F (.a8):
lda $3EE4,y ; status 1
bit #$20 ; Imp
beq :+
jmp MyFix
:
MyFix: phy
rep #$20
lda $302C,y ; the caster's spell list
sta $EE
sep #$20
tdc
lda $30A7 ; $3084 + $23, Imp's entry
bmi Fail ; $FF: not in the list
asl2
tay
lda ($EE),y
ply
cmp #$23 ; Imp
bne Strike
rts
Fail: ply
Strike: jmp RandCmdInvalid
Credit: previously found by Lapse Pointer, my implementation
#618 A Fenix Down or another revive no longer brings back an enemy that has just hidden, like the last Piranha, or been Possessed, which left a battle that could not be won.
In 1.0
A revive such as a Fenix Down that landed on an enemy just after it hid, like the last Piranha at the Barren Falls, or was Possessed on the Phantom Train, brought it back hidden: it could not be hit, and the battle could neither be won nor run from.
Now
A revive goes to another enemy, and the hidden one stays down.
For ROM hackers 1.0 addresses
ChooseTarget, C2/587E. A revive ($BA bit 3) skips CheckTargetsPresent (C2/5A4D), so it keeps a monster that left by Hide alone: the Piranha's hide AI effect and Possess (TargetEffect_50, C2/3B98) set Hide but leave $3AA0 bit 0 set and the monster out of $2F4C. The revive at C2/469C then brings it back hidden, counted in $3A77 and out of reach. Replace the lda $B8 / ora $B9 at C2/58A5 (4 bytes) with a jsl to the routine below (37 bytes of free space in any bank). Same hook as #490: merge the two and keep its re-aim at the living monsters for an aim left empty. Assassin's plan is another way: put the hidden or Possessed monster in $2F4C, as Possess does for the ghost.
; C2/58A5 (.a8 .i8), in place of lda $B8 / ora $B9:
jsl MyFix
MyFix: lda $BA
and #$08 ; a revive
beq Done
lda $B8
bne Done ; a character is aimed at
phx
ldx #$12
Loop: lda $3EF9,x ; status 4
and #$20 ; Hide
beq Next
lda $3019,x ; the monster's bit
trb $B9
Next: dex
dex
cpx #$08
bcs Loop
plx
Done: lda $B8
ora $B9
rtl
Credit: previously found by Assassin, my implementation
#620 A fighter who is both Muddled and Charmed now attacks its own side, as with either status alone, instead of fighting as if unaffected.
In 1.0
A fighter who was both Muddled and Charmed fought as if it had neither: a party member attacked the enemies, and an enemy attacked the party.
Now
A fighter with both attacks its own side, as with either status alone, until both have ended.
For ROM hackers 1.0 addresses
Retarget, C2/5937, picks the side of an automatic action as bit 7 of $B8 and turns it (EOR #$80) once for each cause. C2/5945 turns it for a Charm attacker in $3395,x, and C2/597E turns it again for Muddle ($3EE5 bit 5), so a fighter with both turns twice and acts as if it had neither. Make the Charm turn skip a Muddled fighter, whose Muddle turn follows: replace the 11 bytes at C2/5945 with a jsl to the routine below (19 bytes of free space in any bank) and 7 nops. Each status still ends as before.
; C2/5945 (.a8), in place of lda $3395,x / bmi / lda #$80 / eor $B8 / sta $B8:
jsl MyFix
nop ; 7 of them
MyFix: lda $3EE5,x ; status 2
and #$20 ; Muddle: its own turn follows at C2/597E
bne Done
lda $3395,x ; Charm attacker, $FF for none
bmi Done
lda #$80
eor $B8
sta $B8
Done: rtl
The rainbow colour bands in the Bserk spell's animation stayed at full strength from start to finish instead of building up and fading out.
Now
The bands now build up and fade out in steps.
For ROM hackers 1.0 addresses
Bserk (sprite) animation script, D0/17D5. Each ramp step sets a gradient level and calls _d01823 (D0/1823), whose 8-frame loop runs update_rainbow_gradient 0 every frame, so the gradient stays at full strength. Rewrite the block from D0/17EF to the return at D0/182A in place: three subroutines, each loop 8 of update_rainbow_gradient N and blank_frame (N = 11, 7, 3), called in that order before the 65-frame hold at 0 and in reverse after it, then reset_gradient and end_anim_script. It is 8 bytes shorter. This is the disassembly author's own alternative block for the script.
Credit: previously found by everything8215, my implementation
#16 Aero, Quasar, Flare Star and Blizzard Fist now scroll in the direction their animations name; every release, the GBA included, slid them one way.
In 1.0
The lines that slide sideways across the screen in the animations of Aero, Quasar, Flare Star and Blizzard Fist always slid the same way, ignoring the left or right direction each animation names; every release of the game does this, the GBA included.
Now
Each now slides its lines in the direction it names.
For ROM hackers 1.0 addresses
magic_init_80 (C1/CE9A), animation command $80/$50, random BG3 line scrolling for Aero, Quasar, Flare Star and Blizzard Fist. It sets Y = 1 to read the direction parameter, but the lda at C1/CE9F reads [zAnimScriptPtr] without the index, so it gets the sub-command byte, never zero, and the lines always scroll one way. Change it to lda [zAnimScriptPtr],y (opcode $A7 to $B7, same size).
Credit: previously found by everything8215, my implementation
#17 Three animation script mistakes fixed: a missing loop end, Hope Song's colour math, and the Magitek fight loop.
In 1.0
Three battle animation scripts had slips: the closing flash of the esper in Maduin's Chaos Wing and Starlet's Group Hug did not repeat as written, Siren's Hope Song cut its rainbow colours before they faded out, and Magitek armor lost its action one frame into a step forward.
Now
The esper's flash now repeats as written, Hope Song's rainbow fades out, and the armor keeps its action through the whole step forward.
For ROM hackers 1.0 addresses
Three animation script edits in bank D0. (1) _d04cc2 (D0/4CC2), called from Chaos Wing (sprite) and Group Hug (sprite): its loop 16 has no end_loop before the return at D0/4CCA. Insert end_loop (1 byte); _d04ccb follows at once, so move _d04cc2 to free space in bank D0 and repoint the calls at D0/4B79 and D0/4CBE. (2) Hope Song (bg3), D0/5045: disable_monster_color_math (2 bytes) at D0/50C3 runs before the ramp-down, so the gradient never shows. Move it to just before reset_gradient at D0/50EF. (3) _d07135 (D0/7135), Magitek fight: magitek_action 0 at D0/713E sits inside the loop; swap it with the end_loop at D0/7142.
Credit: previously found by everything8215, my implementation
#22 Battle setup now clears its whole buffer, and a clear meant for the damage numbers no longer writes into the fourth party member's sprite.
In 1.0
At the start of a battle, the list of items gained during the fight was emptied except for its last three places, and when damage numbers appeared on several targets at once, a clear meant for them wrote into the fourth party member's sprite instead.
Now
The whole list is now emptied at the start of every battle, and that clear leaves the party member's sprite alone.
For ROM hackers 1.0 addresses
Two buffer fills in the battle graphics code. user_init (C1/102A) sets the 80-byte buffer w7e602d ($7E602D) to $FF, but the cpx at C1/12D5 stops at $40: change the immediate to $0050, in place. magic_tmp_buf_clr (C1/A487) sets the data bank to $7F for its first loop, and the second loop at C1/A49C, which clears w7e7b3f and three neighbouring arrays in bank $7E, still runs with bank $7F, so it clears from $7F7B3F, inside the fourth character's sprite graphics, instead. Insert pea $7E7E / plb / plb (5 bytes) before the clr_ax at C1/A49A.
Credit: previously found by everything8215, my implementation
#97 Party members are now drawn right while fleeing: a Morphed Terra runs, a Frozen party member stays still, and a jumper keeps the Jump poses.
In 1.0
Holding L and R to run away draws the party in the running pose, but a Morphed Terra stood still, a Frozen party member ran on the spot inside the ice, and a party member leaping into a Jump was drawn running in mid-air.
Now
A Morphed Terra now runs, a Frozen party member stays still, and a jumping party member keeps the Jump poses.
For ROM hackers 1.0 addresses
DrawCharSprite, C1/34DB. While the run flag $2F45 is set, the block from C1/3516 forces pose $0C unless the character is held still; its last test, the and at C1/353F, asks status 4 Morph ($08) where Freeze ($02) belongs. Change that operand from $08 to $02. For the Jump: insert (7 bytes) after the bne at C1/3525, which ends the acting-as-enemy test, a test of $3F2C, the mask of characters jumping or seized, that branches to the normal pose path at C1/3549.
lda $3f2c ; characters jumping or seized
and $36 ; this character's bit
bne @3549 ; no run pose
Credit: previously found and fixed by Leet Sketcher, my implementation
#98 Crusader's Purifier animation no longer overwrites the Reflect barrier's colours for the rest of the battle.
In 1.0
Crusader's Purifier animation overwrote the colours the Reflect barrier shares with the status icons and the target cursor, so for the rest of that battle the barrier was drawn in the wrong colours, and so were the icons and the cursor.
Now
Those colours are now put back as soon as the animation ends.
For ROM hackers 1.0 addresses
DeinitAnimType, C1/B085, the end of every battle animation. Animation command $80 $67 (AnimCmd_00_67_far, C2/BBCF, used by Crusader's Purifier) loads 16 colours into sprite palette 15, both copies ($7FE0 and $7DE0). Colours 12 to 15 are the status icon and cursor colours that LoadStatusPal (C1/3E4D) sets once at battle start, and nothing puts them back. Replace the code at C1/B0F3 (6 bytes) with jsl MyFix / rts / nop; MyFix copies the 8 bytes at C2/C6A1 to $7FF8 and $7DF8, then runs the replaced lda #$17 / sta $898D. 27 bytes of free space, reached with jsl; A 8-bit, X 16-bit.
jsl MyFix ; at C1/B0F3
rts
nop
MyFix: phx
ldx #0
@loop: lda f:_c2c689+24,x ; the status colours
sta $7ff8,x ; palette 15, colours 12 to 15
sta $7df8,x ; and its other copy
inx
cpx #8
bne @loop
plx
lda #$17
sta $898d ; main screen designation
rtl
Credit: previously found by Master ZED and fixed by Lenophis, my implementation
#100 Two tiles of the Sealed Gate bridge no longer use the wrong colour sub-palette.
In 1.0
Two small tiles of the bridge in the Sealed Gate battle background were coloured with the dark green and blue of the clouds instead of the bridge's own colours.
Now
Both tiles now use the bridge's colours.
For ROM hackers 1.0 addresses
Battle background graphics SEALED_GATE_2 (LZSS at E8/B1F3, pointer in BattleBGGfxPtrs E7/1650). Decompress. Tile 6 (bytes $C0 to $DF), drawn in the bridge's two wrong tiles, has bitplane 3 clear in some or all pixels of rows 0 to 5, so those pixels take colours 0 to 7, the cloud's half of the palette. Set bitplane 3 on those rows (bytes $D1, $D3, $D5, $D7, $D9, $DB become $FF) and re-index the pixels that then land on the wrong shade: bytes $C0, $C6, $D0 and $D6 change. Recompress; if it grows, move it to free space and repoint.
Credit: previously found and fixed by Gi Nattak, my implementation
#102 Carbuncle's sprite no longer has part of its aura missing in the lower right.
In 1.0
Part of the glow around Carbuncle's sprite was missing at the lower right.
Now
The glow now closes around that corner.
For ROM hackers 1.0 addresses
Monster graphics CARBUNKL (4bpp, at EC/AA68), an 8 by 8 tile block whose stencil stores 19 tiles: rows 0 to 3, columns 0 to 3, and row 4, columns 0 to 2. The glow is cut where the art meets the unstored tile at row 4, column 3. Drawn anew from the 1.0 art: move the whole sprite up two pixel rows (the top two are empty), then close the glow with five pixels (row, column): (30, 24) = 5, (30, 25) = 4, (31, 23) = 5, (31, 24) = 4, (32, 23) = 4. The stencil and size stay the same. To keep Carbuncle where 1.0 puts it, add 2 to the y of move_xy at D0/4ED5, D0/4EDC and D0/4EE3 (the summon, CARBUNKL_SPRITE) and at D0/BCA1 and D0/C4D1 (its flights in battle events 21 and 24).
Credit: previously found and fixed by DrakeyC, my implementation
#104 A Tyranosaur in the Colosseum no longer steps back while the animation of Haste, Slow and a few other attacks plays on it.
In 1.0
Casting Haste or Slow on a Tyranosaur at the Colosseum made its sprite jump a few pixels backwards until the spell's animation ended.
Now
The Tyranosaur now stays put when Haste or Slow is cast.
For ROM hackers 1.0 addresses
InitMonsterPos, C1/1588. For battle $023F, the Colosseum, the opponent's left x is $18 + ($80 - width * 8) / 2, stored in $80C3. The opponent is drawn from 16-pixel sprites laid on an 8-pixel grid, and an odd tile width (the Tyranosaur is 7) gives an x 4 pixels off it. An animation that copies the monsters onto BG1 (init $1D, magic_type1d: Haste, Slow, Berserk and a few more) draws that copy at $80C3 / 8 tiles (MonstersToBG1, C1/1E63), so while it plays the opponent sits 4 pixels to the left. Insert and #$F8 (2 bytes) after the adc #$18 at C1/15FD, before the tax at C1/15FF. An x already on the grid is unchanged.
Credit: previously found and fixed by Leet Sketcher, my implementation
#105 The Dice weapons are now drawn with the correct pips on their side faces.
In 1.0
Four of the five dice sprites for Dice and Fixed Dice showed two opposite faces of a die side by side, which no real die has.
Now
The side faces are now redrawn as a real die.
For ROM hackers 1.0 addresses
Attack graphics, AttackGfx3bpp (D3/0000, 3bpp, uncompressed). The dice are 16 by 16 sprites: top-left tiles 835, 837, 839, 841 and 843 (from D3/4E48), bottom halves at tile + 16. Opposite faces must sum to seven, and this die is the Japanese kind, 1, 2, 3 clockwise round a corner. Each sprite keeps its top face; the side faces are redrawn as (top, left, right): 835 (3, 2, 1), 837 (1, 2, 4), 841 (3, 2, 1), 843 (4, 2, 6); 839 is right. Pips are single pixels; on a cube's right face they sit on rows and columns 8, 10 and 12 of the 16 by 16 sprite. A 1 is one pip in colour 2 at the centre. 16 bytes change, between D3/4E6A and D3/50B2.
Credit: previously found by mblock129, my implementation
#107 Casting Quasar before W Wind or Spiraler no longer corrupts the shared tornado animation.
In 1.0
Once Quasar had been used in a battle, a later W Wind or Spiraler in the same battle drew its tornado as a wavy, striped pattern.
Now
The tornado now draws correctly.
For ROM hackers 1.0 addresses
Attack animation script $01A0, Quasar's BG1 part (D0/31AB). It ends with set_scroll_hdma BG1, 3 at D0/3200 without first resetting the BG1 scroll HDMA it used, and the tornado script that W Wind and Spiraler share inherits that state and draws striped. Insert reset_scroll_hdma BG1 (command $80 $25, 2 bytes) before that set_scroll_hdma.
Credit: previously found and fixed by Leet Sketcher, my implementation
#108 Edgar now holds the Flash tool out when he uses it, and its flash is drawn on the tool.
In 1.0
Edgar stayed in his ready pose when he used the Flash tool, and the flash was drawn away from the tool in his hand.
Now
Edgar now holds the tool out, and the flash is drawn on it.
For ROM hackers 1.0 addresses
Attack animation script $0052, the Flash tool's sprite (D0/783B). It stands Edgar in the READY pose and puts the flash 16 up and 16 forward, away from the tool in his hand. Change attacker_frame READY at D0/7847 to FIGHTING_1; change move UP_FORWARD, 16 at D0/784A to move FORWARD, 8 and follow it with move UP, 10 (2 bytes); delete mod_pal MONSTER, ADD, YELLOW, 0 at D0/7852, a tint of zero that does nothing, so the script keeps its size and its frame count. The 6 bytes from D0/784C shift down by 2.
Credit: previously fixed by Gi Nattak and Khaos, my implementation
#109 Tritoch's tail no longer loses a piece during its summon animation.
In 1.0
A piece was missing from the end of Tritoch's tail during its summon animation.
Now
The tail is now drawn whole.
For ROM hackers 1.0 addresses
Battle animation frame data, AttackAnimFrames (D1/0141), 2 bytes per tile: position (x in the high nibble, y in the low, in tiles), then tile number. The frame starting at D1/C645 (frame 2393 in AttackAnimFramesPtrs, D4/DF3C), used by Tritoch's summon, has no entry for the tail's last piece. Change the entry at D1/C649 to $35 $07: tile $07 at x 3, y 5. The entry it replaces put tile $02 at x 2, y 0.
Credit: previously found by bydoless, my implementation
#126 Thirteen attack animations, Blow Fish and Fire Ball among them, no longer turn a right-facing party member away from the enemy when hit.
In 1.0
Thirteen attack animations, Blow Fish and Fire Ball among them, turned a right-facing party member, as in a back attack, round to face away from the enemy when hit.
Now
In a back attack the party member now flinches towards the enemy.
For ROM hackers 1.0 addresses
Attack animation scripts, bank D0. The target_frame command ($82, frame when facing left, frame when facing right) is written HIT, HIT_FLIP in fourteen places, and the extra flip turns a right-facing target away when hit. Change the command's third byte from $46 to $16 (HIT for both) at D0/160E, D0/2A51, D0/2A62, D0/2CC2, D0/3339, D0/336B, D0/3454, D0/34DB, D0/3550, D0/3564, D0/35C6, D0/3642, D0/3B21 and D0/3B94 (the addresses are the $82 byte). Same size.
Credit: previously found and fixed by Leet Sketcher, my implementation
#127 Imp status from Rippler or Sour Mouth now correctly updates the enemy's sprite.
In 1.0
An enemy turned into an Imp by Rippler or Sour Mouth kept its normal look until it had taken a turn or two, while the Imp spell changed it at once.
Now
The enemy is now drawn as an Imp when the attack's animation ends.
For ROM hackers 1.0 addresses
Attack animation scripts _d033cf (Rippler, D0/33CF) and _d078c8 (D0/78C8, the BG3 script Sour Mouth shares). Both can hand out Imp, but neither runs the target_imp command ($80 $13), so the monster's sprite never changes. Insert target_imp (2 bytes) before the end_anim_script at D0/3435 and at D0/790E. Each script is followed at once by the next, so it moves to free space in bank D0 and its pointer is changed. #385 later makes target_imp follow the status the battle settled.
Credit: previously found and fixed by Leet Sketcher, my implementation
#184 An instant death weapon hitting a bodyguard now shows the red X instead of the green Reflect barrier.
In 1.0
When a party member wearing a True Knight stepped in to shield another and was killed by an instant-death weapon such as the Scimitar, the green Reflect barrier played over the guard instead of the red X.
Now
The guard's death now shows the red X with no barrier.
For ROM hackers 1.0 addresses
ScimitarEffect (C2/3890), the weapon instant-death effect, ends by queueing the death animation and jumping to CopyGfxParamsToBuf at C2/38EF. The word $A6 is a cover mask to the blow's animation block and a reflect mark to a magic one, and the block copied at the end of the attack is the death's, so a bodyguard's cover mask shows as the reflect barrier. Change that jmp to jsr (in place) and insert rep #$20 / stz $A6 / sep #$20 / rts after it (7 bytes).
Credit: previously found and fixed by Assassin, my implementation
#198 The animation of a spell bounced from an enemy onto a party member is no longer drawn mirrored, the way the enemies face.
In 1.0
When a spell bounced off an enemy's Reflect onto a party member, the spell's animation over the party member hit by the rebound was drawn mirrored, the way the enemies face.
Now
The animation over the party member hit by the rebound is no longer mirrored.
For ROM hackers 1.0 addresses
GetTargetNum, C1/BCA6. It names the monster target whenever wAnimMonsterTargets is non-zero, and on a reflected leg that mask still holds the leg already drawn, so the animation over a party member hit by the rebound is drawn mirrored by the monster's facing. $62D1 is non-zero while a rebound, Super Ball or launcher leg is drawn. Insert at the start of GetTargetNum: lda $62D1 / bne to the character branch at C1/BCB6 (5 bytes).
GetTargetNum:
lda $62d1 ; drawing a rebound leg?
bne @bcb6 ; then name the characters it reached
Credit: previously found and fixed by Leet Sketcher, my implementation
#199 The countdown shown in battle no longer skips a second about once a minute; the time left was always counted correctly.
In 1.0
The countdown shown in battle ticked a little slower than once a second, so about once a minute the display skipped a second.
Now
The countdown in battle now ticks exactly once a second.
For ROM hackers 1.0 addresses
DrawTimer, C2/B925. The frame counter $628F is reloaded with 60 when it reaches zero, and the frame that finds it zero is one more, so the display ticks every 61 frames and skips a second about once a minute. Change the immediate of the lda at C2/B936 from 60 to 59 (1 byte). The timer itself was always counted correctly.
Credit: previously found and fixed by Assassin, my implementation
#201 A Muddled or Controlled enemy no longer flickers every frame while an effect such as Rippler draws it upside down.
In 1.0
Some effects, Rippler most visibly, draw an enemy upside down. An enemy turned round by Muddle or Control flickered every frame for as long as the effect lasted.
Now
The enemy is now drawn steadily for the whole effect.
For ROM hackers 1.0 addresses
UpdateMonsterSprites (C1/27C2), the pass that draws a monster flipped vertically for effects such as Rippler. It saves $80F3,x (graphics flip) xored with $617E,x (the facing flip of a monster that Muddle or Control has turned round), sets the vertical bit, draws, and writes the saved value back, so a turned-round monster has its flip inverted every frame. Remove both eor $617E,x (3 bytes each, nop them) at C1/282F and C1/2837. Animation command $D5, magic_code55 (C1/E707), makes the same mistake: remove its eor $617E,y at C1/E71B.
Credit: previously found and fixed by Leet Sketcher, my implementation
#202 Absorbing Vanish with Runic no longer makes the caster visibly disappear and reappear.
In 1.0
When Celes or Gogo absorbed a Vanish spell with Runic, the spell never took hold, but the party member visibly vanished and came straight back.
Now
A party member who absorbs Vanish now stays in view throughout.
For ROM hackers 1.0 addresses
Animation command $80/$14, magic_init_20 (C1/D6BD). It runs as part of Vanish's animation whether the spell landed or not, sets the vanish bit in the character's sprite and starts the fade, so an absorbed Vanish still blinks the character out. Make the sprite match the status the battle has settled instead: replace everything from the asl at C1/D6C9 up to the rts at C1/D6E4 (27 bytes) with the 24 bytes below and three nop. No fade is started here.
asl ; A = character slot ($10)
tay ; y = slot * 2
asl4
tax ; x = slot * 32
lda $3ee4,y ; status 1 the battle settled
eor wCharGfxDataBuf::ActiveStatus1,x
and #$10 ; vanish
eor wCharGfxDataBuf::ActiveStatus1,x
sta wCharGfxDataBuf::ActiveStatus1,x
ldx wAnimThreadPtr
Credit: previously found and fixed by Leet Sketcher, my implementation
#205 A shield no longer plays its block twice when a Fenix Down is used on a living wearer, and a blocked three-BAR Phoenix no longer leaves Setzer or Gogo standing forward.
In 1.0
Using a Fenix Down on a living party member who carried a shield that shows a block animation raised the shield twice, and a shield that blocked a three-BAR Phoenix, Setzer's own or Gogo's, left the caster standing forward for the rest of the battle.
Now
The shield is now raised once, and the caster steps back into line.
For ROM hackers 1.0 addresses
The step forward that Item, Slot, Health and a command with no target play first: ldx of the STEP_FORWARD animation then jsr ExecSimpleAnim, at C1/B903, C1/BB6A, C1/BC03 and C1/BC48. InitAnimThreads starts the shield's block thread unless $62C0 is set; the swirl before a spell sets it, this step does not, so a blocked item shows the block twice. Each other target also got a step thread that moved the attacker, so a blocked three-BAR Phoenix left Setzer forward. Replace each ldx/jsr pair (6 bytes) with jsr MyFix and three nop; MyFix (13 bytes of free space in bank C1) plays the step with $62C0 set.
Credit: previously found and fixed by Leet Sketcher, my implementation
#255 Edgar now wears his Gerad disguise consistently through the Tentacles fight in Figaro's engine room, instead of switching to king's robes.
In 1.0
In the Tentacles fight in Figaro Castle's engine room, Edgar appeared in his usual king's colours, blond hair included, though he is disguised as Gerad before and after it.
Now
For that fight Edgar now keeps Gerad's grey hair and dark coat.
For ROM hackers 1.0 addresses
LoadCharGfx's palette choice, C1/3E06. The battle palette comes from CharPalTbl by character graphic, so Edgar, dressed as Gerad from Nikeah to Figaro's engine room, turns back to his royal palette for the one fight of that stretch, the engine room Tentacles (formation 84). Insert before the lda $2EAE,x at C1/3E06 (24 bytes): when $11E0 is 84 and $11E1 is 0, and the character at $2EC6,x is Edgar (4), pull X, load 1 (the merchant palette) and branch to C1/3E24, as the Locke soldier test just below does.
lda $11e1
bne @plain
lda $11e0 ; formation
cmp #84
bne @plain
lda $2ec6,x ; character
cmp #$04 ; edgar
bne @plain
plx
lda #$01 ; merchant palette
bra @3e24
@plain: ; the lda $2eae,x at C1/3E06 follows
Credit: previously found and fixed by Leet Sketcher, my implementation
#280 Doom's reaper no longer carries off only the top half of a Magitek Armor rider, and Vanish no longer leaves their legs outside the armor.
In 1.0
The game kept no sprite of a Magitek Armor rider's legs, so Doom's reaper carried off only the top half of a rider, and Vanish drew a rider's legs outside the armor for the rest of the battle.
Now
The reaper now carries off the whole rider, and a vanished rider shows no legs outside the armor.
For ROM hackers 1.0 addresses
A Magitek rider ($64BA set) is drawn with no leg row. In _c136e2 (C1/36E2), where every sprite rebuild ends, replace and #%11 / tax / lda $202A,x at C1/36F9 (6 bytes) with jsl and two nop; the routine blanks the slot's leg row in bank $7F (table at C2/E41A, plus $0240, $40 bytes) when $64BA is set, then runs those three. In LoadAnimProp, the lda #$2E at C1/9D39 becomes a jsl (insert: 4 bytes in place of 2): for BG1 script $003C ($6275, Doom's soul) on a rider ($613D), copy $7FA000 to $7FA3FF to a buffer, restore each slot's leg row from its sheet (table at C2/E422, pose $61C4,x), send it to VRAM $0000 with WaitTfrVRAM_far (C1/1989), return $2C. About 190 bytes of free space.
Credit: previously found and fixed by Leet Sketcher, my implementation
#286 The stray line of pixels along the top of the Fanatics' Tower battle background is now covered by the wall tiles.
In 1.0
A line of stray pixels ran along the top of the Fanatics' Tower battle background.
Now
The line is now covered by the wall tiles.
For ROM hackers 1.0 addresses
Battle background tilemap FANATICS_TOWER, LZSS at E7/36AB, pointer in BattleBGTilesPtrs (E7/1848). Decompressed it is 32 by 32 tile words (2,048 bytes). Row 0 names tiles this background never loads, which draws the line of stray pixels. Copy row 1 (bytes $40 to $7F) over row 0 (bytes $00 to $3F); no other row changes. Recompress; if it grows, move it to free space and repoint.
Credit: previously fixed by DrakeyC, my implementation
#384 Setzer no longer lands 24 pixels behind his mark and stays stuck there after three BARs on the Slot summon Palidor.
In 1.0
When Setzer's Slot landed three BARs and summoned Palidor, he came down behind his place in the line and stayed there for the rest of the battle.
Now
He now lands back in his own place.
For ROM hackers 1.0 addresses
Slot handler (graphics command for Slot). It runs MagicCmdAnim, then _c1b86b (C1/B86B), which asks for the step back by incrementing $61AE plus the character. Palidor's animation cancels a pending step back with command $80/$4A (C1/CF6A, clears $61AE to $61B1), but for the Slot the request comes after the animation, so Setzer steps back in the air and lands 24 pixels behind. For an esper result (attack $36 to $50) ask before the animation. Change the jsr MagicCmdAnim at C1/B90D to jsr MyFix and the jsr _c1b86b at C1/B913 to jmp PopMonsterPalID. MyFix needs 25 bytes of free space in bank C1.
MyFix: ldy #2
lda ($76),y ; attack number
cmp #$36
bcc @late
cmp #$51
bcs @late
jsr _c1b86b ; esper: step back asked for first
jmp MagicCmdAnim
@late: jsr MagicCmdAnim
jmp _c1b86b
Credit: previously found and fixed by Leet Sketcher, my implementation
#385 Imp no longer disfigures an immune enemy's sprite, and Celes absorbing Imp with Runic no longer flickers into an Imp and back.
In 1.0
The Imp spell flipped its target's sprite between normal and Imp on every hit, so a Vanished enemy immune to Imp was drawn as an Imp without being one, and Celes absorbing Imp with Runic flashed into an Imp and straight back.
Now
A sprite now changes only when the Imp status really changes, so an immune enemy keeps its look and Celes stays herself.
For ROM hackers 1.0 addresses
magic_init_19 (C1/D6E5), animation command $80/$13, the Imp spell's sprite change. It toggles the sprite whenever the spell hits: for a character bit 5 of $2EBD+32*slot at C1/D6F1, for a monster $62C2+slot at C1/D70D. Make both follow the status the battle has settled. Character: replace the 14 bytes at C1/D6F1 with a jsl to MatchLook (30 bytes of free space, any bank), which copies bit 5 of $3EE4+2*slot into the sprite and returns zero when they already agreed; on zero, reload X from wAnimThreadPtr and return without the status change animation. Monster: replace the code from C1/D70D with phx, jsr SyncMonsterGfx, plx, rts, the routine of #386.
lda #$20 ; imp, at C1/D6F1
jsl MatchLook
bne @go
ldx wAnimThreadPtr
rts
@go: nop2 ; on to lda $10 as before
MatchLook:
pha
lda $10 ; character slot
asl
tay
asl4
tax
lda $3EE4,y ; settled status 1
eor $2EBD,x ; status the picture shows
and $01,s
sta $01,s
beq @done
eor $2EBD,x
sta $2EBD,x
@done: pla
rtl
Credit: previously found by Master ZED and fixed by Leet Sketcher, my implementation
#386 Imp given by anything other than the Imp spell, Rippler or Sour Mouth now redraws the enemy as an Imp too.
In 1.0
Only the Imp spell redrew an enemy as an Imp; any other way of giving the status left its normal look (Rippler and Sour Mouth have their own fix, entry 127).
Now
After every attack, each enemy is now drawn to match its Imp status, whatever gave it the status.
For ROM hackers 1.0 addresses
Only the Imp spell's animation command changes a monster's imp sprite ($62C2+slot). After every attack animation, graphics command $06 (C1/B760) ends at C1/B772: insert jsr SyncMonsterGfx (3 bytes) there. SyncMonsterGfx (12 bytes) calls a sweep in any bank that sets each living monster's $62C2 byte from bit 5 of its status at $3EEC+2*slot (a dead one keeps its sprite) and returns carry set if any byte changed; then it runs InitMonsterGfx (C1/257C) and WaitTfrMonsterGfx (C1/25C1). #385 leaves 20 bytes free from C1/D713; SyncMonsterGfx and the 6 bytes of this insert fit there. The sweep needs 47 bytes of free space.
Sweep: lda #0
xba ; B = 0 for tax
ldy #0
ldx #0
@loop: phx
txa
asl
tax
lda $3EEC,x ; monster's status 1
plx
asl ; wound into carry
bcs @next
and #$40 ; imp, shifted
beq :+
lda #1
: cmp $62C2,x
beq @next
sta $62C2,x
ldy #1
@next: inx
cpx #6
bne @loop
cpy #1
rtl
Credit: previously found by Master ZED and fixed by Leet Sketcher, my implementation
#387 Facing right, the Zephyr Cape's second block sprite now draws behind the party member instead of in front, matching facing left.
In 1.0
The Zephyr Cape's block is drawn in two sprites: facing left, one is drawn behind the party member, but facing right, as in a back attack, both were drawn in front.
Now
Facing right, the second sprite now goes behind the party member too.
For ROM hackers 1.0 addresses
Cape block sprite script, attack animation script $47 at D0/7A21. The second frame is set to draw order FRONT, so facing right both frames are in front of the character. Change the operand of the block_draw_order command at D0/7A3E ($9B) from $40 (FRONT) to $00 (LEFT_HAND), one byte at D0/7A3F; the first frame already uses RIGHT_HAND, so each frame now takes the side it takes facing left.
Credit: previously found by Master ZED and mblock129, my implementation
#388 Imperial, Kodachi and Blossom swung from Shadow's left hand no longer draw at his wrist below his raised hand.
In 1.0
When Shadow swung an Imperial, Kodachi or Blossom from his left hand with Fight, the blade was drawn at his wrist, below his raised hand.
Now
The blade is now drawn in his hand.
For ROM hackers 1.0 addresses
Attack animation script $21 at D0/7D6C, the left-hand sprite of Imperial, Kodachi and Blossom. Its first move lifts the knife 9 pixels, which leaves it at Shadow's wrist. Change the operand of the move command at D0/7D73 ($83) from $C8 (up 9) to $CC (up 13), one byte at D0/7D74.
Credit: previously found by Master ZED and AWJ, my implementation
#389 A reflected status spell with nobody to hit on the other side no longer plays its animation on one of them anyway.
In 1.0
When a reflected Regen, Haste, Slow or Bserk bounced to the other side and nobody there could be hit, because they were all jumping or hidden, the animation still played on the first of them.
Now
When the rebound finds nobody to hit, no animation plays at all.
For ROM hackers 1.0 addresses
Graphics script command $09, reflected attack, at C1/A79B. It builds the rebound's targets in $613D (characters) and $613E (monsters) and then plays the spell's animation whatever they hold, so with nobody to hit it plays on the first character. Insert before the jsr InitSimpleAnim at C1/A7D7 (17 bytes): when both bytes are zero, undo the command's start (jsr PopMonsterPalID, stz $62D1) and jmp NextGfxCmdData.
lda $613D ; before jsr InitSimpleAnim at C1/A7D7
ora $613E
bne :+
jsr PopMonsterPalID
stz $62D1
jmp NextGfxCmdData
:
Credit: previously found by Master ZED and Lenophis, my implementation
#390 A party member's Rage Special blocked by a shield, cape or dog no longer leaves them stuck forward with arms raised.
In 1.0
When a Muddled Gau turned his Rage's Special on a party member who blocked it with a shield, a cape or Shadow's dog Interceptor, Gau was left forward with his arms raised, out of line for the rest of the battle.
Now
The attacker now steps back into line, as after any other attack.
For ROM hackers 1.0 addresses
FightCmdAnim (C1/BECD). A character's Special runs through it with the monster attack flag $618A set, and its end asks for the step back only while that flag is set (the lda $618A at C1/C222). The block branch clears the flag at C1/C041, so a blocked Special never steps back. Replace that stz (3 bytes) with three nop. The flag's other reader in the block path adds $200 to hit scripts from $60 up, and every block script is $40 to $48, so it may stay set.
Credit: previously found by Master ZED, my implementation
#391 Shadow throwing at an enemy that jumped away, with none left, now steps out and back instead of drifting off screen.
In 1.0
When Shadow threw a weapon at an enemy that had jumped out of reach and no other enemy was left, the throw was skipped and he ended a step behind the line, and repeated throws could walk him off the screen.
Now
He now steps out with his arms raised, then steps back into his own place.
For ROM hackers 1.0 addresses
Throw handler (graphics command for Throw). A thrown weapon's step out lives in its sprite script, which runs once per target, so with no target left only the step back plays. After the and #$0F at C1/B9DF (the low nibble of ItemJumpThrowAnim, D1/0040), insert 16 bytes: for any throw other than 8 to 12 (skeans and edges, whole without a target), call NullTargetAnim (C1/BBF7), the step out and back that Tools use; if it returns carry clear there was no target, so return.
cmp #8 ; after and #$0f at C1/B9DF
bcc @thrown
cmp #13
bcc @go
@thrown:
pha
jsr NullTargetAnim
pla ; carry kept from the call
bcs @go
rts
@go: ; clc, adc as before
Credit: previously found and fixed by Leet Sketcher, my implementation
#398 An enemy revived after X-Zone, Snare or a neighboring undead revival now reappears visibly instead of staying invisible and untargetable.
In 1.0
An enemy brought back after X-Zone or Snare, by a spell or Life 3, stayed invisible and could not be picked with the cursor, and a Life 3 enemy killed in the same turn as a reviving undead enemy came back hidden too.
Now
A revived enemy now shows up again and can be targeted, unless the battle's own script has hidden it on purpose.
For ROM hackers 1.0 addresses
Revive monster, _c2469c (C2/469C). It puts the monster back in $2F2F and out of $3A3A but never sets its bit in $61AB, the battle graphics' mask of monsters shown, which X-Zone's animation clears and an undead revival rebuilds without the dead. Sprites and the target cursor both need that bit. Insert and $3409, tsb $61AB (6 bytes) before the plx at C2/46A7: with A still the monster's mask, this sets the bit unless a script has hidden the monster ($3409 bit clear, AI command $F5).
Credit: previously found by Master ZED, my implementation
#411 Fleeing a pincer attack no longer turns dead and petrified party members around with the living.
In 1.0
When the party fled from a pincer attack, dead and petrified party members turned round to run along with the living.
Now
Only the living turn round now.
For ROM hackers 1.0 addresses
Battle animation init $59 (run), C2/EE30. In a pincer it writes the new facing to all four characters' Flip byte ($61BE, $20 per slot) from C2/EE5B up to the rts at C2/EE67, dead and petrified ones included. Replace those four sta (12 bytes) with a jmp to the routine below (35 bytes of free space in bank C2; X is 16-bit here). It skips a slot whose graphics status 1 copy ($2EBD, $20 per slot) has Dead or Petrify ($C0).
jmp MyFix ; replaces the four sta at C2/EE5B
MyFix: phx
pha ; the new flip
ldx #$0060 ; slot 4 down to slot 1
@loop: lda $2EBD,x ; status 1 (graphics copy)
and #$C0 ; dead or petrified
bne @next
lda 1,s
sta $61BE,x
@next: rep #$20
txa
sec
sbc #$0020
tax
sep #$20
bpl @loop
lda #$00
xba ; A's high byte back to 0
pla
plx
rts
Credit: previously found by Novalia Spirit, my implementation
#413 The battle message window no longer stops clearing after 256 messages in one fight.
In 1.0
After 256 messages in one battle, the message window at the top of the battle screen stopped clearing, and a message stayed on screen.
Now
The window keeps clearing however many messages a battle shows.
For ROM hackers 1.0 addresses
InitMsgWindow, C1/98C4. The inc of the message window count $629E at C1/98C5 wraps to 0 on the 256th message, and C1/9917 reads 0 as no window to clear. Store 1 instead, same size: delete the inc at C1/98C5, move the next three instructions (stz, lda #$01, sta) up 3 bytes, and put sta $629E in the 3 bytes freed before C1/98D0.
Credit: previously found by Novalia Spirit, my implementation
#418 A Hawk Eye or Sniper thrown during Gau's Rage now throws itself instead of the Rage enemy's own weapon.
In 1.0
When a Hawk Eye or Sniper threw itself at a flying enemy during a Rage, the weapon seen flying was the Rage enemy's own.
Now
The throw now shows the Hawk Eye or Sniper itself.
For ROM hackers 1.0 addresses
TargetEffect_08 (Hawk Eye and Sniper), C2/38FE. When it turns a Fight into a Throw, it takes the item from $B7, the weapon animation number, less one, at C2/391A. Under Rage that number is the Rage monster's weapon sprite. Replace the 5 bytes at C2/391A with a jsr to the routine below (32 bytes of free space in bank C2). A character takes the item from the battle hand lists: $2B86 plus 5 per slot for the right hand, $2B9A when $B6 bit 7 (the left hand, set by _c2299f at C2/299F) is set. A monster keeps the old way.
jsr MyFix ; replaces lda $B7 / dec / sta $B6 at C2/391A
MyFix: lda 7,s ; the attacker (5,s at the call site)
cmp #$08
bcs @monster
lsr ; slot 0 to 3
pha
asl
asl
adc 1,s ; x 5
bit $B6
bpl @right
adc #20 ; the left hand's list
@right: tax
pla
lda $2B86,x ; the item in that hand
sta $B6
rts
@monster:
lda $B7
dec
sta $B6
rts
Credit: previously found by Master ZED, my implementation
#435 Celes running with L and R no longer draws the Runic sword standing straight up out of her head.
In 1.0
Holding L and R to run while Celes absorbed a spell with Runic kept her in her running pose, with the Runic sword drawn standing straight up out of her head.
Now
She now holds the absorb pose for the whole animation, then goes back to running.
For ROM hackers 1.0 addresses
DrawCharSprite, C1/34DB. While L and R are held, the block from C1/3516 picks the run pose unless a test branches to C1/3549, where the frame an animation sets (wCharGfxData::AnimFrame) is used. So the run pose overrides a pose set by an animation, such as Runic's absorb. Insert before the lda at C1/3520 (5 bytes): a nonzero animation frame goes to C1/3549.
lda near wCharGfxData::AnimFrame,x
bne @3549 ; the animation's pose wins
Credit: previously found by Novalia Spirit, my implementation
#436 The battle backdrop no longer stays shifted after a wave effect from Ifrit, Bismark, Terrato and others, and Siren's script no longer leaves a frozen vertical wave.
In 1.0
After a wave effect such as Ifrit's, Bismark's or Terrato's summons, the battle background stayed shifted for the rest of the fight, and Siren's summon left an up-and-down wave frozen on it.
Now
A finished wave now puts the background back, and Siren's summon ends both of its waves.
For ROM hackers 1.0 addresses
InitSineBuf, C1/0D5C, builds eight 64-byte wave tables at $7EE7BF, one per amplitude. _c10d7d (C1/0D7D) stores each value 2 below its sine, so the amplitude 0 table that ends a wave holds -2 on every line and the backdrop stays shifted. Insert after the bne at C1/0D7A (9 bytes, 8-bit A, 16-bit X): clear the first table. Siren's script, D0/5045: the init_scroll_wave at D0/5109 that ends its wave also needs VERT beside HORZ in its direction bits (same size), or the vertical wave stays frozen.
Credit: previously found by Novalia Spirit, my implementation
#437 A Muddled Cyan's Dispatch on himself now draws its slash across his body instead of behind him.
In 1.0
When a Muddled Cyan used Dispatch on himself, the slash appeared behind him, so it never crossed his body.
Now
The slash now crosses his body.
For ROM hackers 1.0 addresses
CalcTargetPos (C1/C48E) and _c4a0 (C1/C4A0), the animation position code. Alignments $80 and $C0 (C1/C540) put the animation 24 pixels to the front of the target on the attacker's facing side, which for a character target is behind him; a Muddled Cyan's Dispatch on himself slashes the spot he stepped from. Insert before the bra at C1/C55D (6 bytes): when the attacker $10 equals the target $12, go to C1/C4EE, the no-offset path.
lda $10 ; attacker
cmp $12 ; target
beq @c4ee ; aiming at itself: no offset
bra @c4f4
Credit: previously found by Novalia Spirit, my implementation
#438 A Magitek Armor rider's armor no longer vanishes during animations like Haste and Regen that redraw the target.
In 1.0
When Haste, Regen, Antdot, an X-Potion or a few other animations played on a party member riding Magitek Armor, the armor vanished for the whole animation and left the rider floating in mid-air.
Now
The armor now stays drawn under its rider.
For ROM hackers 1.0 addresses
The Character Graphics (bg1) script, D0/5FA2, hides the target's sprite while drawing it on BG1, and a Magitek rider's armor is drawn with that sprite (DrawBlockSprites, C1/329F). Change its four hide_target_chars ($F1 $00) at D0/5FB6, D0/5FC1, D0/5FCC and D0/5FD7 to $F1 $02. Command $F1 for a character: replace C1/E306 up to C1/E324 (30 bytes) with jsl ShowHide (35 bytes of free space) and a bra to C1/E324; ShowHide also flags a character hidden with bit 1 in bits 4-7 of $61AC. UpdateCharSprites, C1/297F: insert after the bne at C1/2993 (11 bytes); KeepArmor (18 bytes) returns carry set when Magitek mode is on and the character's flag is set.
; UpdateCharSprites, after the bne at C1/2993
jsl KeepArmor
bcc @2995
lda 1,s ; character number
jsr DrawBlockSprites
; free space, 8-bit A, $10 = character bit
ShowHide:
lda $10
asl4
ora $10
trb near w7e61ac ; hide, drop the bg1 flag
lda [zAnimScriptPtr]
lsr
bcc @hide
lda $10
tsb near w7e61ac ; show
rtl
@hide: lsr
bcc @done
lda $10
asl4
tsb near w7e61ac ; hidden while drawn on bg1
@done: rtl
Credit: previously found by Novalia Spirit, my implementation
#479 True Knight's bodyguard now leaps in front of dice attacks, thrown weapons and Capture, the same way he does for a Fight.
In 1.0
A True Knight bodyguard took hits from dice (a Fight with Dice or Fixed Dice) and thrown weapons (Hawk Eye or Sniper at a floating target) without leaping in front of the weak party member, and with Capture he leapt only after the thief had jumped to his old spot, so the thief struck empty ground.
Now
For all three he now leaps in front first, and the attack plays out against him there.
For ROM hackers 1.0 addresses
The battle code marks the bodyguards in the cover mask (animation data bytes 6 and 7), but only FightCmdAnim (C1/BECD) acts on it, calling PushObjPos (C1/BCBF) and InitCoverAnim (C1/BD5A) at C1/BF1E. In free space, wrap the others: push, InitCoverAnim, _c1b8a4 (C1/B8A4), PopObjPos (C1/BD0B), each through a jsl entry in bank C1. Dice: the jmp at C1/B98A becomes jsl, rts (2 bytes more). Throw: the jsr at C1/B9E4 becomes jsl, jmp C1/B86B (3 bytes more). Capture: at C1/BBC4 push and InitCoverAnim before the jump, then write the thief's landing x and y into his saved position ($813B + 4 × slot); FightCmdAnim then skips its own push and InitCoverAnim for Capture.
Credit: previously found by Novalia Spirit, my implementation
#488 True Knight's bodyguard now leaps in front of a Muddled Umaro's Charge or thrown party member too, instead of the attack landing where he originally stood.
In 1.0
When a Muddled Umaro used Charge on a weak party member, or threw a party member at one, a True Knight bodyguard did not leap in front but took the blow where he stood.
Now
The bodyguard now leaps in front first, and the attack lands on him there.
For ROM hackers 1.0 addresses
Umaro's command animations. UMARO_THROW ends with jmp _c1b8a4 at C1/BA8A; UMARO_TACKLE calls _c1bbe1 (C1/BBE1, _c1b8a4 then _c1b86b) at C1/BA98. Neither runs PushObjPos (C1/BCBF) and InitCoverAnim (C1/BD5A), as FightCmdAnim does, so the bodyguard in the cover mask never leaps. Replace both (same size) with calls to the routines below, 22 bytes of free space in bank C1, which play the animation between the leap and PopObjPos (C1/BD0B). Same leap as #479.
jmp MyCover ; replaces jmp _c1b8a4 at C1/BA8A
jsr MyTackle ; replaces jsr _c1bbe1 at C1/BA98
MyTackle:
jsr MyCover
jmp _c1b86b
MyCover:
pha ; command animation number
jsr PushObjPos
jsr InitCoverAnim ; the bodyguard leaps
pla
jsr _c1b8a4
jsr PopObjPos ; everyone back
clr_a
rts
#491 On the Veldt, Gau waiting for food now falls when killed by Doom or Roulette instead of staying stood up until the screen fades.
In 1.0
On the Veldt, when Gau has leapt into the end of a battle and waits for food, killing him with Doom or Roulette won the fight but left him drawn on his feet until the screen faded.
Now
He now falls when the spell's animation ends, as a party member does.
For ROM hackers 1.0 addresses
UpdateCharStatusAction, C1/3071. Gau's entry leap on the Veldt (character AI $0A, in $2F4A while $2F49 bit 7 is set) ends on a pinned frame ($61C1,x, the AnimFrame byte of the character's graphics record), and a pinned frame is drawn over the status pose; Gau never acts, so it is never let go. Replace the dead test at C1/3083 (bpl, lda, bra; 6 bytes) with jsl MyFix / bcs @30be (same size) and the routine below (47 bytes of free space), which clears the pin for a dead character of $2F47's enemy set.
MyFix: cmp #$80 ; status 1: dead?
bcc @done
lda $2f49
bpl @dead ; character AI off
lda $2f4a
cmp #$0a ; Gau on the Veldt
bne @dead
lda $2f47 ; characters on the enemy side
cpx #$0020
bcc @bit
lsr
cpx #$0040
bcc @bit
lsr
cpx #$0060
bcc @bit
lsr
@bit: lsr
bcc @dead
stz $61c1,x ; let go of the pinned frame
@dead: lda #CHAR_ACTION::DEAD_HORZ
sec
@done: rtl
Credit: previously found by Master ZED, my implementation
#492 An enemy under Relm's Control that becomes Muddled now keeps its back turned like any other Muddled enemy.
In 1.0
Muddling an enemy that Relm was Controlling also ended the Control, and the enemy then turned back to face the party while still Muddled, unlike every other Muddled enemy.
Now
It now keeps its back turned until the Muddle ends.
For ROM hackers 1.0 addresses
RemoveControlAttacker, C2/075B. Control and Muddle turn a monster round with the same bit, and Muddle also ends the Control link, so this routine's clear of the turn-round bit (lda $3019,x / trb $2F54 at C2/0773) turns a still Muddled monster back. Insert before C2/0773: lda $3EE5,x / and #$20 (Muddle) / bne to the phx at C2/0779 (7 bytes), which skips the clear.
Credit: previously found by Master ZED, my implementation
#506 A Jump whose landing animation never plays, as Cyan's Tempest and Dragon Horn could cause, now redraws the jumper as soon as they're back in the fight.
In 1.0
With the Dragon Horn and the Tempest, a Jump could turn into a chain of Wind Slash casts, and the jumper never landed. They kept fighting but stayed invisible, and the cursor could not target them, until a later Jump landed.
Now
The jumper now shows again, and can be targeted, as soon as they are back in the fight.
For ROM hackers 1.0 addresses
copy_player_work (C1/0DF3) ends by storing the not-hidden mask in $61AD. $61AC (characters shown) is cleared when a jump launches and set again only by the landing animation, so a jump whose landing never plays (a weapon's spell proc) leaves the jumper undrawn and out of the cursor's reach. Replace the lda $10 / sta $61AD at C1/0E61 (5 bytes) with a jsl and an rts; the routine (34 bytes of free space in any bank) also sets the $61AC bits of characters present at the start ($3A8D), not hidden, not gone ($3A39) and shown once, on a full update only. Keep those bits in $61B6, a spare byte cleared at C1/0FE0, so Kefka's late entrance in Terra's flashback is not drawn early.
jsl MyFix ; replaces lda $10 / sta $61ad at C1/0E61
rts
MyFix: lda $10 ; characters without hide status
sta $61ad
lda $61ac ; characters shown now
and #$0f
tsb $61b6 ; characters shown once
lda $12 ; zero while battle time is stopped
beq @done
lda $3a39 ; characters that left the battle
eor #$ff
and $10
and $3a8d ; characters present at the start
and $61b6 ; and shown once
tsb $61ac ; characters shown
@done: rtl
Credit: previously found by Master ZED and god-bird, my implementation
#507 A party member left behind during a pincer flee is no longer turned to face the wrong way and made to fight backwards.
In 1.0
When part of the party ran from a pincer attack, a party member who stayed behind with their command menu waiting was turned to face the escape side, then defended and swung their weapon facing away from the enemy.
Now
Only the party members who actually run are turned; anyone who stays keeps facing the enemy.
For ROM hackers 1.0 addresses
Battle animation init $59 (run), C2/EE30. In a pincer it writes the new facing into all four characters' wCharGfxData Flip bytes (four stores from C2/EE5B), so a character whose menu was waiting turns with the runners. Replace those stores and the rts (13 bytes) with a jmp to a loop (40 bytes of free space in bank C2) that writes the flip only for slots whose bit is set in $613D (wAnimCharTargets, bit 0 = slot 0). #411 adds a dead or petrified test to the same loop.
jmp MyFix ; replaces the four sta and the rts at C2/EE5B
MyFix: phx
pha ; the new flip, $00 or $40
lda #$08 ; slot 3's bit
pha
ldx #$0060 ; slot 3's wCharGfxData offset
@loop: lda 1,s
and $613d ; wAnimCharTargets: does this slot run?
beq @next
lda 2,s
sta near wCharGfxData::Flip,x
@next: lda 1,s
lsr
sta 1,s
rep #$21 ; 16-bit a, carry clear
txa
adc #$ffe0 ; x - 32
tax
shorta
bpl @loop
pla
pla
plx
rts
Credit: previously found by ffdic, my implementation
#519 Interceptor's dog and Golem's wall now always show their own block animation, instead of one drawn at random from the party member's equipment.
In 1.0
When Interceptor's dog or Golem's wall blocked a blow, the game picked the block animation at random from those the party member's equipment offered, and Golem's wall wore down only when its own animation was picked.
Now
The dog or the wall now always shows its own animation, and Golem's wall wears down with every blow it stops.
For ROM hackers 1.0 addresses
CheckHit's block animation picker, _c223bf (C2/23BF). At C2/23C8 it ORs the equipment's block animation bits (BlockGfx) with $FE, which holds the Golem or Interceptor bit once the engine has chosen that block, and RandBit at C2/23CF draws one of them, so the dog or the wall shows only when it wins the draw, and Golem takes the blow only then. Insert lda $FE / bne to C2/23CF (4 bytes) before the lda at C2/23C8 and drop the ora $FE after it, so a chosen block draws from $FE alone.
Credit: previously found and fixed by Bropedio, my implementation
#528 The True Knight's bodyguard leap now lands on the side the blow comes from, in front of the party member he shields.
In 1.0
In a pincer, a True Knight wearer leaping in front of a near-fatal party member could land on the wrong side, leaving them in the open. In any battle, a wearer later in the party order than that party member was drawn behind them.
Now
The wearer now lands on the side the blow comes from, and is drawn in front.
For ROM hackers 1.0 addresses
InitCoverAnim (C1/BD5A) picks the guard's side from the protected party member's own facing ($7B10,y at C1/BD8B), which says nothing about where the blow comes from. Replace C1/BD8B up to C1/BD9A (15 bytes) with jsr GetAttackerNum (C1/BC89), a jsl to a routine in free space, and bra to C1/BDD1. The routine compares the attacker's centre x ($8033 for a character, $800F for a monster, a word per slot) with the party member's in $24: attacker to the left gives X = $0000, to the right X = $000C, equal falls back to $7B10. Leave A zero. Draw order: in CoverOffsetTbl (C1/BEA1) change the first guard's Y offset in both rows, C1/BEA3 and C1/BEAF, from $0000 to $0001, so UpdateDrawOrder (C1/957C) puts him in front of the party member.
Credit: previously found and fixed by Bropedio, my implementation
#529 The True Knight's bodyguard now turns to face the blow when he leaps, and turns back when he steps home.
In 1.0
The True Knight wearer's leap moved them but never turned them, so a wearer who crossed the field in a side attack, or stood in the half of a pincer facing the other way, arrived facing the party member they were shielding, with the blow at their back.
Now
The wearer now turns to face the blow when leaping, and turns back when stepping home.
For ROM hackers 1.0 addresses
Same routine as #528. The leap never sets the guard's horizontal flip. The routine also turns each guard in the cover mask (byte 6 of the command at ($78)): it saves his wCharGfxData Flip byte, then sets bit 6 for a blow from the right and clears it for one from the left, keeping bit 7, the vertical flip. $7B10 stays, since it picks hand and weapon. PushObjPos (C1/BCBF): insert a stz of the saved mask. PopObjPos (C1/BD0B): insert before the jsr UpdateDrawOrder at C1/BD56 a jsl that writes the saved flips back for the guards in the mask and clears it. Needs six bytes of RAM; this ROM hack uses $7EED04 to $7EED09, unused in 1.0.
#532 A muddled enemy turned into a zombie no longer keeps its back to the party once the Muddle is gone.
In 1.0
A muddled enemy turned into a zombie, by a controlled Osteosaur, FossilFang, Bloompire, Zombone, Exoray or Tiger, kept its back to the party to the end of the fight, even after a cure.
Now
The enemy now faces the party once it is no longer muddled.
For ROM hackers 1.0 addresses
UpdateStatus, C2/4391. $2F53 holds one bit per target, and its monster half turns a monster round. The Confuse set and remove handlers keep it, but the zombie strip in _c24585 takes Muddle off by writing the status word, so the bit stays. Insert a jsr before the jsr ResetStatusMod at C2/43FC (3 bytes) to the routine below (36 bytes of free space in bank C2). A target whose status 1/2 word ($3EE4,y) holds Muddle is set in $2F53; one without it is cleared, unless a controller holds it ($32B9,y, ControlAttacker, bit 7 clear).
jsr MyFix ; inserted at C2/43FC
MyFix: php
rep #$20
lda $3ee4,y ; status 1 and 2
and #$2000 ; Muddle
beq @clear
lda $3018,y
tsb $2f53
bra @done
@clear: sep #$20
lda $32b9,y ; ControlAttacker
bpl @done ; held by a controller
rep #$20
lda $3018,y
trb $2f53
@done: plp
rts
Credit: previously found by Assassin, my implementation
#538 Terra no longer ends a fight a step out of line when her Morph runs out between the two spells of X-Magic.
In 1.0
If Terra's Morph ran out during the first spell of X-Magic, the automatic Revert stepped her forward a second time, and she ended the fight a step in front of the party.
Now
She now ends back in line.
For ROM hackers 1.0 addresses
Animation script $7A, Revert (sprite), at D0/69DA. Its first command, the call at D0/69DC, steps the attacker forward. Between the two casts of X-Magic the caster still stands forward and owes a step back ($7E61AE), so a Revert landing there steps forward a second time. Insert jump_step (command $DB and a one-byte relative offset, 2 bytes) before that call, aimed at the fixed_draw_order at D0/69DF, so the step is skipped when the attacker already stands forward. Every other step forward in the scripts has this guard.
Credit: previously found by C-Dude, my implementation
#546 Life, Life 2 or a Fenix Down that kills an undead enemy now shows a number over it, its maximum hit points or 9999.
In 1.0
Life, Life 2 or a Fenix Down that killed an undead enemy showed no number over it, while the same revive on a party member shows the hit points it restores.
Now
A number, the enemy's maximum hit points or 9999, now shows over it as it falls.
For ROM hackers 1.0 addresses
MagicStatusEffect, C2/4406. When a life effect is turned round into Wound on an undead target, the tsb $FC at C2/443B sets Wound and writes no damage figure, so no number pops. Insert after that tsb, before C2/443D, a jsl to the routine below (19 bytes of free space): damage taken ($33D0,y) becomes the target's max HP ($3C1C,y), 9999 at most.
jsl MyFix ; inserted at C2/443D, fall-through path
MyFix: php
rep #$20
lda $3c1c,y ; max HP
cmp #10000
bcc :+
lda #9999
: sta $33d0,y ; damage taken
plp
rtl
Credit: as in the Pixel Remaster, my implementation
#550 The arrow over the party member whose turn it is no longer vanishes while Quake's pit is open.
In 1.0
During Quake, the arrow over the party member whose turn it is vanished for about two seconds, while the party member stayed in view.
Now
The arrow now stays in view.
For ROM hackers 1.0 addresses
DrawCursorSprites, C1/B540. The turn arrow's OAM attribute byte ($031B) is set to $28 at C1/B58E: priority 2 and palette 4, one below the characters' 3, so an effect drawn between the two (Quake's pit) hides it. Insert after the jsr at C1/B55C, where X is the character times 32 in 16 bits: lda $61BD,x (the character's priority byte), and #$30, ora #$08, sta $031B (10 bytes). Remove the lda #$28 and sta $031B at C1/B58E (5 bytes).
#560 Gau's Rage special keeps its own animation when he holds the Atma Weapon, instead of drawing a black blade or crashing the game.
In 1.0
If Gau held the Atma Weapon and used a Rage whose special attack does damage, the special could show a black-edged sword with no hit on the target, or freeze or crash the game.
Now
The special now plays its own animation.
For ROM hackers 1.0 addresses
UltimaEffect, C2/0E39. When the Atma Weapon's scaled damage reaches 501, it writes blade $5B or $5C into $B7 as the animation. That number indexes the weapon table, but for a Rage special (attack name type $3412 = 6) the graphics read the monster attack table, which ends before $5B. Insert ldx $3412 / cpx #$06 / beq C2/0E73 (7 bytes) after the bcc at C2/0E64, before C2/0E66. The damage scaling above is untouched.
Credit: previously found by Kardis, Master ZED and urawaza.in, my implementation
#568 X-Zone cast at the Air Force's Speck, which absorbs it, no longer makes the live Speck vanish from the screen and the target cursor while it goes on absorbing spells.
In 1.0
When the Air Force's Speck absorbed X-Zone, it vanished from the screen and the target cursor, yet kept absorbing spells.
Now
The Speck now stays on screen and can be targeted.
For ROM hackers 1.0 addresses
magic_code71, C1/E2C0, animation command $F1 (show or hide target). The hide path for a monster, reached by the beq at C1/E2DC, clears its bit in $61AB whatever the battle decided, so an X-Zone the Speck absorbs still hid it. Insert 31 bytes before the hide code at C1/E2E8: hide only when the monster has left $2F2F, is in $2F4D (leaving), has Wound, or has Hide in status 4; otherwise go to C1/E324 with the sprite kept. X is the monster number 0 to 5 here.
lda $2f2f ; monsters present
and $10 ; this monster's bit
beq @hide
lda $2f4d ; monsters that can't be targeted
and $10
bne @hide
txa
asl
tax
lda $3eec,x ; status 1
bmi @hide ; Wound
lda $3f01,x ; status 4
and #$20 ; Hide
bne @hide
bra @e324 ; picture stays
@hide: ; the original hide code, C1/E2E8
Credit: previously found and fixed by ◆6vOWP2tBUM, my implementation
#569 When one party member's shield blocks a Muddled Cyan's Cleave or Phoenix, it no longer plays again for every other party member, or leaves Cyan's body out of place.
In 1.0
When a party member's shield blocked a Muddled Cyan's Cleave, or the shield of a party member who was not KO'd blocked a Phoenix, the animation played again for every other party member, and Phoenix's copies cut strips out of the sprites. If the Cleave killed Cyan too, his body lay below his place for the rest of the battle.
Now
Cleave now plays once and Cyan falls in his place, and Phoenix plays at most once.
For ROM hackers 1.0 addresses
InitAnimThreads, C1/AC6B. The loop entry at C1/ACF5 skips a sprite script with bit 15 set in $6273 (a script that belongs to the attacker alone), but the hit branch at C1/AD31 gives each target in $12 that neither blocked nor reflected a copy of it. A shield block makes the loop run, so Cleave's leap played once per party member, and Phoenix, drawn once by the esper thread, gave each unblocked party member a whole copy (20 OAM sprites), overrunning the PPU's limit of 34 slivers per scanline. Reorder the four instructions from C1/AD31 so $6273 is loaded first, and insert bmi to C1/AD5C (2 bytes) after that load.
ldy $6273 ; sprite script number, moved up
bmi @ad5c ; new: the attacker's own script
sty $24
ldy $7b35 ; frame height/width
sty $22
Credit: previously found by Locke_Cole, Tsushiy and Novalia Spirit and fixed by Tsushiy, my implementation
#590 A Super Ball thrown when every enemy left is in the air no longer stops the battle for over a minute.
In 1.0
If a Super Ball was thrown while every enemy left was in the air, from a Reach Frog's or Umaro's jump or a Palidor whose caster was Muddled after choosing it, no ball flew and the battle stood still for over a minute.
Now
The thrower steps forward and back, and the battle goes on at once.
For ROM hackers 1.0 addresses
_a8b3 (C1/A8B3), the extra thread's target loop at C1/A923. With no target entry below $80 the loop falls out of the bne at C1/A949 with A = $FF, entry 15 of the 12-word table at C2/CE8B gives $0002, and CreateExtraThread (C1/B208) puts the thread at offset $12, across threads 1 and 2, which switches on thread 2 with no script. Replace C1/A93C to C1/A94A with the loop's end first, a bra to C1/A95E (no extra thread), then the found-entry block: C8 C0 0A 00 D0 E1 80 1A 98 C9 04 90 02 09 80. Then the bmi at C1/A925 takes $15 and the beq at C1/A933 takes $0F. Same size, 16 bytes changed.
bmi NextEntry ; C1/A925
...
beq Found ; C1/A933
...
NextEntry: ; C1/A93C
iny
cpy #$000a
bne @a923
bra @a95e ; no target: no extra thread
Found: tya
cmp #$04
bcc @a94b
ora #$80
@a94b: sta $10 ; C1/A94B, unchanged
#591 A True Knight wearer who takes a blow in place of another party member is no longer drawn over or under a neighbour after going back in line.
In 1.0
When a True Knight wearer leapt in front of another party member and took the blow, they went back to their place drawn over the next party member or under the one before, until anyone acted again.
Now
The wearer is drawn in place as soon as they are back.
For ROM hackers 1.0 addresses
PopObjPos (C1/BD0B) puts the line-up back after a Fight and sorts the draw order with UpdateDrawOrder (C1/957C), which returns at once while $7B0C is set. After a covered hit, FightCmdAnim reaches it in the frame the animation's last sort, made from the leap's positions, set $7B0C (a covered miss first waits 32 frames at C1/C1FE), so the leap's order stays drawn. Replace the jsr UpdateDrawOrder and rts at C1/BD56 with jmp MyFix, 13 bytes of free space in bank C1: when byte 6 of the animation record, the character bodyguards, is not zero, clear $7B0C first.
MyFix: ldy #$0006
lda (z78),y ; character bodyguards
beq @sort
stz $7b0c ; drop the leap's waiting sort
@sort: jmp UpdateDrawOrder
Credit: previously found and fixed by Leet Sketcher, my implementation
#606 In a side attack with the first enemy place empty, a 7-7-7 from Slot no longer turns the upper-left party member away from the enemies.
In 1.0
In a side attack whose first enemy place is empty, a party member in the upper-left place who rolled 7-7-7 with Slot turned their back on the enemies while waiting to cast Joker Doom.
Now
The party member faces the enemies throughout.
For ROM hackers 1.0 addresses
_gaugefull, C2/2188, starts the ready stance of an advance wait action. At C2/21E6 it copies the action's targets from $3520,y to $B8, and the stance faces the lowest monster target in $B9. A 7-7-7's targets hold all six monster bits, empty slots included, so in a side attack with monster slot 0 empty the character faces that empty slot. Insert lda $B9, and $2F2F, sta $B9 (7 bytes, 8-bit accumulator) before the lda at C2/21F5, keeping only the monsters present.
lda $B9 ; monster targets
and $2F2F ; monsters present
sta $B9
Credit: previously found by Master ZED, my implementation
#622 Eleven shields, helmets, armor, relics and weapons now show the block or dodge animation that matches what they actually block.
In 1.0
The Flame, Ice, Thunder and Tortoise Shields never showed the shield block against magic. The Green Beret, Cat Hood, Snow Muffler and Beads carried no block animation, and the ManEater and Enhancer carried a parry though they give no Evade; since Evade did nothing before #2, only the Cat Hood's and Snow Muffler's bare "Miss" showed in the original, and the two weapons' parry matched the misses their MBlock caused.
Now
Each of the eleven items shows the block or dodge animation that matches what it blocks, the helmets and the Beads once Evade works (#2); the Dueling Mask, our own, gets the same dodge.
For ROM hackers 1.0 addresses
Item byte 27 (ItemProp, D8/5000, 30 bytes a record) holds the block animation: bits 0-1 the graphic (dagger, sword, shield, cape), $04 physical, $08 magic; the high nybble is the weapon special. The battle builds each fighter's pools from it ($11D0 physical, $11D1 magic) and picks one when a miss is drawn (in the US 1.0 ROM MBlock decides physical and magic misses alike and Evade does nothing, see #2), drawing nothing from an empty pool. Set byte 27 of: ManEater (6) $40, Enhancer (19) $00, Flame Shld (96), Ice Shld (97), Thunder Shld (98) and TortoiseShld (101) $0E, Green Beret (114), Cat Hood (128), Snow Muffler (162) and Beads (212) $07.
; byte 27 of each record at D8/5000 + 30 x item:
; item 6 ManEater $44 -> $40
; item 19 Enhancer $05 -> $00
; items 96, 97, 98, 101 $06 -> $0E
; items 114, 128, 162, 212 $00 -> $07
Credit: previously found and fixed by an unnamed GBA patch author, my implementation
#625 Damage numbers are now drawn in front of every party member and enemy, so a large enemy lower on the screen, like the Air Force's Missile Bay, no longer hides them.
In 1.0
Damage numbers were drawn together with the party member or enemy they belonged to, so a large enemy lower on the screen covered the numbers above it: on the way to the Floating Continent, the Air Force's Missile Bay hid the damage dealt to the Air Force.
Now
Damage numbers are drawn in front of every party member, enemy and attack sprite.
For ROM hackers 1.0 addresses
DrawSprites (C1/2897) walks the draw order from C1/28F5, the target lowest on the screen first, and each target's damage numerals are drawn inside its own call, by UpdateMonsterSprites at C1/27DB and UpdateCharSprites at C1/29AC, so a target drawn earlier takes the earlier sprite slots and covers them. Insert jsr MyFix after the jsr UpdateCharXPos at C1/28CA (3 bytes): a loop over the ten draw-order entries with the visibility tests of that walk and of UpdateCharSprites (C1/297F), calling DrawDmgNumSprites (C1/2B2A) with the monster number, or the character number plus $80 (about 100 bytes of free space in bank C1). Replace both old calls with three nops; A already holds what they return.
#634 Battle scenes with the dialogue window over a battle, like Leo's fight with Kefka, no longer show stray marks in the screen's first column.
In 1.0
In battle scenes that show the dialogue window over a battle, such as Leo's fight with Kefka at Thamasa, stray marks could show in the screen's first column, level with the window's edges.
Now
No marks show there now.
For ROM hackers 1.0 addresses
user_init fills the window HDMA buffer $7E9A1F, 4 bytes a line (window 1 left and right, window 2 left and right). The loop at C1/1227 gives lines 0 to 150 a window 1 of 8 to 247; the loop at C1/1240 gives lines 151 on, the menu rows, 0 to 247, so window-layer tiles can show in column 0 there. Change the first loop's bound, the cpx #$025C at C1/123B, to $0400, and replace the stz at C1/1240 with bra C1/1257 (2 bytes), past the second loop.
Credit: previously found by a Reddit user, my implementation
Enemy AI and formations
#10 A lone Latimeria or L.90 Magic can no longer pincer the party by itself.
In 1.0
A lone Latimeria or L.90 Magic could start the fight as a pincer attack, which almost every other battle against a single enemy rules out.
Now
Both fights now rule out pincers.
For ROM hackers 1.0 addresses
BattleProp (CF/5900), 4 bytes per formation; byte 0 bit 6 disables the pincer attack. Formation 224 (Latimeria alone) at CF/5C80 and formation 486 (L.90 Magic alone) at CF/6098: set bit 6 of byte 0 in each.
Credit: previously found and fixed by Novalia Spirit, my implementation
#19 The fourth Mag Roader's AI script now has its missing terminator.
In 1.0
The brown Mag Roader in the World of Ruin's Narshe mines ran on into the Wild Cat's script, so when hit it could strike back with a plain attack, or with Fire Ball when it was the last enemy left.
Now
It no longer strikes back when hit, like the other Mag Roaders.
For ROM hackers 1.0 addresses
AI script of monster 243 ($F3, the fourth Mag Roader), CF/9448. Its main section ends with $FF at CF/9451, but the counter section after it has no closing $FF, so the Wild Cat's script, which starts at CF/9452, runs as its counter section. Add $FF after CF/9451 (1 byte). In a binary patch, copy the script with the extra $FF (11 bytes) to free space in the AI script block and change its pointer at CF/85E6, an offset from AIScript (CF/8700).
Credit: previously found by Lord J and everything8215 and fixed by Djibriel and Lord J, my implementation
#61 The last-spell-used record is now only written when the action really was a spell.
In 1.0
Some enemies strike back only when one particular spell hits them, but after that spell had hit once they kept remembering it, so later attacks that were not spells set off the same counter.
Now
An enemy now remembers a spell only when a spell really hit it.
For ROM hackers 1.0 addresses
_c235e3 (C2/35E3) records who last acted on a target. The spell used ($3410) goes to $3D49,y and its caster to $3291,y only when $3410 is not $FF, so after an action that is no spell the old caster is still there and a monster's counter to a spell keeps firing. Insert sta $3291,y and a bra to C2/3601 (5 bytes) after the beq at C2/35F8, and make that beq a bne over them, so $FF (no caster) goes to $3291,y when the action was no spell.
Credit: previously found and fixed by Leet Sketcher, my implementation
#79 The damage-triggered counter no longer stays armed after firing once, answering misses, status spells, heals and buffs.
In 1.0
Some enemies counter only when an attack damages their HP or MP, but after the first such counter the trigger stayed set for the rest of the battle, so they also countered misses, status spells, heals and buffs aimed at them, striking whoever had last damaged them.
Now
Such a counter now answers only an action that actually damaged the enemy.
For ROM hackers 1.0 addresses
The AI condition if_hit (FC 05, C2/1C70) reads $327C,x, the actor who last dealt this monster HP or MP damage. ApplyDmg writes it and nothing clears it, so after one damaging blow every later action satisfies the condition. In ExecAIRetal (C2/4BF4), insert lda #$FF, sta $327C,x (5 bytes) just before C2/4C52, after the counter script has run. The early return to C2/4C52 skips it, and every block of one counter pass still sees the same record.
Credit: previously found and fixed by Master ZED, my implementation
#93 The mine cart ride no longer gives the same Mag Roader fight every time.
In 1.0
The Mag Roader fights on the mine cart ride out of the Magitek Research Facility were meant to vary at random but were the same every time.
Now
The fights now vary at random from one ride to the next.
For ROM hackers 1.0 addresses
Mine cart commands $E0 and $E1, TrainCmd_e0 (EE/327A) and TrainCmd_e1 (EE/32C8), pick one of two Mag Roader formations from RNGTbl indexed by $1F6D and then advance $1F6D. They address it absolute while the data bank does not map low RAM, so they read and bump another byte and the index never moves. Make every access long, $001F6D: the lda at EE/327A and EE/32C8 becomes lda f: (1 byte more each), and the inc at EE/328F and EE/32DD becomes lda f:, inc a, sta f: (6 bytes more each, as inc has no long mode; A is 8-bit there).
Credit: previously found and fixed by Imzogelmo, my implementation
#113 Allo Ver's Rage can now be learned, since its one fight, in the Cave on the Veldt, used a formation the Veldt would not register.
In 1.0
Allo Ver never appeared on the Veldt, so Gau could never learn its Rage, and its one fight, in the Cave on the Veldt, gave no Magic Points.
Now
Allo Ver can now appear on the Veldt after that fight, and the fight gives 1 Magic Point.
For ROM hackers 1.0 addresses
Event battle groups, EventBattleGroup (CF/5000), 4 bytes each: two formation words. Group 153 (CF/5264), the Cave on the Veldt fight with Allo Ver, names formation 515 in both. The Veldt registers no formation from 512 up, so the Rage is never learned. Set both words to 126 ($007E), a formation with the same content below 512.
Credit: previously found and fixed by Master ZED and Assassin, my implementation
#150 Two ways to freeze the game around the last Phunbaba fights in Mobliz are closed.
In 1.0
In the last Phunbaba fights in Mobliz, the game froze after his final defeat if BabaBreath blew away a lone party member felled by their own blow, and froze on a black screen if, with Phunbaba Charmed and the party lifted by Palidor, Terra's arrival sent the leader back to the airship.
Now
A Charmed Phunbaba still plays out his finish, BabaBreath only blows away party members and is skipped when the whole party is down, and the next party member takes the lead if the leader is sent back.
For ROM hackers 1.0 addresses
Charm marks Phunbaba unresponsive, and a counter block then runs only after if_self_dead or if_always, so his ending never runs, and his turned aim lets BabaBreath's target lists hold monsters as well as party slots. Insert jsr MyFix at the head of AttackerEffect_33 (C2/4061), 15 bytes of free space in bank C2. PHUNBABA_3: after if_self_dead at CF/BE31 insert if_num_chars 1, then end_if and if_self_dead before boss_death at CF/BE38; insert if_always before the if_hit at CF/BE3D and if_num_chars 1 before CF/BE45. PHUNBABA_2: if_always before the if_hit at CF/BE15. Repoint both scripts. In _cc4c1b, insert update_party after the call _cacb25 at CC/4CCA, so a leader it removes no longer blocks the new leader's last step.
MyFix: rep #$20
lda #$FF00 ; the monster slots
ora $3A40 ; and characters on the enemy side
trb $A2 ; out of both target lists
trb $A4
sep #$20
rts
Credit: previously found and fixed by Leet Sketcher, my implementation
#151 Chadarnook's goddess now gets her full time on each appearance, so she no longer turns back into the demon at once.
In 1.0
In Owzer's basement, Chadarnook's goddess could turn back into the demon almost at once after appearing, and actions queued against her landed on the demon.
Now
She now stays for her full time or until five blows land.
For ROM hackers 1.0 addresses
Scripts CHADARNOOK_1 (the demon) and CHADARNOOK_2 (the goddess). The goddess leaves on if_monster_timer 40 (CF/B680), but nothing resets that clock when the demon brings her back, so after a return by the five-blow route she can leave at once. Make her clock the battle timer $3A44 and wind it at each hand-over: change $0B (IF_MONSTER_TIMER) to $16 at CF/B681 and $00 (RESET_MONSTER_TIMER) to $04 at CF/B685, and replace the demon's two set_battle_var 3, 0 at CF/AAAD and CF/AAC1 with reset_battle_timer ($FB $04 $00), same size.
Credit: previously found and fixed by RoSoDude, my implementation
#152 The Veldt cave's double SrBehemoth fight now gives two BehemothSuits, instead of one plus a ThunderBlade or, rarely, a Jewel Ring.
In 1.0
In the Cave in the Veldt, the fight with SrBehemoth and its undead form gave a ThunderBlade, or rarely a Jewel Ring, in place of one of the two BehemothSuits both carry.
Now
The fight now gives two BehemothSuits.
For ROM hackers 1.0 addresses
The Veldt cave's SrBehemoth fight changes formation into 424, which holds the undead SrBehemoth in slot 2 and nothing in slot 1. Win data is read for the slot defeated in the first formation, slot 1, and $1FF there reads a wrong item. In BattleMonsters, formation 424's slot 1 monster byte at CF/7ADA: $FF to $19 (monster 281, SrBehemoth; its high bit is already set), present bit left clear. In SRBEHEMOTH_UNDEAD's death block, kill_monsters at CF/9F03 kills all monsters; change its mask byte at CF/9F06 from $FF to $02, its own slot.
Credit: previously found by Master ZED and fixed by Dragonsbrethren, my implementation
#153 Veldt formations no longer put an enemy in the slot Gau needs to return from a Leap.
In 1.0
Gau needs the sixth enemy slot free to return from a Leap, but 42 Veldt formations put an enemy there while a lower slot stood empty, so those fights could never bring him back.
Now
Gau can now return from a Leap in those fights.
For ROM hackers 1.0 addresses
Gau returns from a Leap only into monster slot 6: InitParty tests it at C2/2FB7 and clears $11E4 bit 0 if a monster stands there. BattleMonsters (CF/6200), 15-byte records: flags, present mask, six monster low bytes, six positions, high bits. In each formation the Veldt can record (below 512, not flagged off the Veldt, a monster below 256) with slot 6 filled and a lower slot empty, move slot 6's present bit, low byte, position and high bit to the lowest empty slot; slot 6 becomes $1FF. That is 42 formations in 1.0. TELSTAR's first alarm, restore_monsters at CF/89BD: mask byte at CF/89C0 from $3C to $1E.
Credit: previously found and fixed by Djibriel, my implementation
#181 Kefka at the Sealed Gate, Kefka at the Imperial Camp and Colosseum Shadow now properly load their special attack instead of it always missing with a blank name.
In 1.0
Kefka at the Sealed Gate, Kefka at the Imperial Camp and Shadow in the Colosseum never had their special attack loaded, so when their script called for it the attack always missed and its name window was blank.
Now
Each one's special attack now works and shows its name.
For ROM hackers 1.0 addresses
Sealed Gate Kefka, Imperial Camp Kefka and Colosseum Shadow are characters run by monster AI. Their setup calls InitAI (C2/2D71) but not LoadMonsterProp, which fills $33A8 (monster index for the special), $3C81 (special animation) and $322D (special attack data), so the special always misses with a blank name. Insert after the longa at C2/2D74, where A is the monster number (36 bytes): set those three from it, then reload A. A monster gets the values LoadMonsterProp already gave it.
sta $33a8,y
tax
sep #$20
lda f:MonsterSpecialAnim,x
sta $3c81,y
rep #$20
txa
asl5 ; 32-byte monster record
tax
sep #$20
lda f:MonsterProp+31,x
sta $322d,y
rep #$20
lda $33a8,y ; on to the script pointer
Credit: previously found and fixed by Leet Sketcher, my implementation
#193 Kefka's fight at the Cave to the Sealed Gate lasts past the first hit, until he is down or about half a minute of battle time passes; he stands for his line.
In 1.0
The scripted fight with Kefka in the Cave to the Sealed Gate ended on the first attack aimed at him, and if the party did kill him he lay knocked down through the closing scene.
Now
The fight now ends when he is knocked out.
For ROM hackers 1.0 addresses
AI condition IF_DEAD (C2/1CE4) gets its self mask from GetAITargetMask (C2/1DEE): the monster byte of the actor's mask. The scripted Kefka is character slot 0, so the test is always true. Replace the 9 bytes from C2/1CE6 with jsl to the routine below (33 bytes of free space) and five nop. In AI script 372, add clr_status DEAD before the dlg inside the if_self_dead at CF/BF1B, then if_battle_timer 64 / clr_status DEAD / dlg $4B / end_battle / end_if after it. The 17 new bytes do not fit the block's 7 free, so move the script (69 bytes) to CF/FB29 (free) and repoint AIScriptPtrs (CF/8400), offsets from AIScript (CF/8700).
DeadMask:
lda $3a2e
bne @mon ; operand names monster slots
lda $3019,y
bne @self ; a monster's own bit
lda $3018,y ; a character's own bit
sta $3a2e
lda $3a40 ; characters on the enemy side
eor $3a42 ; that are not alive
rtl
@self: sta $3a2e
@mon: lda $3a73 ; as before
eor $3a75
rtl
Credit: previously found and fixed by SilentEnigma, my implementation
#204 An enemy the Scimitar's cut removes from the battle no longer fires its own counterattack, such as a Nohrabbit's Cure.
In 1.0
An enemy that the Scimitar's cut killed and removed from the battle at once still used its counter to a particular attack, like the Nohrabbit casting Cure.
Now
Such an enemy now stays silent.
For ROM hackers 1.0 addresses
AI counter conditions $03 (item used, C2/1C3B), $02 (attack used, C2/1C3F) and $01 (command used, C2/1C40), which fall through into each other. None asks whether the monster is still in the battle, so one the Scimitar cut down still counters. At each entry, with the monster's index still in Y, insert a test of its Hide bit ($3EF9,y bit 5), which of all kills only ScimitarEffect sets, with Death; when set, clc and rts (condition false). Each entry then continues past the next entry's test instead of falling into it, since Y has changed by then. With a shared 6-byte test routine this is 7 bytes per entry, plus a 2-byte bra in the item and attack entries.
Gone: lda $3ef9,y ; status 4
and #$20 ; hide: left the battle
rts
; item entry at C2/1C3B
jsr Gone
beq :+
clc
rts
: tya ; the original entry, then bra past the
... ; attack entry's test to its iny
Credit: previously found and fixed by Leet Sketcher, my implementation
#211 Rhinox's self-cast Life 3 no longer always fails for lack of MP: its MP is raised from 35 to 50, the spell's cost.
In 1.0
Rhinox, in the Magitek Research Facility, casts Life 3 on itself when it is left alone, but it had 35 MP and Life 3 costs 50, so the spell always failed.
Now
Rhinox now has 50 MP, enough for one cast of Life 3.
For ROM hackers 1.0 addresses
Monster 117 (Rhinox), MonsterProp record at CF/0EA0, MP field (offset $0A, 16-bit) at CF/0EAA: change 35 to 50, the cost of Life 3, which its script casts on itself when alone.
Credit: as in the Pixel Remaster, previously found by W-Crazyswordsman via the Final Fantasy Wiki, my implementation
#215 A petrified enemy's dying counterattack now fires only once instead of every turn.
In 1.0
A petrified enemy with a dying counterattack used it again on every turn.
Now
It now uses it once.
For ROM hackers 1.0 addresses
The Petrify set handler at C2/460B starts with jsr _c246a9 (C2/46A9), which arms the "has died" mark that fires a dying counterattack. InitStatusVars re-asserts Petrify on every pass, so a stone enemy is re-armed every turn. Arm it only for a death arriving now or a Petrify the target does not already carry: insert the test below around the jsr (14 bytes more, 17 in all, A 16-bit). $FC is status 1/2 to set, $F8 the current status 1/2.
lda $fc
and #$0080 ; dead being set: always counts
bne :+
lda $f8
and #$0040 ; already petrified: not again
bne :++
: jsr _c246a9
:
Credit: previously found and fixed by Leet Sketcher, my implementation
#257 A confused KatanaSoul no longer uses its instant death attack on itself and ends the fight early.
In 1.0
A confused KatanaSoul could use its instant-death special on itself, the only enemy on its side, and end the battle at once.
Now
A confused KatanaSoul now hits itself for ordinary damage and the fight goes on.
For ROM hackers 1.0 addresses
MonsterControl (CF/3D00), 4 attack bytes per monster, which is also the list a confused monster picks from at random. Monster 292, KatanaSoul, at CF/4190: its second attack is Special, a kill with no damage. KatanaSoul fights alone, so a confused one aims it at the only monster on its side, itself. Change the byte at CF/4191 to $FF (no attack), which leaves Battle. It cannot be controlled, so nothing else reads the record.
Credit: as in the Pixel Remaster, my implementation
#258 Curley no longer wastes turns casting Life 2 on Larry when Larry has only run off the screen.
In 1.0
In the Dream Stooges fight in Cyan's dream, Curley took Larry for dead while Larry had only run off the screen, and spent his turns casting Life 2 at the empty space.
Now
Curley now leaves Larry alone when he has only stepped away.
For ROM hackers 1.0 addresses
AI scripts of Curley and Larry (the Dream Stooges). Larry hides himself with hide_monsters; a hidden monster is present but not alive, which is what Curley's if_monsters_dead MONSTER_2 tests, so Curley casts Arise at him every turn. Use battle switch 0 bit 0 as "Larry is away" (12 bytes; later scripts and their pointers move). In Curley's script, insert if_battle_switch_clr 0, 0 (FC 15 00 00) before the if_monsters_dead MONSTER_2 at CF/AB0F. In Larry's, insert set_battle_switch 0, 0 (F9 01 00 00) before the reset_monster_timer at CF/ABB3 where he runs away, and clr_battle_switch 0, 0 (F9 02 00 00) before the end_if at CF/AB98 where he comes back.
Credit: previously found by Master ZED and Xenomic via the Final Fantasy Wiki, my implementation
#259 Doom Gaze killed by a Doom or Condemned timer instead of a hit now still gives its Bahamut magicite.
In 1.0
The Bahamut magicite scene after the Doom Gaze fight played only if Doom Gaze died while answering an attack, so if a Doom or Condemned countdown killed it, no magicite came and Bahamut was lost for the rest of the game.
Now
The Bahamut scene now follows however Doom Gaze died.
For ROM hackers 1.0 addresses
UpdateSRAM, C2/4936, where Doom Gaze's HP ($3BF4,x) is saved to $3EBE at C2/49CD for his next encounter. The world map gives Bahamut only when $3EBD bit 0 (battle switch 13) is set, and only Doom Gaze's own if_self_dead counter block sets it; a counter answers an attack, so a death from a Doom or Condemned timer leaves it clear and the magicite is lost. Replace that lda/sta (6 bytes, 16-bit A) with jsl MyFix and two nop. MyFix (24 bytes of free space) does the save and sets $3EBD bit 0 when his status 1 ($3EE4,x) has Wound.
jsl MyFix ; replaces lda/sta at C2/49CD
nop
nop
MyFix: lda $3bf4,x ; doom gaze's hp
sta $3ebe
sep #$20
lda $3ee4,x
bpl @alive ; bit 7: wound
lda $3ebd
ora #$01
sta $3ebd
@alive: rep #$20
rtl
#323 Enemy AI timers no longer keep counting during Quick or while an actor is stopped, so timed script branches no longer come early.
In 1.0
Enemies that act after a set time kept counting during Quick, and a stopped enemy kept counting too, so timed moves came early.
Now
Enemy timers now stand still during Quick, and a stopped enemy's own timer stands still too.
For ROM hackers 1.0 addresses
GlobalCounter_01, C2/5BFC, every 16 frames: it increments the battle timer $3A44 and each present actor's timer, skipping only Wound and Petrify; AI timer conditions read these. Two inserts. After the php at C2/5BFC, in 8-bit A (5 bytes): lda $3404, bpl to the plp at C2/5C19, so neither timer moves during Quick. Before the inc at C2/5C12, in 16-bit A (8 bytes): lda $3EF8,x, bit #$0010, bne to C2/5C15, so a Stopped actor's timer holds.
@5bfc: php
lda $3404 ; quick running?
bpl @plp ; the plp at C2/5C19
longa
; ... as 1.0 up to the petrify/wound test
lda $3ef8,x ; status 3
bit #$0010 ; stop
bne @5c15
inc wTargetProp2::MonsterTimer,x
Credit: previously fixed by RoSoDude, my implementation
#415 Doom in Kefka's Tower now checks his HP on his own turn too, so he no longer skips his second phase.
In 1.0
In Kefka's Tower, Doom checked for his second phase only when something hit him directly, so if he lost HP any other way he skipped that phase.
Now
He now also checks his HP on his own turn, so he enters the second phase however the damage came.
For ROM hackers 1.0 addresses
Doom's AI script, main part at CF/ADCB. His second phase (Image, Reflect and Haste, monster switches 0 and 1) runs only in the counter part under if_hit, the block at CF/AE30, and no damage he deals himself sets that off. Insert at the head of the main script a copy of that block, the 38 bytes from if_hp SELF, 32640 at CF/AE30 through set_monster_switch 1, plus end_if: 39 bytes. The AI block (AIScript, CF/8700) has 7 spare bytes in 1.0, so move the grown script (179 bytes) to the free bytes at CF/FB29 to CF/FBFF and set Doom's AIScriptPtrs (CF/8400) entry to $7429, its offset from CF/8700.
Credit: previously found by Novalia Spirit, my implementation
#416 The Veldt's Dried Meat scene now waits for both Sabin and Cyan to be standing before it plays.
In 1.0
On the Veldt, using Dried Meat on Gau played his recruitment scene even when Sabin or Cyan had fallen, or Sabin had left the fight with Spiraler, and both still spoke their lines.
Now
The scene now waits until both are standing, and until then Gau runs off, as he does after any other hit.
For ROM hackers 1.0 addresses
Gau's Veldt AI script, counter part: if_item DRIED_MEAT at CF/BEC2 and if_battle_switch_clr 13, 1 lead to recruit_gau at CF/BECA and the GAU_INTRO scene, with no test that Sabin and Cyan can speak. Insert before that recruit_gau (16 bytes) four conditions: if_level_greater SABIN, 0 / if_status_clr SABIN, DEAD / if_level_greater CYAN, 0 / if_status_clr CYAN, DEAD. When one fails, the if_hit block after it plays GAU_RUNS_AWAY. The AI block has no room for the 65-byte grown script (#415): move it, or other scripts, to free space in bank CF and repoint what moves in AIScriptPtrs.
Credit: previously found by Novalia Spirit, my implementation
#417 Relm now joins the Ultros fight whenever he dies, instead of only when a hit drops him below a set HP while active.
In 1.0
At the Esper Mountain, Relm joined the Ultros fight only when a hit took him below a set HP, so if he died while stopped, frozen, Muddled, Berserk or Charmed she never came in.
Now
His death now brings her in as well, so she always joins.
For ROM hackers 1.0 addresses
Ultros's AI script on the Esper Mountain, counter part at CF/B1F7. Relm enters (battle_event RELM_ULTROS_INTRO, battle switch 0 bit 4) only under if_hit and if_hp SELF, 10240, which a stopped, frozen, Muddled, Berserk or Charmed Ultros never runs. Insert at CF/B1F7, before that if_hit (19 bytes): if_self_dead / if_battle_switch_clr 0, 4 / battle_event RELM_ULTROS_INTRO / set_battle_switch 0, 4 / kill_monsters ALL, SAND / end_if. The AI block has no room (#415), and the grown script (257 bytes) is larger than bank CF's largest free run: move other scripts out of the block to make room, and repoint what moves in AIScriptPtrs.
Credit: previously found by Novalia Spirit, my implementation
#514 Chaos Drgn in the Phoenix Cave now actually acts, instead of its leading wait freezing its AI script forever.
In 1.0
Chaos Drgn in the Phoenix Cave never took an action for the whole fight.
Now
It now acts as its script says.
For ROM hackers 1.0 addresses
DoNextAICmd (C2/1AB4) re-reads the script's first command every turn. Only an ordinary command reaches the jump to the saved resume point at C2/1ABF; the cmp #$FC at C2/1ABB sends a wait ($FD) to the top-of-script path, so a script that opens with a wait waits for ever. Insert before C2/1ABB (4 bytes): cmp #$FD, beq to C2/1ABF. Monster 226 (Chaos Drgn) has the only script in the game that starts with a wait.
Credit: as in the Pixel Remaster, previously found by ffdic, my implementation
#539 Back, pincer and side attacks now come as often as each fight's data says; battles drew their random numbers from only 60 of 256 starting places.
In 1.0
Battles drew their random numbers from only 60 of 256 starting places, so back, pincer and side attacks came at the wrong rates.
Now
Those attacks now come as often as each fight's data says.
For ROM hackers 1.0 addresses
InitBattle, C2/23ED. The random seed for $BE, loaded at C2/2440, is the frame counter ($021E, 1 to 60) times 4, so only 60 of the 256 starting places in the random table occur, and the battle-type roll a fixed number of draws later sees only 60 outcomes. Replace the lda $021E and its two asl (5 bytes) with a jsl and a nop to a routine in free space (55 bytes) that returns the play time in frames cut to a byte: $021E + 60 x $021D (seconds) + 16 x $021C (minutes) + 192 x $021B (hours), mod 256, since a minute is 16 and an hour 192 frames mod 256.
Credit: previously found by Assassin, my implementation
#543 An enemy killed by X-Zone, Snare, Odin, Raiden, Cleave or X-Fer no longer makes its final attack one action later; partners still come back and bosses still end the fight.
In 1.0
An enemy killed by X-Zone, Snare, Odin, Raiden, Cleave or X-fer still ran its dying script, final attack included, after the next action of anyone.
Now
The dying script now runs without its attacks.
For ROM hackers 1.0 addresses
Special effect $23 (X-Zone, Snare, Odin, Raiden, Cleave, X-Fer) zeroes $341A so no counter answers the action, but a monster it kills keeps its died bit in $3A56, and CheckRetal (C2/4C5B) queues its dying pass after the next action of anyone. Mark such deaths in $3A50 (unused in 1.0) at the head of CheckRetal; in the AI's attack command, after the pla at C2/1B3A, skip the action (on to its jmp DoNextAICmd) in a counter pass ($B1 bit 0) of a marked actor; clear the mark at the end of ExecAIRetal (C2/4BF4), before the plp at C2/4C52 on the fall-through path. Other script commands still run. Three jsl inserts plus a bcs, 44 bytes of free space.
QuietMark: php ; jsl at C2/4C5B
lda $341a
bne @done ; counters allowed
rep #$20
lda $3a56
tsb $3a50
@done: plp
rtl
QuietSkip: pha ; 16-bit a, x = the actor
lda $b1
lsr
bcc @done ; not a counter pass
lda $3018,x
and $3a50
cmp #1 ; carry set: skip
@done: pla
rtl
QuietEnd: php ; jsl before the plp at C2/4C52
rep #$20
lda $3018,x
trb $3a50
plp
rtl
Credit: previously found by Master ZED, my implementation
#564 An enemy's counter to a spell bounced off several party members with Reflect no longer always strikes the first of them, but the reflector the game picks as attacker.
In 1.0
When a spell cast at the party bounced off several party members with Reflect onto one enemy, a counter that answers magic, a command, an item or an element always struck the first reflector in party order, whoever had cast the spell.
Now
The counter now strikes one of the reflectors at random.
For ROM hackers 1.0 addresses
CalcReflectDmg, C2/3483. Each bounce is recorded in turn, so the counter records ($3290 command, $3291 spell, $32A4 item, $32A5 element) end on the last reflector processed, while $32E0 (bit 7 hit, low bits the attacker) names one picked at random. Insert jsl MyFix (4 bytes) after the bpl at C2/3511. MyFix (52 bytes) saves P, sets 8-bit A and index, and for x = 9 down to 0: skip $A0,x = $FF; y = $A0,x times 2; if $32E0,y bit 7 is set, store its low bits times 2 in $3290,y and $32A5,y, in $3291,y when $3410 is not $FF, and in $32A4,y when $3411 is not $FF.
Credit: previously found and fixed by Assassin, my implementation
#565 An enemy killed by its own Poison or Seizure right after a party hit no longer dies without its final attack, such as the Colosseum Retainer's Tradeoff.
In 1.0
An enemy killed by its own Poison or Seizure during the animation of a party hit that set up its counter could die without its final attack, as the Colosseum Retainer did without its Tradeoff; beside other enemies the final attack came only after the next action.
Now
Its dying script now runs as when anything else kills it.
For ROM hackers 1.0 addresses
ExecAIRetal, C2/4BF4. It takes the batch's one counter chance (trb $33FC at C2/4BFD, return to C2/4C52 if already spent) before the death mark (trb $3A56 at C2/4C02, to the dying pass at C2/4C28). A counter that ran just before the monster's own Poison or Seizure tick killed it had spent the chance, so the dying pass returned with the mark still set. Swap the two pairs so the death mark is tested first: the same 10 bytes reordered, with both branch offsets recomputed. A dying pass then leaves the chance alone, and adds no pass, since a counter batch queues only dying counters.
Credit: previously found by DAMIDDLEFINGER and Assassin and fixed by Brave New World, my implementation
#610 With Reflect on the party, two Cranes whose spells bounce onto each other no longer counter each other's counters.
In 1.0
With Reflect on the party, when a Crane's spell bounced onto both Cranes, the two countered each other's counters.
Now
A Crane's counter no longer answers the other Crane's counter; it still answers the Crane whose spell bounced onto it.
For ROM hackers 1.0 addresses
ApplyDmg, C2/12F5. When damage lands on a target other than its attacker, the sta at C2/1313 records the attacker in RetalTarget ($327C,y). The counter script reads it when the queued counter runs (ExecRetal, C2/4B7B): AI condition $05 (IF_HIT, C2/1C70) takes it as the target. CheckRetal already queues no counter for a counter's damage (the bcs at C2/4C95), but a counter's strike still overwrites the record, so a monster with a counter queued answers that strike instead of the attack it was queued for. Skip the store while $B1 bit 0 (a counterattack runs) is set: insert pha, lda $B1, lsr, pla, bcs over the sta (7 bytes, 8-bit accumulator; pla leaves the carry alone).
pha
lda $B1 ; bit 0: a counterattack runs
lsr
pla
bcs Skip
sta $327C,y ; the original store
Skip:
Credit: previously found by Assassin and Lenophis, my implementation
Menus and screens
#11 The Skills menu no longer shows Ragers after picking Espers then Rage.
In 1.0
Choosing Espers and then Rage in the Skills menu showed 'Ragers' above the list.
Now
Each heading now replaces the previous one completely.
For ROM hackers 1.0 addresses
Skills menu, the small title window. The titles SkillsLoreTitleText to SkillsBlitzTitleText (C3/5C91 to C3/5CB0) are drawn at the same spot and nothing clears the one before, so a shorter title leaves the tail of a longer one (Espers, then Rage). Pad each title with spaces to seven characters (Lore and Rage +3, Dance and Blitz +2, Espers +1, 11 bytes in all). The strings sit back to back: write the padded set to free space in bank C3 and change the ldy #near that loads each, at C3/5203 (Lore), C3/539B (Rage), C3/577E (Dance), C3/5460 (Espers) and C3/55DE (Blitz).
Credit: previously found by everything8215 and vivify93 and fixed by Madsiur, my implementation
#12 The scroll arrows beside the battle Lore list now follow the English one-column layout.
In 1.0
The scroll arrows beside the English battle Lore list were set for the Japanese two-column list, so after eight scroll steps only the up arrow showed, as if the list had ended, and at the real end both arrows still showed.
Now
The down arrow now shows until the real end, where only the up arrow shows.
For ROM hackers 1.0 addresses
Battle menu state $1B, lore select (C1/8308). The list itself scrolls to the English limit, 20 (the cmp at C1/8336), and the scroll bar step loaded by the ldx at C1/8393 is derived from 20 too. The arrows get their limit from the lda # at C1/838F, stored to $36 before UpdateMenuScrollArrows (C1/898F): NUM_LORE / 2 - 4 = 8, the Japanese two-column value. Change that immediate from 8 to 20 ($14), in place.
Credit: previously found by everything8215, my implementation
#66 The MP display no longer blanks the tens digit when a hundreds digit is present.
In 1.0
When Step Mine's cost reached 100 to 109 MP, between 50 and 55 hours of play, the battle's Lore list blanked the middle zero and showed a gap.
Now
The middle zero now shows.
For ROM hackers 1.0 addresses
nmi_player_mp_set (C1/050C) draws the MP cost: $40 and $41 hold the hundreds and tens digits, 8 is added to make each a tile, and a zero digit is blanked. The bne at C1/057A, taken when the hundreds digit is not zero, goes on to the tens test, so a zero tens digit is blanked under a hundreds digit too. Change its target to the rts at C1/0590 (one offset byte), so the tens digit is blanked only after a blank hundreds digit.
Credit: previously found and fixed by Assassin, my implementation
#106 Betting the Striker no longer shows both the opponent's name and Shadow's overlapping.
In 1.0
Betting the Striker at the Colosseum showed both Chupon's name and Shadow's on the preview screen, one just below the other.
Now
The preview now shows only Shadow's name.
For ROM hackers 1.0 addresses
DrawColosseumCharWindow, C3/AE34. _c3b28d (C3/B28D) stores the opponent-name position ({3, 16} on BG3 screen A) in $7E9E89 and draws the monster's name; then CheckColosseumShadow (C3/AE93) sets $0201 for the Striker, and DrawColosseumShadow (C3/AEA7) draws Shadow's name at {4, 15}, from the ldy_pos at C3/AEFA, so both show. Move the jsr CheckColosseumShadow at C3/AE7F up to before the jsr _c3b28d at C3/AE67. In _c3b28d, insert tay (1 byte) after the sta $7E9E89, and after the shorta at C3/B296 insert lda $0201 / beq past / jmp DrawColosseumShadowName (C3/AED9) (8 bytes). Remove the jsr at C3/AEA7 and the ldy_pos at C3/AEFA.
Credit: previously found and fixed by Novalia Spirit, my implementation
#122 The battle menu now shows the right command when Offering turns Fight into four random attacks.
In 1.0
A party member wearing the Offering makes four attacks at random targets when told to Fight, but the battle menu still showed Fight.
Now
The command now reads X-Fight while the Offering is worn.
For ROM hackers 1.0 addresses
MenuTextCmd_0d, C1/69EC, draws a command slot's name from BattleCmdName (D8/CEA0, 7 bytes a name). Offering (relic effect 4, bit 0, $3C58 per character) turns Fight into four random attacks, but the slot still names Fight. Write X Fight into battle command name 30 ($1E), which 1.0 leaves blank, at D8/CF72. Insert jsl MyFix (4 bytes) before the xba at C1/69EC. MyFix (25 bytes of free space, any bank): when the command is Fight and the character in $62CA wears an Offering, return $1E instead.
jsl MyFix ; inserted before the xba at C1/69EC
MyFix: cmp #$00 ; Fight?
bne @done
lda #0
xba
lda $62ca ; character whose menu this is
asl
tax
lda $3c58,x ; relic effect 4
and #$01 ; Offering
bne @xfight
lda #$00
@done: rtl
@xfight:
lda #$1e ; name 30, X Fight
rtl
Credit: previously found and fixed by Leet Sketcher, my implementation
#123 The Magic menu no longer drops the MP label beside a summon's cost.
In 1.0
Pressing Up in the battle Magic menu to reach the equipped Esper showed the summon's cost as a bare number, with no MP label.
Now
The MP label is back beside the summon's cost.
For ROM hackers 1.0 addresses
SummonMagicListText, C2/E083, the text that draws the Magic list below the summon window. The two letters of the MP label beside the cost are written as the wrong tile codes, so they come out blank. Change $2C to $8C at C2/E093 and $2F to $8F at C2/E095 (M and P).
Credit: previously found and fixed by Lenophis, my implementation
#124 Two enemies with the same long name now group together in menus instead of listing separately.
In 1.0
Two enemies with the same long name could each get a line of their own in the battle's list of enemy names, and two different names could even be merged, leaving one out.
Now
Same-named enemies now share one line, and different names each keep theirs.
For ROM hackers 1.0 addresses
LoadMonsterProp, C2/2C30. The duplicate name check indexes MonsterName (CF/C050) by monster number times 8 and compares 8 bytes, but US names are 10 bytes, so it compares the wrong text. Rewrite C2/2C49 to C2/2CA0 in place (the same size by instruction lengths): drop the asl at C2/2C49 and the 8-byte copy loop from C2/2C54, and point $F8-$FA at this monster's name instead; index the other name by 10 at C2/2C6F; compare with lda [$F8],y at C2/2C75 and cpy #10 at C2/2C83; at C2/2CA0 the pushed index is now times 4, so asl2 becomes asl3.
pla ; C2/2C48: index * 4, the asl dropped
; pha, phx, phy as 1.0; the tax dropped
clc
adc $1ff9,y ; index * 5
asl ; index * 10
clc
adc #.loword(MonsterName)
sta $f8
lda #.bankbyte(MonsterName)
sta $fa
; ...C2/2C4E and C2/2C51 unchanged, copy loop removed
asl2 ; C2/2C6F, the other monster
clc
adc $1ff9,y
asl
tax
; ...
lda [$f8],y ; C2/2C75
; ...
cpy #10 ; C2/2C83
Credit: previously found and fixed by Assassin, my implementation
#147 Stripping equipment from a party member outside the active party now recalculates their stats correctly.
In 1.0
After an event stripped the equipment from a party member outside the active party, their HP or MP could show the wrong value following a later lineup change.
Now
Their HP and MP now show correctly.
For ROM hackers 1.0 addresses
EventCmd_8d (C0/9FCE), event command $8D (remove_equip). After emptying the slots it calls the field UpdateEquip at C0/A032, and that routine (C0/6CF3) recalculates only characters in the current party, so a character outside it keeps stats from the old equipment. Insert before the lda #2 at C0/A035 (6 bytes): lda $EB (the character) and jsl UpdateEquip_ext (C2/0006), which reloads that character's derived stats.
Credit: previously found and fixed by Leet Sketcher, my implementation
#167 Using a Tent, Warp Stone or Rename Card now updates the item menu's remembered cursor position.
In 1.0
With Config set to remember the cursor position, reopening Item after using a Tent, Warp Stone or Rename Card put the cursor back where it had been before.
Now
Reopening Item now puts the cursor where you left it.
For ROM hackers 1.0 addresses
Menu state ITEM_SELECT, C3/1EF7. The remembered item cursor $022F and scroll $0231 are stored (from $4F and $4A) only in the B button branch, by the 10 bytes at C3/1F15, and a Tent, Warp Stone or Rename Card closes the menu without passing it. Move those 10 bytes to just before the jsr InitItemDesc at C3/1F09, so they run every frame the list is up.
Credit: previously found and fixed by Novalia Spirit, my implementation
#168 A relic that renames a command now shows the new name in Gogo's command list too.
In 1.0
Gogo's Status screen always showed the plain command names, so with a Thief Glove on it read "Steal" while battle offered him "Capture".
Now
It now shows the names his relics give the commands, as battle does.
For ROM hackers 1.0 addresses
DrawStatusGogoWindow (C3/5DC1) lists Gogo's commands from the party's records, which hold base commands, and DrawCmdNameGogo (C3/5ED7) draws them as they are, so a relic's new name never shows. The list bytes must stay base commands, since the one picked is stored in Gogo's record. Insert jsr MyFix before the jsr DrawCmdNameGogo at C3/5EC0 (42 bytes of free space in bank C3). It looks the command up in RelicCmdTbl1 (C3/6198) and, if the matching relic bit of $11D6 is set, returns the RelicCmdTbl2 (C3/619D) entry. Needs #173, which loads Gogo's own relic bits into $11D6 before the list is drawn.
MyFix: sta $E0 ; the command to draw
lda #$40 ; relic bit of entry 0 (Thief Glove)
sta $E1
ldx $00
@find: lda f:RelicCmdTbl1,x
cmp $E0
beq @worn
lsr $E1 ; bits run down in table order
inx
cpx #5
bne @find
lda $E0 ; no relic renames it
rts
@worn: lda $11D6 ; relic effects 2
and $E1
bne @rename
lda $E0 ; relic not worn
rts
@rename:
lda f:RelicCmdTbl2,x
rts
Credit: previously found and fixed by Leet Sketcher, my implementation
#169 A description cut short by opening an item's details screens now carries on where it stopped, instead of starting over.
In 1.0
Opening an item's details screens before the Item list finished typing out a description left it half written, and back in the list it was wiped and typed again from the first letter.
Now
The description now carries on from where it stopped.
For ROM hackers 1.0 addresses
BigTextTask (C3/A80E) types the item description a letter a frame. Opening the details screens and coming back resets it, so a half-typed line is wiped and typed again. Add a hold in one free direct-page byte (the menu leaves $D2 to $DF unused; $D2 below): _c3858c (C3/858C), entering the details screens, sets it (inc before the rts at C3/8594); _c389de (C3/89DE), back in the list, and ClearBigTextBuf (C3/A796), at its head, clear it (stz, 2 bytes each). While it is set BigTextTask runs no state and keeps the task (9 bytes).
; BigTextTask (C3/A80E), inserted before its tax
pha
lda $D2 ; the hold
bne @held
pla ; on to the tax and the state jump
; ...
@held: pla
sec ; keep the task
rts
Credit: previously found and fixed by SilentEnigma, my implementation
#170 Optimum no longer falls back to equipping whatever happens to occupy an unrelated inventory slot when nothing fits.
In 1.0
When nothing fit a slot, Optimum equipped whatever sat in the last inventory slot, whatever it was.
Now
Optimum now leaves that slot empty.
For ROM hackers 1.0 addresses
GetBestEquip (C3/9819), Optimum's pick for a slot, walks the list of inventory positions at $7E9D8A for the first entry that is not an imp item and never compares against the list's count in $7E9D89. With an empty list it reads stale entries and equips what sits in inventory slot 255. Insert at C3/9821, before the clr_ax, a test of Y against the count (6 bytes; the data bank is $7E here) that branches to a new exit (5 bytes) returning $FF, empty. GetBest2Hand (C3/983F) already counts, and its fallback enters GetBestEquip from the top.
; GetBestEquip, at @9821
@9821: tya
cmp $9d89 ; number of items in the list
bcs @none
clr_ax ; the loop as before
...
@none: plb
ply
lda #$ff ; empty
rts
Credit: previously found and fixed by Kejardon, Lenophis and Imzogelmo, my implementation
#172 The compact battle magic menu skipped three unlearned spells at a time instead of two.
In 1.0
The Magic list in battle skipped unlearned spells three at a time, but the English list has two columns, so it could leave blank rows.
Now
It now drops every row of two with no learned spell in it.
For ROM hackers 1.0 addresses
InitSpellList (C2/5551) places each known spell in $11A0 by the spell order tables, then packs the list back into the master spell list at $3034, dropping rows with no known spell. The packing loop at C2/55F0 still tests and copies three cells a row, the Japanese layout, while the US menu has two columns. Rewrite the loop in place for two cells a row as below; it is shorter, so pad with nop after the bcc.
@55f0: lda $11a0,x
and $11a1,x
cmp #$ff
beq @next ; both cells empty
lda $11a0,x
sta $3034,y
lda $11a1,x
sta $3035,y
iny2
@next: inx2
cpx #54
bcc @55f0
Credit: previously found and fixed by Novalia Spirit, my implementation
#173 Gogo's Status screen showed command availability from whichever party member you last viewed instead of Gogo's own equipment.
In 1.0
Gogo's Status screen greyed out commands by the equipment of the party member you had viewed last instead of his own.
Now
It now checks Gogo's own equipment.
For ROM hackers 1.0 addresses
DrawStatusGogoWindow (C3/5DC1) lists the commands Gogo may take and colours each through CheckCmdEnabled (C3/5F25), which reads the weapon flags at $11DA and $11DB that UpdateEquip last built, so they belong to whichever character's screen was open before. Insert after the jsr DrawWindowFar at C3/5EAA (6 bytes): lda #$0C (Gogo) and jsl UpdateEquip_ext (C2/0006), which rebuilds those flags from Gogo's own record.
Credit: previously found and fixed by Assassin, my implementation
#174 Returning from an Espers subscreen left the scrolling cursor eight pixels too high.
In 1.0
After going back from an Esper's page to the Espers list, whether by equipping it or with B, the scroll indicator came back eight pixels too high.
Now
It now returns at its usual height.
For ROM hackers 1.0 addresses
_c35913 (C3/5913) redraws the Espers list on the way back from a subscreen and creates its scroll arrow task, storing $0060 into the task's wTaskProp::SpeedX_H, which ScrollArrowTask adds to the arrow's vertical position. The first entry to the list (C3/20B3) stores $0068 there, so after a return the arrow sits 8 pixels too high. Change the operand of the lda at C3/592A to $0068 (in place).
Credit: previously found and fixed by Novalia Spirit, my implementation
#176 Closing a shop's buy menu on the very first frame drew it wrong and could freeze the game when reopened.
In 1.0
Leaving a shop's buy menu on the very first frame it allowed drew the shop's top menu wrong and left party member sprites behind, and enough of them could freeze the game on the way back into the buy menu.
Now
The shop's top menu now draws correctly and going back in is safe.
For ROM hackers 1.0 addresses
Same routine as #177 (the remover of CharIconTask tasks). _c3b760 (C3/B760) reloads the shop's top page and only clears z47, the flag of the character icon task group. A task created on the buy menu's last frame has not run its state 0 yet, which sets z47 bit 0 again, so the group survives and tasks pile up until InitTask finds no free slot. Insert a jsr to that routine (3 bytes) after the stz z47 at C3/B764.
Credit: previously found and fixed by Novalia Spirit, my implementation
#177 Going in and out of an ability list from the Skills page many times no longer freezes the game.
In 1.0
With two status icons showing, going from the Skills page into an ability list such as Magic and back about twenty times froze the game.
Now
Any number of trips now works.
For ROM hackers 1.0 addresses
ReloadSkillsMenu (C3/29A5) redraws the Skills page on the way back from an ability list, and the redraw creates a CharIconTask (C3/7B42) for each status the character shows without removing the set made on the previous visit. Every round trip adds tasks; their priority level has 40 slots, and once they are full InitTask loops forever at C3/11AD. Insert a jsr before the jsr _c34d27 at C3/29B2 to the routine below (42 bytes of free space in bank C3), which clears every task whose code pointer is CharIconTask.
RemoveIconTasks:
phb
lda #$7e
pha
plb
ldx zZero
longa
@loop: lda near wTaskProp::CodePtr,x
cmp #near CharIconTask
bne @next
stz near wTaskProp::CodePtr,x
stz near wTaskProp::State,x
stz near wTaskProp::w7e35c9,x
stz near wTaskProp::Pal,x
dec zNumTasks
@next: inx2
cpx #$0080
bne @loop
shorta
plb
rts
Credit: previously found and fixed by SilentEnigma, my implementation
#178 Gogo's Status screen showed a stray colored streak from his portrait poking through the command window.
In 1.0
Gogo's Status screen showed a thin red and yellow streak across his command window, from his portrait.
Now
The streak is gone.
1.0
Now
For ROM hackers 1.0 addresses
_c35f50 (C3/5F50) gives six rows of the BG2 tilemap high priority so Gogo's command window covers his portrait. The US version moved the portrait down one row, and the start row stayed at 6, so the portrait's bottom line shows through. Add $40 (one tilemap row) to the ldx immediate at C3/5F50, so the rows start at row 7 (in place).
Credit: previously found by Assassin and everything8215 and fixed by Assassin, my implementation
#180 Putting a Cherub Down on Shadow, or removing one, no longer erases Interceptor permanently.
In 1.0
Putting a Cherub Down on Shadow in the menu, or taking one off, lost Interceptor for good.
Now
Shadow now keeps Interceptor.
For ROM hackers 1.0 addresses
_c391c4 (C3/91C4) applies the Cherub Down's float to the character record when relics change. At C3/91DC it builds $81 or $00 and stores the whole status 4 byte ($0015,y), which also holds Interceptor (bit 6), so Shadow's dog is wiped. Replace the 15 bytes from C3/91DC up to the rts at C3/91EB with the code below, which is 18 bytes, so insert 3: keep the byte's other bits and set or clear only bits 7 and 0.
@91dc: lda $0015,y
and #$7e ; keep everything but float's two bits
pha
lda $3034
lsr ; carry = the relic's float
pla
bcc :+
ora #$81
: sta $0015,y
Credit: previously found and fixed by Novalia Spirit, my implementation
#188 MP costing critical weapons and MP restoring items now recheck the magic menu, so spells no longer stay wrongly lit or greyed out.
In 1.0
Weapons that spend MP on critical hits, such as Illumina, Ragnarok and Ogre Nix, did not refresh the spell lists in battle, so spells the wielder could no longer afford stayed lit and failed when picked. Tapir and Pep Up restored MP just as quietly, leaving affordable spells greyed out.
Now
Both now update the spell lists at once.
For ROM hackers 1.0 addresses
Menus recheck affordable spells when $3204,y bit 7 is set. AttackerEffect_07 (C2/3F22), the MP-for-critical effect (Ogre Nix falls into it), subtracts MP without setting it. Insert lda #$0080 / jsr SetCharFlag (C2/464C) after the trb zb2 at C2/3F4D (6 bytes, A is 16 bit), on the fall-through only: the no-MP exits branch to the rts at C2/3F4F. Tapir (TargetEffect_17, C2/3CA2) and Pep Up share a tail that sets MP to max; insert the same two instructions before its rts at C2/3CB7 (6 bytes).
Credit: previously found and fixed by Assassin, my implementation
#200 The Equip, Relic and Status screens no longer show double Battle Power for a two-handed weapon worn with the Gauntlet.
In 1.0
With the Gauntlet, the Equip, Relic and Status screens showed a two-handed weapon's Battle Power doubled, though the battle's damage formula never doubles it.
Now
The screens now show one and three quarters of the weapon's Battle Power, the figure the damage formula uses.
For ROM hackers 1.0 addresses
CalcNewAttackPower (C3/9371) and CalcOldAttackPower (C3/93AB), used by the Equip, Relic and Status screens. With the Gauntlet flag ($A1, or $A0 for the saved weapons) they copy the armed hand's Battle Power into the empty hand and the sum shows double. The battle uses BP + (BP + BP/2) / 2. Replace the copy code at C3/9375 and C3/93AF with a bra to the add. Insert before each rts (C3/93AA, C3/93E4): if the flag is set, pass the 8-bit sum to the routine below and store the 16-bit result in $F3 (or $F1). The Status screen re-adds the hands after its jsr at C3/5FFF: remove C3/6002 up to the jsr HexToDec5 at C3/6010.
GauntletBP: ; in: A (8-bit) battle power
pha
clr_a
pla ; high byte zero
longa
pha
lsr
clc
adc 1,s ; BP + BP/2
lsr
clc
adc 1,s ; BP + (BP + BP/2) / 2
sta 1,s
pla ; out: A (16-bit)
rts
Credit: previously found and fixed by Assassin, my implementation
#208 The status window during target selection no longer goes blank for a party member in Magitek Armor or with Interceptor active.
In 1.0
While choosing a target, the status window could go blank for a party member or enemy whose first status has no name, such as Magitek, Morph, Control or Interceptor, and Shadow's Life 3 never showed while Interceptor was active.
Now
The window now skips statuses with no name and shows the first one that has a name, so Shadow's Life 3 appears.
For ROM hackers 1.0 addresses
GetFirstStatus, C1/4587, which picks the one status named in the target-select status window: the first set bit of status 1/2, else of status 3/4. Magitek, Dance, Rage, Frozen, Morph, Control, Hide and Interceptor have no name but still win the search, so the window goes blank. Insert and #$FFF7 (drops Magitek, 3 bytes) after the lda at C1/4589, and and #$84FE (drops the other seven, 3 bytes) after the lda at C1/459F. A is 16-bit at both.
Credit: previously found and fixed by Leet Sketcher, my implementation
#427 Fixed a save-menu slot mixup on a same-frame A-and-Down press, and two ending glitches: Setzer's card entry and Gogo's helmet-eye timing.
In 1.0
On the save screen, pressing A and Down on the same frame, on a used slot above an empty one, overwrote the used slot with no prompt. In the ending, Setzer's first card flew in from near the top of the screen, and Gogo's helmet eye glinted about a second late.
Now
The save now goes to the empty slot, Setzer's card starts from the right place and Gogo's eye glints on time.
For ROM hackers 1.0 addresses
(a) Save select, menu state $14 at C3/2540: the slot is stored from the cursor ($4B) before jsr UpdateGameSaveCursor (C3/15AA) at C3/2548 moves it. Move that jsr to the head of the state, before the lda at C3/2540 (same size). (d) Ending, Gogo 1 at C3/E432: the 180-frame wait is loaded with lda at C3/E44D instead of stored; change it to sta, same operand (long indexed, opcode $BF to $9F). InitSetzerCardsAnim, C3/E8E3: the first card's y position goes to PosX_H; change the operand of the sta at C3/E8EE from $7E33CA to $7E344A (PosY_H).
Credit: previously found and fixed by Novalia Spirit, my implementation
#487 The field Magic list's MP cost box and the Relic screen's stat changes now update reliably even on a quick direction tap.
In 1.0
In the field Magic list, a quick tap of the d-pad could leave the MP cost box showing the cost of the spell the cursor had just left, and the Relic screen's yellow numbers could lag the same way.
Now
The cost box and the relic numbers now keep up with the cursor.
For ROM hackers 1.0 addresses
BigTextTask, C3/A80E. The description task writes one letter a frame, starts over after the last, and queues its text upload in DMA slot 2 with jsr TfrBigTextGfx (C3/A991) at C3/A888 on every letter. The Magic list and the equip screen give that slot to a bg3 tilemap (the MP cost, the stat numbers) on odd frames, so the tilemap is overwritten. Replace that jsr with a jsr to the routine below (24 bytes of free space in bank C3): on an odd frame ($021E bit 0) whose slot destination ($1C, high byte) is $40 to $47, it skips the upload and clears bit 0 of $45.
jsr MyFix ; replaces jsr TfrBigTextGfx at C3/A888
MyFix: lda $021e ; game time, frames
lsr
bcc @tfr ; even frame: the text's own
lda $1c ; DMA slot 2 destination, high byte
cmp #$40
bcc @tfr
cmp #$48
bcs @tfr
lda #$01 ; a bg3 tilemap has the slot:
trb $45 ; leave it, hold the palette upload
rts
@tfr: jmp TfrBigTextGfx
Credit: previously found by Assassin, Novalia Spirit and Lenophis, my implementation
#497 The target cursor can now reach enemies in 28 formations that no directional press could previously select.
In 1.0
In 28 enemy groups, the target cursor could not land on one enemy while a nearer enemy stood in the way from every enemy in every direction.
Now
The cursor now reaches those enemies.
For ROM hackers Method
SelectMonsterUp (C1/7258), Down (C1/7292), Left (C1/72CE) and Right (C1/7308) each run a 90-degree wedge pass and end with an rts (C1/726B, C1/72A5, C1/72E1, C1/731B); with nothing in the wedge $75 stays $FF. In place of each rts insert a jml (4 bytes) to a fallback that knows the direction, several hundred bytes of free space and 12 free bytes of RAM. When $75 is $FF it runs the four wedge passes from every living monster of the group (GetTargetCursorPosition, C1/71BE, and the routines' own comparisons), marks every slot some press picks plus the group's entry slot, and moves to the nearest unmarked living monster in the pressed half-plane, using the keep-nearest code at C1/7213. It returns through an rts in bank C1.
Credit: previously found by Assassin, my implementation
#512 A tall Colosseum opponent, Borras, SrBehemoth, GtBehemoth or Doom Drgn, no longer overlaps the two item boxes on the match-up screen.
In 1.0
On the Colosseum's match-up screen, Borras, SrBehemoth, GtBehemoth and Doom Drgn overlapped the two item boxes at the top.
Now
They now stand lower, clear of the boxes.
For ROM hackers 1.0 addresses
AlignColosseumMonster (C3/B0B7), ground alignment _b0f8 (C3/B0F8). $E0 is the sprite's first screen-A row and the sprite always ends at row 15, so a 16-wide stencil more than 11 tiles tall starts inside the two item boxes, rows 0 to 4. Insert before the clr_a at C3/B0F8 (16 bytes): unless $99 is $08 (an 8-wide stencil, which starts at row 8 anyway), raise $E0 to 5 when it is lower.
lda $99 ; stencil size: $08 small, $20 large
cmp #$08
beq @row
lda $e0 ; first screen-A row
cmp #$05
bcs @row
lda #$05
sta $e0
@row: ; the clr_a at C3/B0F8 follows
Credit: previously fixed by DrakeyC, my implementation
#513 Figaro Castle's and South Figaro's shop discount now applies whenever Edgar is anywhere in the active party, not only when he leads.
In 1.0
Figaro Castle's shops and four South Figaro shops sold at half price only when Edgar led the party, not when he was elsewhere in line.
Now
The half price now applies whenever Edgar is in the party.
For ROM hackers 1.0 addresses
AdjustShopPrice (C3/BA2C), half-price-for-Edgar handler at C3/BA92. At C3/BA95 it tests the leader's map sprite (r0202), so the discount follows whoever walks in front. In place of that test (7 bytes), insert a party test (17 bytes): Edgar's party byte $1854 must have bit 6 set and its low three bits equal to the party on the map, $1A6D; then half price at C3/BAA0, otherwise full price at C3/BA9C. The South Figaro shops on the same setting follow.
lda $1854 ; Edgar's party byte ($1850 + 4)
and #$40 ; in a party?
beq @full
lda $1854
and #$07 ; which party
cmp $1a6d ; the one on the map?
beq @half ; C3/BAA0
@full: ; C3/BA9C
Credit: as in the Pixel Remaster, my implementation
#536 Gau can use the Pugs Rage: it is listed, described and can be picked in the menu and in battle.
In 1.0
Gau could learn the Pugs Rage, but it showed as a blank line in the Skills menu and in battle and could not be picked.
Now
It is now listed and can be picked in the Skills menu and in battle.
For ROM hackers Method
Monster 255 (Pugs) shares the Rage lists' blank value $FF. The rule: when rage 255 is known ($1D4B bit 7), the first $FF in a list is Pugs and later ones are blanks, since the known rages are packed from the top; and no rage chosen is the battle's $FFFF fill of $33A8, not $FF alone. Battle: the Rage command at C2/1560 and RandRage (C2/05D1) test both bytes of $33A8; FixPlayerAttack at C2/4DEC clears $33A9 for a Rage picked from the list; RandRage picks by position in the packed list and reads a count of 0 with rage 255 known as 256. Battle window: the $FF test at C1/8531 and the rows of DrawRageListText (C1/4CE2) ask the rule, and Pugs is written with list text command $15 (unused in 1.0, table at C1/6414), a copy of ListTextCmd_18 (C1/6628) without its blank test. Menu: DrawRageName (C3/5418) asks the rule.
Credit: previously found and fixed by SilentEnigma, my implementation
#544 While Haste, Regen, X-Potion or seven other animations play on a party member, the others can aim at that party member; the cursor used to pass over them for about two seconds.
In 1.0
While Antdot, Bserk, Dischord, Eyedrop, Haste, Regen, Revivify, Slow, X-Potion or X-Ether played on a party member, the others' target cursor would not land on that party member, and moved off if already there.
Now
The cursor now lands on them as usual.
For ROM hackers 1.0 addresses
The target cursor code masks characters with $201D and $61AC (characters shown) at 20 places, the first in ValidateSelTargetGrp at C1/6D78, and once more where UpdateTargetCursors draws the cursors on characters. Animation script $3B (Antdot, Bserk, Dischord and seven more) draws its target on BG1 and hides the sprite, clearing its $61AC bit for 129 frames. Depends on #438, which marks a character hidden while drawn on BG1 in bits 4-7 of $61AC. Replace each pair (lda or and $201D, then and $61AC: 6 bytes) with a jsl and two nop to the routine below (28 bytes of free space). A jumper stays out of reach.
Shown: lda #$ff ; for lda $201d / and $61ac
Reach: php ; for and $201d / and $61ac
and $201d
pha
lda $61ac
lsr4 ; drawn on BG1
ora $61ac ; or shown
and #$0f
and 1,s
sta 1,s
pla
plp
ora #0 ; n and z from the result
rtl
Credit: previously found by Master ZED, my implementation
#605 Pressing A quickly on a skill list or item a party member cannot use, then L or R on one exact frame, no longer leaves the menu in large blocks.
In 1.0
Pressing A quickly several times on a skill list the party member cannot open, or on an item they cannot equip, then L or R on one exact frame left the whole screen in large blocks until the menu closed.
Now
The screen stays clear.
For ROM hackers 1.0 addresses
DisableInterrupts, C3/352F, which the menu calls before it changes screens. Its jsr to ResetTasks at C3/3534 can stop the mosaic task part way, and $B5, the menu's mosaic value, keeps what the task last wrote; the menu copies $B5 to the mosaic register every frame until it closes. The US guard in CheckShoulderBtns (C3/2022) narrows the window but leaves it open. Insert stz $B5 (2 bytes) before the rts at C3/3540. This is Novalia Spirit's Frozen Mosaic Fix for the US ROM, which finds the room by moving the three stz before it into a subroutine.
Credit: previously found and fixed by Novalia Spirit, my implementation
#632 An item's description still being typed when its details screen opened stayed half typed there; the details screen now shows it whole.
In 1.0
Opening an item's details screen while its description was still being typed out, or with a direction still held, left the description half typed or blank there.
Now
The details screen now shows the whole description from its first frame.
For ROM hackers 1.0 addresses
Builds on #169 and its hold byte ($D2). Change its inc before the rts at C3/8594 to lda #$81 / sta $D2 (2 bytes more). In its held branch of BigTextTask (C3/A80E), insert lda $D2 / bpl / jsr MyFix (7 bytes) before the sec. MyFix (135 bytes of free space in bank C3) does one stage a frame. $81, in any task state: start over (code, 17 bytes: states 0 and 2, C3/A818 and C3/A81B, less the state store), loop state 1's letter step (C3/A853, less the jsr TfrBigTextGfx at C3/A888) to the end byte, then do as C3/A89C ($45 bit 0, state 2); hold $82. $82: DMA slot 1 ($12 to $18) sends $7EA271, $0700 bytes, to VRAM $6800 in place of the details' BG3 screens; hold $83. $83: if slot 1 still points there, jsr InitDMA1BG3ScreenAB (C3/0F25); hold 1.
; MyFix, stage $81: the picked row's whole text ($7E9EC9) from its start
jsr ClearBigTextBuf ; with #169 it also clears $D2
stz $8d
ldx $2d ; the task
longa
stz $33ca,x ; text position
stz $344a,x ; buffer pointer
shorta
; then the letter loop and the end, as the text says
#20 Screen shake now uses its frequency table properly.
In 1.0
Some scenes on field maps shake the screen, and each shake has a setting for how often it flares up as it dies down, but all three settings flared up at the same rate.
Now
Each setting now has its own rate, and all three flare up less often than before.
For ROM hackers 1.0 addresses
UpdateShake (C0/0EA0), field screen shake, even frames. The and f: at C0/0EDE masks the random number with ShakeAndTbl (C0/0F45) where it should use ShakeFreqTbl (C0/0F49), so the frequency setting picks the wrong mask. Point that operand at ShakeFreqTbl: add 4 to its low byte, in place.
Credit: previously found by everything8215, my implementation
#28 World map encounters now follow where the party actually is, not where it was when the map loaded.
In 1.0
The world map chose enemies from the area the party was in when the map last loaded, after a battle, closing the menu or leaving a town, so the next battle in a new area could bring the enemies of the area left behind.
Now
Battles now bring the enemies of the area the party is in.
For ROM hackers 1.0 addresses
CheckBattleWorld (C0/C176), world map random battles. It picks the encounter sector from $1F61 (y) and $1F60 (x), the saved world position, which changes only when the world map loads. Read the live position instead: the lda $1F61 at C0/C1B2 becomes lda $0AE2 and the lda $1F60 at C0/C1B9 becomes lda $0AE0. Operands only, in place.
Credit: previously fixed by Imzogelmo, Lenophis and Novalia Spirit, my implementation
#37 Five event branches that pointed at the wrong place now point at the right one.
In 1.0
Five walks that move NPCs around on maps jumped to the wrong point in their scripts, so the game read the start of the walk as if it were steps.
Now
Each jump now lands on the first real step of its walk.
For ROM hackers 1.0 addresses
Six backward object-script branches (command $FC; the next byte is the distance back from the opcode) land on an obj_script header, or in one case four bytes before the first command, so header bytes run as commands. Lower each distance byte so the branch lands on the script's first command: the branches at CA/48B7 and CA/48BC by 1, CA/ED79 by 4, CB/9BF2 and CB/9DD0 by 2, CC/737A by 1. The distance byte is the byte after each address. All six are NPC walking loops. Five are taken in play; the Doma soldier's at CB/9BF2 never is, since the scene changes map before his first loop ends, so its fix shows nothing. The disassembly's seventh marked branch, to _ca20a9 (Setzer), is left alone.
Credit: previously found by everything8215, my implementation
#48 Two messages that were cut off before a page break now show their reply text.
In 1.0
Two messages ended where a page break was meant, so the reply never appeared: the Imperial trooper's answer to Edgar at Figaro Castle, and Banon's reply to Edgar's Magitek idea, with the talk of contacting an Esper after it, at the Returners' hideout.
Now
Both now turn the page, so the replies appear as written.
For ROM hackers 1.0 addresses
Dialogue 123 ($007B) and 347 ($015B) each hold a line and its reply, joined by the end code $00 where the page break $13 belongs, so the text stops after the first line. Change the $00 at CD/166E (in 123) and at CD/3A12 (in 347) to $13. In 347 its last page then runs to five lines; change the line break $01 at CD/3A66, inside Banon's last line, to a space ($7F), so it holds four. Same size; no pointer moves.
Credit: previously found and fixed by Novalia Spirit, my implementation
#67 With Gau on the Veldt, the Zozo scenes no longer leave Arvis's house empty or freeze after Terra's flashback: they now ask who is present, not who was recruited.
In 1.0
With Gau away on the Veldt, three scenes checked whether party members had been recruited instead of whether they were present. The friends left out at the Zozo split stayed standing in the room instead of leaving for Narshe, so Arvis's house stayed empty, and the scene after Terra's flashback could put a party member on the bed she must get out of, which soft locked the game.
Now
The scenes now ask who is with the party, so the Zozo scene carries on and the friends wait in Arvis's house.
For ROM hackers 1.0 addresses
Three party tests in two events ask set_case AVAIL_CHARS whether Gau ($01AB) is recruited, then set_case PARTY_CHARS whether he is here. When he is away on the Veldt the first if_switch jumps on with the case still AVAIL_CHARS, so the tests of $01A2 onward at the target ask whether each character is recruited instead of whether they are in the party. Insert set_case PARTY_CHARS (1 byte, $DE) at each target, before its first if_switch: CA/ABBC and CA/AC18 (the Zozo split for Vector, after Ramuh), CA/C474 (the return to Zozo with Setzer, before Terra's flashback).
Credit: previously found and fixed by Leet Sketcher, my implementation
#70 Switching party on certain frames no longer skips tile-triggered events such as the Inferno and Poltrgeist fights, doors, floor switches or the Phoenix Cave freeze.
In 1.0
Switching parties, or opening the menu, on the wrong frame made the game ignore the tile just stepped on, so the Inferno and Poltrgeist fights in Kefka's Tower could be walked past, a party could be left in a doorway, and in the Phoenix Cave a party could end up on a crossing it never took.
Now
The game now deals with the tile first and switches parties after.
For ROM hackers 1.0 addresses
FieldMain (C0/007F) latches the party's tile at the head of each frame and runs the trigger and entrance checks on it. The party's step ends later, in MoveObjs, and CheckChangeParty (C0/6D26) runs at the end of the frame, so a walking party is tile-aligned at a frame's end only on its landing frame, whose tile nothing has checked yet. Move the jsr CheckChangeParty at C0/013A up to just before the jsr UpdateObjActions at C0/011F (same size; the jsrs between shift by 3). In CheckChangeParty, insert before the lda at C0/6D2D: lda $59, bne Done, lda $4A, bne Done (8 bytes), so it also waits while the menu opens or the screen fades.
Credit: previously found and fixed by Leet Sketcher, my implementation
#71 The red light marking a chest-as-switch in Kefka's Tower now shows until the chest is pressed and comes back when leaving the tower resets it.
In 1.0
In Kefka's Tower, the red light that marks a chest working as a switch went out when the other parties pressed their floor switches, and once Poltrgeist was beaten it stopped coming back after leaving the tower, even with the chest still unpressed.
Now
The light now shows until the chest is pressed, and comes back when leaving the tower resets the switch.
For ROM hackers 1.0 addresses
Kefka's Tower, map 334: the chest-as-switch's red light is NPC 12, shown by event bit $06AF. Floor-switch event _cc14cd (CC/14CD) clears $06AF at CC/14EA, and the tower-exit block _cc0f7d (CC/0F7D), which also clears the chest's bit $007F, sets it again at CC/0FBD, behind the Poltrgeist test at CC/0FB7. Remove the switch $06AF=0 at CC/14EA (2 bytes; overwrite it with a second copy of the switch $06B2=1 that follows). Swap the if_switch at CC/0FB7 and the switch at CC/0FBD, same size, so leaving always relights it. Replace the call _cc14a6 at CC/148A and CC/149D, the chest's two press events, with a call to: call _cc14a6 / switch $06AF=0 / hide_obj NPC_12 / return (9 bytes of free space).
Credit: previously found and fixed by Novalia Spirit, my implementation
#72 Chests and pickups now give the item or amount they actually announce.
In 1.0
Several treasures gave something other than their message named: a buried Remedy in the Cave to the Sealed Gate gave a Soft, Owzer's basement chests promised a Potion, an Ether and a Remedy and gave a Tonic, a Tincture and a Soft, and two finds announcing 2000 GP paid 293, and a message called the Water Edge a "Water Skean".
Now
Every one now gives what its message names, and the Water Edge has its real name.
For ROM hackers 1.0 addresses
Event command operands. _cb310b: the give_item at CB/3124 gives Soft ($F4); make it Remedy ($F5). _cb3129 and _cb4a4e: the give_gil at CB/3142 and CB/4A84 pays 293; make the 2-byte amount 2000 ($07D0). Owzer's basement chests _cb4a8e, _cb4acd and _cb4b0c: the give_item at CB/4AC4, CB/4B03 and CB/4B42 gives Tonic, Tincture and Soft; make them Potion ($E9), Ether ($EC) and Remedy ($F5). Dialogue 1700 ($06A4) names a Water Skean: write Water Edge over it. With the dialogue's two-letter compression both names take 7 bytes, so the entry keeps its length.
Credit: previously found and fixed by Imzogelmo, my implementation
#73 The Nikeah chocobo stable now charges the 80 GP it quotes.
In 1.0
Nikeah's chocobo stable quoted 80 GP for a ride but took 150 GP when you accepted.
Now
It now takes the quoted 80 GP.
For ROM hackers 1.0 addresses
Nikeah chocobo stable, event _ca8fbf (CA/8FBF). The dialogue before it quotes 80 GP, and the take_gil at CA/8FC9 takes 150. Change its 2-byte amount to 80 ($0050).
Credit: previously found and fixed by Novalia Spirit, my implementation
#74 Gear optimized automatically before the Phunbaba and Tentacles fights no longer lands in the wrong slot.
In 1.0
Before the first Phunbaba fight the game picks the best gear for Terra, and before the Tentacles for Edgar, and now and then an item landed in the wrong slot, such as a weapon in the helmet slot.
Now
Every item now goes to its own slot.
For ROM hackers 1.0 addresses
Event command $9C calls OptimizeCharEquip (C0/C6B3), which runs the menu's optimise routine with the field NMI still on. That routine holds the equip slot selector in $4B across an hblank wait, and the field NMI's UpdateFixedColor (C0/1053) writes $4B as its fixed-colour rate, so an NMI in that window changes the slot. Insert jsr DisableInterrupts (C0/0374) before the php at C0/C6B9 (3 bytes). Before the jsr PopDP at C0/C6C3 insert jsr DisableInterrupts, jsr InitInterrupts (C0/0573), jsr EnableInterrupts (C0/0386) (9 bytes).
Credit: previously found and fixed by Assassin, my implementation
#75 At the Phoenix Cave entrance, the airship hook is now set in place before the jump, so nobody rides up a tile off it.
In 1.0
When you talk to the bobbing airship hook at the Phoenix Cave entrance, the leader could ride up a tile off it.
Now
The leader and the hook now rise together.
For ROM hackers 1.0 addresses
Phoenix Cave entrance, exit event _cc20e5 (CC/20E5). NPC_1 is the airship's hook, which the map's start-up event keeps bobbing between (4,0) and (4,1). The leader's jump and his seven-tile ride up are fixed, so the two only stay together when the hook is on its low tile as the jump starts. Insert before the obj_script SLOT_1 at CC/20EE (6 bytes) an object script that puts the hook there.
obj_script NPC_1 ; inserted before CC/20EE
pos {4, 1}
end
Credit: previously found and fixed by Leet Sketcher, my implementation
#76 Two canal tiles behind the South Figaro cafe are now solid, so Locke's scenario can't be finished by wading across.
In 1.0
Two tiles in the canal behind the South Figaro cafe looked solid but could be walked on, letting you cross the canal in Locke's scenario and finish without stealing the merchant's clothes.
Now
Both tiles are now solid.
For ROM hackers 1.0 addresses
Map layout 7, SOUTH_FIGARO_EXT_BG1 (South Figaro's BG1), LZSS compressed at D9/EC62, offset in SubTilemapPtrs (D9/CD90). Two canal tiles behind the cafe use tile $08, which is walkable, where the identical-looking solid tile $07 belongs. Decompress and change the bytes at offsets $919 and $91A from $08 to $07. Recompress; if it grows, move it to free space and repoint.
Credit: previously found and fixed by Novalia Spirit, my implementation
#89 Opening the menu at the wrong moment no longer resets Duncan's hop routine so he can be walked off screen.
In 1.0
If you visit Duncan's cabin without Sabin, Duncan practises inside, two hops to the right and two back, and opening and closing the menu restarted his practice from wherever he stood, so doing it again and again pushed him through the wall and off the screen.
Now
Closing the menu no longer restarts his hops, so he stays in the room.
For ROM hackers 1.0 addresses
Map 128's start-up event _cc0b8c (CC/0B8C) restarts Duncan's hop script from its first frame on every load, including the reload after the field menu. Insert if_switch $01B6=1, EventReturn (6 bytes) at its head. Event bit $01B6 is set while a reload keeps the map's NPC state, as after a battle or the menu, and other start-up events test it the same way.
Credit: previously found by Mario Meo and mblock129, my implementation
#90 Wrexsoul's palette now loads even before his battle event runs, so he no longer appears gold.
In 1.0
In the Dreamscape, Wrexsoul was drawn in gold like the background until the event that starts his battle ran.
Now
His colours now load before he can be seen.
For ROM hackers 1.0 addresses
In Cyan's dream, Wrexsoul's field sprite draws with sprite palette 15, which only _cb97d6 (CB/97D6), the event that starts his battle, loads with the ESPER_TERRA palette ($08). Before that, palette 15 holds whatever was there last. Insert load_pal 15, ESPER_TERRA (3 bytes, $60 $0F $08) before the return at CB/97A9 in map 126's start-up event _cb97a1 (CB/97A1).
Credit: previously found by mblock129, my implementation
#92 Opening and closing the menu after opening the Fanatics' Tower's secret door, or one of the two painting doors in Owzer's mansion, no longer draws it shut with wrong tiles beside it.
In 1.0
In the Fanatics' Tower, after the secret door was opened, opening and closing the field menu drew it shut again, though it still opened, and left wrong tiles beside it. The same happened at the two doors left by paintings in Owzer's mansion in the World of Ruin, and upstairs the party could step into the wall beside the frame.
Now
The doors now stay drawn open and the tiles beside them do not change.
For ROM hackers 1.0 addresses
Same fault and fix as #393. Map 363's start-up event _cc5173 (CC/5173) draws the door shut as BG1 tiles $05, $15, $45 once $02DC is set; the layout holds a wall there ($61 at (10, 12)). CheckDoor records an opened door by position only. On a map load the jsr DrawOpenDoor at C0/BF6D runs before the start-up, finds that wall instead of $05 (the cmp at C0/4D28) and paints the 3 by 2 big door over (10, 12) to (12, 13). The start-up then repaints the shut door over the left column only. With #393's two edits it reads the $05 and draws small door 1.
Credit: previously found by Master ZED and fixed by Leet Sketcher, my implementation
#94 The secret cave door no longer closes itself in the World of Ruin when the menu is opened.
In 1.0
In the World of Ruin, the secret cave entrance in Narshe shut again when you opened and closed the main menu, and stayed shut until you left the area and came back.
Now
The entrance now stays open.
For ROM hackers 1.0 addresses
Narshe's secret entrance (map 32) is painted by an event; $01F0 is set while it is open. Each map load reads the layout from ROM; the World of Balance map's start-up event _ccd0e7 repaints it, map 32's does not. Insert a jsr (3 bytes) at SkipStartupEvent (C0/C0A4) to a routine walking a table of map, event bit, x, y, layer, tile block. For a row matching $82 whose bit is set it puts the position in $8F/$90, the layer base in $2A/$2B ($2A = 0), the block in $8C-$8E, 4 in $055A + layer, and calls ModifyMap (C0/1EC4). Rows: map 32, $01F0, (14,54), the BG1 and BG2 blocks of _ccd0e7. 121 bytes of code in bank C0, plus the table.
Credit: previously found and fixed by Leet Sketcher, my implementation
#95 The esper Terra at Zozo no longer opens her eyes again when the party re-enters her room; she sleeps until Locke calls out "Magicite!!"
In 1.0
The esper Terra lay unconscious in a room at Zozo with her eyes open. After Ramuh's talk the party put her back to bed with them closed, but leaving the room and coming back showed them open again.
Now
She now lies with her eyes closed whenever she is in the room, and opens them when Locke calls out "Magicite!!"
For ROM hackers 1.0 addresses
Map 226's startup event _caf004 (CA/F004) and the scene _cac4b0 (CA/C4B0). NPC_1, the esper Terra (npc_prop switch $0314), keeps her default frame WALKING_DOWN_2, eyes open, from the map load on. Insert at the head of _caf004 (10 bytes): if_switch $0314=0 past the pose, then obj_script NPC_1 with action EYES_CLOSED_DOWN. The switch test is needed: with $0314 clear the NPC is disabled and a synchronous obj_script on it never ends. In _cac4b0 insert obj_script NPC_1 with action WALKING_DOWN_2 (4 bytes) at CA/C512, straight after the dlg DLG_1421 at CA/C50F.
_caf004:
if_switch $0314=0, @pal
obj_script NPC_1
action EYES_CLOSED_DOWN
end
@pal: ; the original load_pal follows
; in _cac4b0, after dlg DLG_1421
obj_script NPC_1
action WALKING_DOWN_2
end
Credit: previously found by Assassin and fixed by Madsiur, my implementation
#96 The two captive Gogo NPCs in the Dreamscape no longer wear Mog and Umaro's palette.
In 1.0
The Dreamscape begins with the first two party members held captive, and if Gogo was one of them, Gogo wore Mog and Umaro's palette and looked pink until rescued, in both spots where Gogo can be held.
Now
A captive Gogo now has the right colours in either spot.
For ROM hackers 1.0 addresses
NPC properties, NPCProp (C4/1D52), 9 bytes a record, palette in bits 2 to 4 of byte 2. The two captive Gogo NPCs of the Dreamscape (records at C4/574B and C4/57C0, graphics GOGO) have palette 5, MOG_UMARO. Set it to 3, STRAGO_RELM_GAU_GOGO, Gogo's own: bits 2 to 4 of the bytes at C4/574D and C4/57C2.
Credit: previously found and fixed by Imzogelmo, my implementation
#99 Setzer's compliment no longer makes Edgar blush with Celes, fails when Sabin is present, or leaves them pink for the rest of the scene.
In 1.0
On the Blackjack, Celes blushes when Setzer compliments her, but Edgar shares her palette so he blushed too, and with Sabin in the party nobody blushed at all. Afterwards the colour did not come all the way back, so Celes and Edgar stayed pink for the rest of the scene.
Now
Only Celes blushes now, with or without Sabin, and her colour comes back in full.
For ROM hackers 1.0 addresses
Event _cb2041 (CB/2041), Setzer's compliment. The blush fades colour 6 of the palette Celes shares with Edgar and Sabin, so all three blush, and the if_switch $01A5 skips at CB/2069 and CB/2089 drop it whenever Sabin is in the party. Replace the first if_switch (6 bytes) with load_pal $0E, EDGAR_SABIN_CELES / obj_pal EDGAR, RAINBOW / obj_pal SABIN, RAINBOW (9 bytes), so the brothers use a copy in spare slot $0E. Delete the second if_switch. In the loop after it, change mod_sprite_pal INC, 2 at CB/2091 to UNDEC, 0, which walks the colour back to its stored copy. After that loop insert obj_pal EDGAR and SABIN, EDGAR_SABIN_CELES and load_pal $0E, RAINBOW (9 bytes). The event grows by 6 bytes in all.
Credit: previously found and fixed by Novalia Spirit, my implementation
#101 After Wedge breaks the gate in the Narshe mines, a strip of mismatched floor no longer shows beside the opening until the next reload.
In 1.0
After Wedge breaks the gate in the Narshe mines, a strip of mismatched floor showed at the left of the opening until the next fight or menu.
Now
The floor there now matches.
For ROM hackers 1.0 addresses
Event _cc9e23 (CC/9E23), Wedge breaking the gate in the Narshe mines. Its mod_bg_tiles BG1, {41, 5}, {3, 4} at CC/9E9F paints the broken gate; the first tile of its second row (map tile (41, 6), byte at CC/9EA7) is $11, which is see-through, so the scene's own BG2 tile shows there, and it is not the floor the map holds after a reload. Change that byte to $96, the map's floor tile at that spot.
Credit: previously found and fixed by Novalia Spirit, my implementation
#103 The coin in Celes's coin toss is now set up before she starts moving, so her sprite is no longer garbled for a moment.
In 1.0
During Celes's coin toss with Setzer on the Blackjack, her sprite was garbled for a moment.
Now
Her sprite now shows cleanly.
For ROM hackers 1.0 addresses
Event _cb2103 (CB/2103), Setzer's coin toss. create_obj NPC_2, the coin, at CB/213F runs after obj_script CELES at CB/212D has started, and creating the object then garbles her sprite for a moment. Move the create_obj (2 bytes) up to CB/212D, before the obj_script; the 18 bytes between shift down by 2. Same size.
Credit: previously found and fixed by Gi Nattak, my implementation
#110 A black gap in the Mt. Kolts secret room and four wrong tiles in the Kefka's Tower dragon room are now drawn correctly.
In 1.0
A black gap showed behind a platform in the Mt. Kolts secret room, and four tiles in the corners of the Kefka's Tower dragon room were black.
Now
Those tiles now show the same wall or floor as the tiles around them.
For ROM hackers 1.0 addresses
Map layouts, LZSS, pointers in SubTilemapPtrs (D9/CD90); change each tile in the decompressed block. Mt. Kolts secret room: MT_KOLTS_INT_BG1 (DA/CC56) and MT_KOLTS_INT_BG2 (DA/D31F), 64 tiles wide; tile (25, 31), offset $7D9, becomes $B3 on BG1 and $DE on BG2, closing the black gap behind the platform. Kefka's Tower dragon room (map 335): KEFKAS_TOWER_CASTLE_BG2 (DD/DB9E), drawn 128 tiles wide; offsets $D5E, $DDE, $F85 and $1085, the tiles at (94, 26), (94, 27), (5, 31) and (5, 33), change from $00 to $01. Recompress; if a block grows, move it to free space and repoint.
Credit: previously found and fixed by Leet Sketcher, my implementation
#111 The map shared by Jidoor's relic shop and Maranda's weapon shop no longer draws its open door mostly inside the wall.
In 1.0
The room shared by Jidoor's relic shop and Maranda's weapon shop showed a thin strip of an open door in the back wall, just left of the closed door.
Now
That strip now shows plain wall.
For ROM hackers 1.0 addresses
Map layout JIDOOR_INT_BG1 (LZSS at DB/FD92, pointer in SubTilemapPtrs D9/CD90), 128 tiles wide, the map shared by the relic shop and Maranda's weapon shop. Tiles (53, 13) and (53, 14) hold an open door set into the wall. Give them the wall of the run either side: offset $6B5 becomes $A3 and offset $735 becomes $44. Recompress; if it grows, move it to free space and repoint.
Credit: previously found and fixed by Novalia Spirit, my implementation
#114 The World of Ruin sector east of Kefka's Tower no longer falls back to Leafers and Dark Winds by default.
In 1.0
Random battles in the eastern part of the six-tree peninsula east of Kefka's Tower, in the World of Ruin, fell back to Leafers, sometimes with a Dark Wind, the first enemies met outside Narshe.
Now
That area now has the same enemies as the rest of the peninsula, such as Lunaris and Osprey.
For ROM hackers 1.0 addresses
World battle groups, WorldBattleGroup (CF/5400): 8 by 8 sectors per world, 4 bytes each (field, forest, desert, dirt), the World of Ruin from +$100. Sector (5, 6), the peninsula east of Kefka's Tower, is all zero at CF/55D4, so it falls back to Leafers and Dark Winds. Copy into it the 4 bytes of its neighbour (4, 6) at CF/55D0, which covers the rest of the peninsula.
Credit: previously found and fixed by Novalia Spirit, my implementation
#115 Four world map grass tiles now correctly allow random battles.
In 1.0
Random battles never started on four World of Balance grass tiles, the light green grass with a dark green pixel in one corner, so those patches were safe to cross.
Now
Those tiles now allow random battles like the rest of the grass.
For ROM hackers 1.0 addresses
World of Balance tile properties, WorldTileProp (EE/9B14), one word per tile; bit 6 ($40) allows random battles. Grass tiles $6D, $6E, $7D and $7E lack it. Set it at EE/9BEE, EE/9BF0, EE/9C0E and EE/9C10, the low byte of each word.
Credit: previously found and fixed by Novalia Spirit, my implementation
#116 When the party dresses Gau up in Jidoor, every recruited party member now plays their part, whoever came along.
In 1.0
When the party dresses Gau up in Jidoor, Cyan and Setzer were left out if they came along, Terra and Celes spoke without moving, and Locke and Edgar's moment needed both of them in the party.
Now
Every recruited party member now plays their part, whoever came along.
For ROM hackers Method
Scene _cb606b (CB/606B), Gau's dress-up in Jidoor. It stages by party membership (set_case PARTY_CHARS) while every party member's sprite stands on the map. Add two event routines (about 280 bytes of event space) and two free event switches. The stage routine does delete_obj for every party member but Gau and Sabin, membership kept; when Locke and Edgar are both recruited, a reserve one joins with char_party X, 1, obj_gfx and obj_pal, its switch set, and both are shown. The restore routine sends that one back with char_party X, 0 and does create_obj for every party member. Insert call to the stage routine at CB/60A4 and to the restore routine at CB/627F. Change set_case PARTY_CHARS to OBJ_CHARS ($DE to $DF) at CB/60A4, CB/615D, CB/61AD, CB/61C8 and CB/637C.
Credit: previously found and fixed by DrakeyC, my implementation
#131 Leaving Arvis's house by the back door, and the Phantom Train's second seating car, now face the party the right way.
In 1.0
Coming out of Arvis's house by the back door in the World of Ruin left the party facing south, and coming out of the Phantom Train's second seating car by its east door left them facing west.
Now
The party now comes out of Arvis's back door facing north and out of the seating car's east door facing east.
For ROM hackers 1.0 addresses
Two load_map commands face the party the wrong way. The facing is bits 4-5 of the command's third byte (bits 12-13 of the map word). Phantom Train, _cba840: the load_map at CB/A842 (map 142, east exit of the second seating car), change the facing at CB/A844 from 3 (left) to 1 (right). Arvis's house, _cc3926: the load_map at CC/392C (map 32, back door in the World of Ruin), change the facing at CC/392E from 2 (down) to 0 (up). Same size.
Credit: previously found and fixed by Novalia Spirit, my implementation
#132 Two NPCs in the auction house no longer draw over the chocobo, cutting off parts of it.
In 1.0
Two NPCs next to the chocobo on show in the World of Balance auction house were drawn over it and hid parts of it.
Now
Both NPCs are now drawn behind the chocobo.
For ROM hackers 1.0 addresses
NPCProp, the auction house's two NPCs at {16, 13} and {18, 13}, records at C4/3FE1 and C4/3FEA (9 bytes each). Both have layer priority TOP_SPRITE_ONLY, which draws them over the chocobo. In each record clear byte 8 bits 3-4 (layer priority to default) and set byte 7 bits 4-5 to 2 (sprite priority low). Same size.
Credit: previously found and fixed by Madsiur, my implementation
#133 If guards shove the party away before the Vector banquet, the next scene no longer has NPCs walking through each other.
In 1.0
Before the banquet in Vector, the guard who steps out to shove the party aside stayed off his post, and in the next scene guards and other NPCs walked through each other.
Now
The guard now returns to his post and the scene plays cleanly.
For ROM hackers 1.0 addresses
Vector banquet guards, events _cc85e3 (CC/85E3) and _cc860d (CC/860D). A guard steps up 2 and aside 1 to shove the party, then stays there and sets $01F0 or $01F1 for later scenes to put him right, which leaves guards and NPCs walking through each other. In each event's last obj_script (CC/8601, CC/862B) insert move DOWN, 2, a dir toward his post, move one tile back, dir DOWN (4 bytes, before the end at CC/8607 and CC/8631) and raise the script's length byte by 4. Delete the switch command after it (CC/860A, CC/8634, 2 bytes).
obj_script NPC_8 ; CC/8601, length byte +4
; ... as 1.0, then before the end:
move DOWN, 2
dir LEFT
move LEFT, 1
dir DOWN
; the switch $01F0=1 after pass_on is deleted
; _cc860d: the same with dir RIGHT, move RIGHT, 1
Credit: previously found by mblock129, my implementation
#134 Switch tiles in a late Gogo scene now use the correct palette.
In 1.0
In a late scene with Gogo, a set of switch tiles was drawn with the wrong palette, miscoloured next to the game's only other switch of that type.
Now
The switch tiles now match the other switch of their kind.
For ROM hackers 1.0 addresses
Event _ca1afa, a late Gogo scene (map 254). The load_pal 14 command at CA/1B13 loads sprite palette $0D (GUARDIAN), but the switch tiles drawn after it want $0C (MACHINERY_2), as the only other switch of that type does. Change the palette byte at CA/1B15 from $0D to $0C. Same size.
Credit: previously found and fixed by Gi Nattak, my implementation
#135 The flying Storm Drgn no longer drifts off its path until it effectively disappears.
In 1.0
The flying Storm Drgn crept one tile further from its circling path with every lap until it effectively disappeared.
Now
It now keeps circling the same path.
For ROM hackers 1.0 addresses
The flying Storm Drgn's loop, object script from _cc43a7 (CC/43A7). Its moves add up to 28 tiles up and 27 down, so each lap ends a tile higher. The move UP_RIGHT, 6 at CC/43C2 is six one-byte diagonal steps; change the last one, at CC/43C7, to move RIGHT, 1 ($81). The lap is then 27 up, 27 down, and closes. Same size.
Credit: previously found and fixed by Novalia Spirit, my implementation
#136 Stripping equipment at the end of the Floating Continent no longer pulls Kamog the moogle's gear into your inventory.
In 1.0
At the end of the Floating Continent, the game strips every party member's equipment, but it also stripped Kamog the moogle's, so his Boomerang and Buckler landed in your inventory as free items.
Now
Kamog's gear no longer turns up in your inventory.
For ROM hackers 1.0 addresses
Event _ca4f10, the end of the Floating Continent, strips the equipment of the roster slots, Shadow's excepted, with remove_equip. Slots $0C and $0D (GOGO, UMARO) still hold the moogles of the Narshe battle at this point, so Kamog's Boomerang and Buckler go to the inventory. Delete remove_equip GOGO and remove_equip UMARO at CA/4F67 and CA/4F69 (4 bytes). In place: change their character bytes (CA/4F68, CA/4F6A) to $00, Terra, whose equipment is already off.
Credit: previously found and fixed by Drakkhen, my implementation
#137 Switching party leader during the Magitek Armor part of Cyan's Soul now clears everyone's riding flag.
In 1.0
If you switched leaders during the Magitek Armor ride in Cyan's Soul, the other party members could stay set as riding after it ended, causing a visual glitch.
Now
Every party member is now taken off the armor when the ride ends.
For ROM hackers 1.0 addresses
Event _cb94e7, the end of the Magitek Armor part of Cyan's Soul. After clearing Magitek from slots 1 to 3 it runs vehicle SLOT_1, NONE (CB/9510, 3 bytes), so after a leader switch another slot keeps riding. Delete it and insert call _cb04aa (4 bytes, one more than it frees), the game's own routine that sets vehicle NONE for all four slots (CB/04AA).
Credit: previously found and fixed by Imzogelmo, my implementation
#138 Jump animations in Zozo no longer always face left, even when jumping right.
In 1.0
On a jump to the right across the gaps of the Zozo rooftops, only the mid-air sprite was mirrored, so the jumper crouched, caught the far ledge and knelt facing left.
Now
A jump to the right is now drawn facing right throughout.
For ROM hackers 1.0 addresses
Zozo rooftop jumps. _ca961c (CA/961C) is the crouch before every jump and _ca962d (CA/962D) the landing after it; both draw poses facing left, and only the mid-air frame is mirrored for the rightward jumps. In _ca961c, after the action NEAR_FATAL at CA/961E and at CA/9624, insert branch_if_not_dir RIGHT past an action NEAR_FATAL_FLIP (4 bytes each), raising the script's length byte by 8. Copy _ca962d to free space with READY_FLIP and NEAR_FATAL_FLIP in place of READY and NEAR_FATAL, and point the two rightward calls at CA/95ED and CA/9617 to the copy.
; _ca961c, after each action NEAR_FATAL (CA/961E, CA/9624):
branch_if_not_dir RIGHT, @c1
action NEAR_FATAL_FLIP
@c1: ; the next 1.0 command
Credit: previously found by mblock129, my implementation
#139 Seven small event slips fixed: hidden leftovers, a facing NPC, a missing battle sound, overlapping lines and a Tent animation.
In 1.0
Gogo's, Umaro's and Mog's recruitments left a hidden leftover of them on the map. Katarin faced the wall instead of the child at the Mobliz reunion, the Chadarnook fight began without its battle-start sound, and two of Cyan's lines could overlap. A leader swapped in the menu just before a Tent was not the one who pitched it.
Now
Each scene now plays cleanly: no hidden leftovers, Katarin faces the child, Chadarnook's fight opens with its sound, Cyan's lines come one after the other, and the new leader pitches the Tent.
For ROM hackers 1.0 addresses
Seven event edits. Tent (CA/00EA): insert update_party ($47, 1 byte) before the fade_in. Gogo (_cb824d, CB/824D, the branch where he does not join the marching party): insert delete_obj GOGO (2 bytes) before the char_party. Umaro (_ccd793) and Mog (_ccd5df): delete the create_obj at CC/D79D and at CC/D5E8 (2 bytes each), which makes an object that already exists. Katarin (_cc4990): the dir DOWN at CC/49BE in NPC_15's script becomes dir LEFT. Chadarnook (_cb4cfa): clear NO_SFX, bit 6 of the battle command's third byte, at CB/4D1A. Cyan (DLG_731): move the dlg at CB/BD9F to after the wait_15f at CB/BDA9, same size.
Credit: previously found and fixed by Leet Sketcher, my implementation
#140 The naming screen no longer misplaces party members when Cyan is leading instead of Sabin.
In 1.0
In the scene in Sabin's scenario where you name Shadow, with Cyan leading instead of Sabin, Shadow stayed visible on screen while he was introduced, and afterwards Sabin briefly popped up in a leftover position.
Now
The scene now works with either of them leading.
For ROM hackers 1.0 addresses
Event _cb0a5f (CB/0A5F), Shadow's introduction at the Imperial Camp. It hides and later shows the object SABIN, assuming Sabin leads; with Cyan leading, the leader stays visible and Sabin's object pops up. Change the object byte of hide_obj SABIN at CB/0A70 and of show_obj SABIN at CB/0A9E from $05 (Sabin) to $31 (SLOT_1, the party leader), at CB/0A71 and CB/0A9F. Same size.
Credit: previously found and fixed by Novalia Spirit, my implementation
#142 The scene of Shadow walking to the South Figaro cafe no longer plays when he will not be there, after Vargas or in Locke's scenario.
In 1.0
If you stayed away from South Figaro until after Vargas, the scene of Shadow walking to the cafe still played on your first visit, but the cafe was empty. Left until Locke's scenario, Shadow was left stuck at the town's entrance.
Now
The walk now plays only when Shadow will be in the cafe.
For ROM hackers 1.0 addresses
Event _caeba1 (CA/EBA1), South Figaro's start-up. The walk of Shadow to the cafe is skipped only once seen ($000A, if_switch at CA/EBA7), so it plays even when the cafe Shadow is not there. Delete that if_switch (6 bytes) and insert in its place if_any: $000A=1 or $0305=0 (the cafe Shadow's own NPC switch), goto _caebc1 (CA/EBC1), 8 bytes.
Credit: previously found by Novalia Spirit and fixed by Madsiur, my implementation
#143 A lasting status Shadow picked up on the Floating Continent no longer carries over to his Colosseum appearance.
In 1.0
If Shadow picked up a lasting status such as Vanish or Float on the Floating Continent and survived its collapse, he still had it as a Colosseum opponent.
Now
He now faces you at the Colosseum without them.
For ROM hackers 1.0 addresses
Event _ca57b3 (CA/57B3), Shadow left behind on the Floating Continent. Nothing clears the statuses he picked up there, and his character record keeps them until he appears again, the Colosseum included. Insert and_status SHADOW, INTERCEPTOR (event command $88, 4 bytes) after the switch $037D=1 at CA/57E4, before the call _ca5806 at CA/57E6. It ANDs his status word with Interceptor's bit, so every other status goes.
Credit: previously found and fixed by Leet Sketcher, my implementation
#145 Owzer's basement diary is no longer readable after Chadarnook is beaten and no longer visible.
In 1.0
After Chadarnook was beaten, the diary on the desk near the dark art gallery in Owzer's mansion was gone, but examining it still brought up its text.
Now
The diary can now be read only while it is on the desk.
For ROM hackers 1.0 addresses
Event _cb4893 (CB/4893), examining Owzer's diary in the basement. It tests the facing switches $01B0/$01B4 and $024A, but not whether Chadarnook is beaten ($0253) or whether the diary object is still shown ($0300), so the examine outlives the diary. Insert two tests before the if_switch at CB/48A1 (12 bytes): if_switch $0253=1, EventReturn and if_switch $0300=0, EventReturn, the way the room's other examines ask it.
Credit: previously found and fixed by Novalia Spirit, my implementation
#146 Two tiles no longer briefly show the wrong graphic after the westernmost Floating Continent teleporter animates.
In 1.0
After you used the westernmost teleporter on the Floating Continent, two nearby tiles briefly showed the wrong graphic.
Now
The two tiles now show the right graphic throughout.
For ROM hackers 1.0 addresses
Event _cad61d (CA/D61D) restores the westernmost teleporter's 3 by 4 block of BG1 tiles at {31, 15} after its animation. The outer two tiles of its bottom row are $B5, where the animation frames in _cad602 (CA/D602) put $48 and $4A. Change the bytes at CA/D62B and CA/D62D to $48 and $4A.
Credit: previously found and fixed by Novalia Spirit, my implementation
#148 The Zozo flashback now borrows Ramuh's magicite to show Maduin's, so an uncollected Siren no longer vanishes.
In 1.0
The Zozo flashback showed Maduin's magicite by borrowing Siren's, so if you had not picked Siren up yet, it vanished from its own spot.
Now
The flashback now borrows Ramuh's magicite, so an uncollected Siren stays where it is.
For ROM hackers 1.0 addresses
The Zozo flashback, _ca9d14 (CA/9D14), shows Maduin's magicite with event object NPC_4 ($13), which on that map is Siren's magicite, so hiding it afterwards hides an uncollected Siren. Use NPC_3 ($12), Ramuh's, which is always collected by then: change the object byte from $13 to $12 in create_obj at CA/9D15, obj_script at CA/9D16, show_obj at CA/9D1E, and the two hide_obj commands of the scenes that follow, at CA/A654 (_caa5fb) and CA/C537 (_cac4b0).
Credit: previously found and fixed by Novalia Spirit, my implementation
#149 A stray facing command in two Thamasa events no longer turns you to face down on the next map load.
In 1.0
Two Thamasa events left a facing command that ran on the next map load, so you arrived facing down instead of the way the scene meant.
Now
You now arrive facing the way the scene sets you.
For ROM hackers 1.0 addresses
Events _cbd209 (CB/D209, landing on the way to Thamasa) and _cc0804 (CC/0804, leaving Thamasa by airship). Each has a stray player_ctrl_off ($3B) before its load_map to the world map, at CB/D2D8 and CC/0922; it takes effect at the next map load and leaves the party facing down. Remove both (1 byte each), or, to keep every later address, replace each with $FD, a one-byte command that only ends the event's frame.
Credit: previously found and fixed by Gi Nattak, my implementation
#163 A Gau-only party with Shadow hired and Gau sent to the Veldt no longer ends up with no party members at all.
In 1.0
If Gau travelled alone after the battle for the Frozen Esper, you hired Shadow, sent Gau off with a Leap and walked to Narshe, Shadow left, leaving no party members and soft locking the game.
Now
When Shadow leaves and empties the party, the screen for choosing a new party now opens.
For ROM hackers 1.0 addresses
Shadow's farewell, event _ccd35c (CC/D35C). After char_party SHADOW, 0 it runs call _cacb95 (CC/D3BC), call RestoreParty (CC/E499) and unlock_camera, then returns; with Shadow the only member the party is left empty. Remove the call at CC/D3BC and insert after the unlock_camera, before the return at CC/D3C5 (51 bytes): set_case PARTY_CHARS sets switch $01A0 plus n for each character n in the party; if any is set run _cacb95 as before, otherwise open the change-group screen through _ccc1b3 (CC/C1B3).
set_case PARTY_CHARS
if_any
switch $01A0=1
; ... $01A1 to $01A6 = 1
switch $01A7=1
goto PartyLeft
if_any
switch $01A8=1
; ... $01A9 to $01AE = 1
switch $01AF=1
goto PartyLeft
call _ccc1b3
player_ctrl_on
return
PartyLeft:
call _cacb95
; the original return at CC/D3C5
Credit: previously found and fixed by Leet Sketcher, my implementation
#165 Eight faults in one room fixed: a bridge, a switch, a chest, a skull switch, a redraw and a lava row.
In 1.0
In one room on the Cave to the Sealed Gate's third basement floor, a switched bridge failed to return after a menu or battle, and a pressed switch stood up again. An opened chest looked shut, a skull switch never looked pressed, the moving island wiped the breaking bridge's tile, and a lava row was never put back.
Now
The bridges, switches, chest and lava now keep the look they should.
For ROM hackers 1.0 addresses
Cave to the Sealed Gate B3, start-up event _cb2e3d (CB/2E3D). Change the switch at CB/2E48 from $01F9 to $01FA, so the trapdoor redraw asks only its own record. Change the skull switch's tile $09 to $8A at CB/32AE and CB/33DC, and the island redraws' tile for {113, 10} from $AE to $AB at CB/32B8, CB/3344, CB/33E9, CB/347B and CB/3518. The press at _cb33c9 draws rows 10 to 22, the release 11 to 22: in the release's blocks at CB/35A1 (BG1) and CB/35E2 (BG2) make the size {109, 10}, {5, 13} and insert a first row (5 bytes each): BG1 $11,$11,$11,$11,$AB, BG2 $D0,$8F,$D0,$8F,$D0. More insertions below.
; _cb2ebb (CB/2EBB), before its return: the opened chest
if_switch $01F8=0, EventReturn
mod_bg_tiles BG1, {100, 13}, {1, 1}
.byte $12
; _cb2f65, before the return at CB/2FD0: the long bridge's record
switch $01FB=0
; head of _cb2fd1 (CB/2FD1): the pressed switch
mod_bg_tiles BG1, {58, 17}, {1, 1}, ASYNC
.byte $8A
; _cb3867 (CB/3867): BG1 $11 and BG2 $D0; _cb386e (CB/386E)
; adds BG2 $D0 and _cb3882 (CB/3882) adds BG2 $AE, each as
mod_bg_tiles BG1, {113, 10}, {1, 1}, ASYNC
.byte $11
mod_bg_tiles BG2, {113, 10}, {1, 1}, ASYNC
.byte $D0
wait_bg
Credit: previously found and fixed by Assassin, my implementation
#175 In split party dungeons, one party standing on a save point let the other party save and use tents anywhere.
In 1.0
In dungeons that split the party, such as Kefka's Tower and the Phoenix Cave, switching from a party standing on a save point to another let the new party save or use a Tent or Sleeping Bag anywhere until it took a step.
Now
A party can now save or use a Tent or Sleeping Bag only when it is itself on a save point.
For ROM hackers 1.0 addresses
ChangeParty (C0/6D77) hands control to another party in a split-party dungeon but leaves $1EB6 bit 5 (tile event) and $1EB7 bit 7 (on a save point) as the old party's tile set them, so the new party can save and use tents where it stands. Insert before the ldx at C0/6DD9, after the beq at C0/6DD7 that returns when the showing character did not change (10 bytes): lda #$20 / trb $1EB6 / lda #$80 / trb $1EB7.
Credit: previously found and fixed by Leet Sketcher, my implementation
#179 A timer due on the same frame as a tile trigger no longer drops the trigger, so the Zone Eater's ceiling judges the right tile and timers cannot skip event tiles.
In 1.0
Inside the Zone Eater, if the ceiling fell on the frame the party finished a step, it judged the party by the tile just left, so you could be crushed in a niche or survive in the open. The same timing let a speedrunner step past event tiles when one of the game's other timers ran out.
Now
The ceiling now always judges the tile the party is on, and timers can no longer skip event tiles.
For ROM hackers 1.0 addresses
The field loop calls CheckTimer (C0/BBF1) before CheckEventTriggers (C0/BC6F), so a timer due on an arrival frame starts its event and that tile's trigger is skipped. Keep a word at $115C, the $AF a trigger last ran at: DoTrigger stores $AF after the sta $05F4 at C0/BCD9; the trigger loop when nothing matched (before Done, C0/BCCF) and LoadMap (before C0/BEE7) store $FFFF. In CheckTimer, insert before C0/BC20 a jsr to a routine (59 bytes) answering nonzero when $AF differs from $115C and a trigger sits at $AF; on nonzero, set a flag and rts. Insert after the jsr ExecEvent at C0/00DA a jsr MyAgain (about 40 bytes), or a party walking over trigger tiles starves the timer. Then retarget the four Sealed Gate bridge guards (code).
MyAgain: lda Flag ; CheckTimer gave way this frame
beq Done
stz Flag
lda $84 ; not while a map load is pending
bne Done
jsr AtNoEvent ; and only once the trigger's event is over
bne Done
clr_a
jsr CheckTimer
jsr AtNoEvent ; did it start the timer's event?
beq Done
jmp ContinueEvent ; C0/9A6D: run it now
Done: rts
AtNoEvent: ; Z set when $e5-$e7 is NoEvent
ldx $e5
bne :+
lda $e7
cmp #^EventScript::NoEvent
: rts
; with the trigger first, a bridge timer Warped out of map 385 outwaits an arrival
; scene, then its clean-up stops that scene's timers; so at CB/2BB2, CB/2C57, CB/2D1E, CB/2D97:
if_switch $01F0=0, EventReturn ; CA/5EB3, was _cb2b06 (right: $01F1, was _cb2cb3)
Credit: previously fixed by Leet Sketcher, my implementation
#189 Shadow now properly gains Interceptor when he rejoins at Albrook, instead of keeping whatever state he already had.
In 1.0
When Shadow joins again at Albrook he kept whatever Interceptor status he had, so a player who never saw the scene that gives him the dog got him back without Interceptor until he was recruited again in the World of Ruin.
Now
He now gains Interceptor at Albrook whether he had it before or not.
For ROM hackers 1.0 addresses
Event _cbd209 (CB/D209), Shadow rejoining at Albrook. The and_status SHADOW, INTERCEPTOR at CB/D2AD keeps Interceptor only if he already had it. Insert set_status SHADOW, INTERCEPTOR (4 bytes, command $89) after it.
Credit: previously found and fixed by Novalia Spirit, my implementation
#197 In Narshe's weapon shop, the Ragnarok Magicite the NPC holds up is no longer drawn over the party leader's head.
In 1.0
In the World of Ruin, the Ragnarok Magicite the NPC in Narshe's weapon shop holds up was drawn in front of every other sprite, so it covered the party leader's head when the leader stood below him to talk.
Now
The Magicite now shows in front of the NPC but behind the leader.
For ROM hackers 1.0 addresses
NPC records of map 24 (Narshe weapon shop, World of Ruin): record 2, the magicite, at C4/235E, and record 3, the NPC holding it up, at C4/2367. The magicite's sprite priority is HIGH (byte 7, bits 4 and 5), which draws it over every sprite, the leader standing below included. Set those bits to normal and swap the two 9-byte records, since among normal NPCs the later record draws in front. The event _cc0b1e then names the magicite NPC_3: change the object byte of create_obj at CC/0B27, show_obj at CC/0B31 and hide_obj at CC/0B4B and CC/0B61 from NPC_2 to NPC_3.
Credit: previously found and fixed by Leet Sketcher, my implementation
#218 Stepping on a diagonal staircase now ticks poison, heals with the Tintinabar and clears tile and save-point flags like any other step.
In 1.0
Stepping up or down a diagonal staircase skipped poison damage and the Tintinabar's healing, and a save point or event tile the party had just left by the stairs could still be used.
Now
Stair steps now count like any other step.
For ROM hackers 1.0 addresses
UpdatePlayerMovement. A flat step (C0/4A03) runs IncSteps, then jsl DoPoisonDmg (C0/4A3F, poison and the Tintinabar) at C0/4A06, then UpdatePartyFlags, then clears the tile event bit ($1EB6 bit 5) and the save point bit ($1EB7 bit 7) at C0/4A1D. A diagonal stair step (from the jsr IncSteps at C0/496A) runs only IncSteps and UpdatePartyFlags. Move the jsl DoPoisonDmg and the ldy $0803 after it (7 bytes) to the end of IncSteps, at C0/4B82 ahead of its rts, where its branches land, and the two bit clears (16 bytes) to the end of UpdatePartyFlags, at C0/4B5F ahead of its rts, where its branches land. Both kinds of step then do all of it.
Credit: previously found and fixed by assassin17, Lenophis and Leet Sketcher, my implementation
#219 Five bugs fixed in the opening walk into Narshe, among them a facing glitch at the guards' scene, a misplaced town entrance trigger and Terra retaking the lead.
In 1.0
In the opening march on Narshe, stepping onto the guards' tile from the side left the leader facing sideways as the guards spoke, the town entrance trigger could be walked round and stepped on from the north, and with Wedge or Vicks put in front through the menu, three scenes made Terra the leader again until the next map, one of them with her back to Wedge and Vicks.
Now
The leader now turns to face the guards, the entrance trigger sits in the one-tile-wide passage every path north crosses, the chosen leader keeps the lead, and Terra faces the two.
For ROM hackers 1.0 addresses
Map 19 at the start of the game. The guards' scene _cc9b71 (CC/9B71) can be entered sideways: insert obj_script SLOT_1 / dir UP / end (4 bytes) before the create_obj at CC/9B77. The entrance trigger for _cc9c08 (CC/9C08), record at C4/03EC, sits in an open plaza: change its y byte to 29, the one-wide corridor, and insert obj_script SLOT_1 / move UP, 3 / end (4 bytes) before the create_obj at CC/9C0E. party_chars makes Terra leader whatever the party order: in _cc9b1d (CC/9B1D) insert dir DOWN first in Terra's script at CC/9B23, and update_party before the obj_script at CC/9B62, which then names SLOT_1; insert update_party before the return at CC/9E22 (_cc9db2) and CC/9EE4 (_cc9e23).
Credit: previously found and fixed by Leet Sketcher, my implementation
#220 Sabin's surprised line at the Imperial Camp no longer depends on whether you saw an unrelated Shadow scene.
In 1.0
At the Imperial Camp, after meeting Shadow in the South Figaro cafe, Sabin could talk as if he knew of the base without ever hearing of it.
Now
Sabin now knows of the base only after the NPC or Shadow at the house near the camp tells him.
For ROM hackers 1.0 addresses
Camp line picker _cb0c75 (CB/0C75) chooses Sabin's line on switch $000B, which every Shadow introduction sets. Let switch $000C, unused in 1.0, mean "Sabin was told of the base". Change the switch operand of both if_switch commands, at CB/0C75 and CB/0C7E, from $000B to $000C (same size). Insert switch $000C=1 (2 bytes) right after each of the two lines that tell him: in the house script _cb0a5f (CB/0A5F) before its call _cad00f at CB/0A6C, and in _cb0ab9 (CB/0AB9) before the dlg of message 458 at CB/0ABF.
Credit: previously found and fixed by DrakeyC, my implementation
#221 The camera no longer slides off center when an NPC-triggered battle starts and the room can't reach where it was panning.
In 1.0
When an NPC started a fight by being bumped into, the camera could pan to a spot the room cannot scroll to, so the view slid off centre and the room came back from the fight still off centre.
Now
The view now stays put, and the room comes back from the fight as it was.
For ROM hackers 1.0 addresses
UpdateCollisionScroll, C0/73AC. When an NPC collision starts a fight, the pan destination $0557/$0558 is set to the touched character's raw tile position ($086A,y and $086D,y shifted right four), and EventCmd_4c (C0/A591) saves it as the scroll position the map reloads with. In a room no larger than the screen the camera cannot centre there, so it slides off and stays. When the touched character is in the party the camera follows, use the current centre $0541/$0542; in a split-party scene it can be another group's leader, whom the pan must still reach. Replace the 23 bytes from the longa at C0/73D7 up to the stz $27 at C0/73EE with a jsr and 20 nop.
jsr MyFix ; at C0/73D7, then 20 nop
MyFix: lda $0867,y ; the touched character's party (bank C0 free space)
and #$07
cmp $1a6d ; the party the camera follows
bne @other
lda $0541 ; screen centre x, in tiles
sta $26
lda $0542 ; screen centre y, in tiles
sta $28
rts
@other: longa ; another group: the original 23 bytes
lda $086a,y
lsr4
sta $26
lda $086d,y
lsr4
sta $28
shorta0
rts
Credit: previously found and fixed by Leet Sketcher, my implementation
#223 When Narshe's secret entrance closes behind Locke and Terra, it no longer leaves one mismatched tile until the map reloads.
In 1.0
When Narshe's secret entrance closed behind Locke and Terra in the opening, one of its tiles was the wrong one and stayed mismatched until the map reloaded.
Now
The closed entrance now looks whole.
For ROM hackers 1.0 addresses
The scene where Locke and Terra escape through Narshe's secret entrance closes it with mod_bg_tiles BG1 at (14,54), 3 by 3, at CC/B00B. The first tile of its third row, the byte at CC/B016, is $D1; the row's last tile is $D2. Change that byte to $D2.
Credit: previously found and fixed by Novalia Spirit, my implementation
#224 The Phoenix Magicite's chest no longer appears shut again on a later visit, since it is given by a cutscene, not the chest.
In 1.0
In the Phoenix Cave, the Phoenix Magicite is handed over in a cutscene rather than taken from its chest, and on every later visit that chest was drawn shut again though it could not be opened.
Now
Once the Phoenix Magicite has been taken, its chest is drawn open.
For ROM hackers 1.0 addresses
LoadMap, after the map's start-up event (SkipStartupEvent, C0/C0A4). The Phoenix magicite is given by a cutscene, which sets event bit $00D7 but not the chest's treasure bit, so InitTreasures (C0/15D7) never swaps the chest at (6,47) on map 313 to its open tile. Insert jsr MyFix (3 bytes) at C0/C0A4. The routine, about 50 bytes of free space in bank C0, paints BG1 tile $12 there with ModifyMap (C0/1EC4) while $00D7 ($1E9A bit 7) is set, and asks for BG1 to be uploaded ($055A = 4).
MyFix: longa
lda $82 ; map index
cmp #313
shorta ; sep keeps the flags
bne @done
lda $1e9a ; bit 7 = event bit $00D7
bpl @done
ldx #.loword(ChestOpen)
stx $8c
lda #^ChestOpen
sta $8e
lda #6 ; x
sta $8f
lda #47 ; y
sta $90
stz $2a
stz $2b ; BG1 map data
lda #$04
sta $055a ; BG1 tiles to VRAM
jsr ModifyMap
@done: tdc ; A = 0 on the way back
rts
ChestOpen: .byte 1, 1, $12
Credit: previously found and fixed by Leet Sketcher, my implementation
#225 The Fanatics' Tower cultists no longer accuse you of taking the Gem Box unless you actually opened its chest.
In 1.0
Stepping onto the tile in front of the Fanatics' Tower's Gem Box chest was enough to arm the cult's ambush, so a party that left the chest shut still met the cultists on the roof.
Now
Only opening the chest and taking the Gem Box arms the ambush.
For ROM hackers 1.0 addresses
Map 366's trigger at (7,8), in front of the Gem Box, runs _cc5440 (CC/5440), which sets switch $02DA (the ambush is armed) and NPC switch $0699 (the cultists stand on the roof, map 364) whether or not the chest is opened. Remove the trigger: map 366's entry in EventTriggerPtrs (C4/02DC) takes map 367's value, 5 more, so the record at C4/16F2 is no longer read. Arm both from the chest instead: in CheckTreasure (C0/4B83), after the treasure bit is set, insert jsr MyFix (3 bytes) before the lda $1f at C0/4C37; 22 bytes of free space in bank C0. The Gem Box is treasure bit $00B8.
Credit: previously found and fixed by Leet Sketcher, my implementation
#226 The lead party member no longer stays stuck in a pose, or mid-stride on stairs, after a scene hands control back.
In 1.0
After a scene handed control back, the lead party member could keep the scene's last pose, such as kneeling or lying down, or end mid-stride on a staircase, until the player took a step.
Now
A leftover pose now relaxes to standing, and a walk that stops on stairs ends standing. Poses a scene means to keep, such as Zozo's rooftop landings, stay until you move.
For ROM hackers Method
Two bytes of RAM, $0746 and $0747 (unused in 1.0): a relax countdown and a held-frame note. LeaderHandedBack runs where control returns: in EventCmd_fe when the event stack empties (before C0/B917) and in UpdateActiveObjAction where the party becomes user-controlled (before the lda #$02 at C0/7696). With the leader's walking animation off it records the frame as held on purpose, otherwise it starts a 69-frame countdown. In UpdateObjFrame (C0/5839) the not-moving branch (the beq at C0/588D) calls LeaderStanding for the leader: in the player's hands, a stride frame of its own facing stands at once (the stairs), and a pose whose countdown ran out relaxes by a one-byte-per-frame table. Clear both bytes in InitNewGame, before the ldx at C0/BEAF. The Zozo landings _ca962d (CA/962D) and _ca9653 (CA/9653) drop the anim_on after their kneel; Vector's CC/93F4 and CC/9798 add anim_off after the kneel.
Credit: previously found and fixed by SilentEnigma, my implementation
#227 On the roof of the Fanatics' Tower, the party no longer turns away from MagiMaster before facing him.
In 1.0
On the Fanatics' Tower roof, the party's closing turn to face MagiMaster always passed through facing right, so a party facing up turned its side to him and back, and one facing left first spun round to face right.
Now
The turn passes through facing right only when the party faces down, and otherwise goes straight to MagiMaster.
For ROM hackers 1.0 addresses
Event _cc544b (CC/544B), MagiMaster on the tower roof. The scene gives the player control before its closing turn, and the leader's obj_script at CC/5570 always steps through dir RIGHT (CC/5572) and wait 1 before dir UP. Insert before the dir RIGHT a branch to the dir UP unless the leader faces down (3 bytes), and raise the obj_script's length byte by 3; the wait_obj before it stays. 1.0 has no facing test: this uses $F0 from #138, which branches forward by its first operand when the facing is in the set in its second (bit 0 up, 1 right, 2 down, 3 left); its handler sits in free space in bank C0, ends in ObjCmd_fd (C0/7B4B) and fills ObjCmdTbl's empty slot at C0/785B.
obj_script SLOT_1
.byte $f0, $06, $0b ; unless facing down, skip to dir UP
dir RIGHT
wait 1
dir UP
end
Credit: previously found and fixed by Leet Sketcher, my implementation
#228 The scene cutting between Sabin at the Imperial Camp and Cyan at Doma no longer shows the wrong party members, or moves them between controllers in two-player mode.
In 1.0
In Sabin's scenario, each cut between Sabin at the Imperial Camp and Cyan at Doma took Sabin (and Shadow, if hired) out of the party and back, which could change the marching order and show the wrong party members. With two controllers it also moved party members from one player to the other.
Now
Cyan now has a party of his own, so Sabin's group keeps its order and each player keeps their party members.
For ROM hackers 1.0 addresses
Sabin's scenario cuts to Doma at _cb9aae (CB/9AAE) and _cba0ec (CB/A0EC), and back to the camp at _cb0bc4 (CB/0BC4) and _cb1337 (CB/1337). Each rebuilds party 1 with char_party, which always assigns a fresh marching slot, so Sabin's and Shadow's order is lost. Give Cyan party 2 and switch with activate_party instead, as below. The Doma cuts also drop set_case PARTY_CHARS and if_case CHAR::SHADOW, whose call to _cba3b9 took Shadow out of party 1. Every sequence shrinks; fill the freed bytes with $FD, the event command that only waits a frame.
; CB/9AAE: delete set_case and if_case
char_party CYAN, 2 ; CB/9ABF, was party 1
switch $02E2=1
delete_obj SHADOW ; CB/9AC4, was char_party SABIN, 0
delete_obj SABIN
activate_party 2 ; new, before load_map at CB/9AC9
; CB/A0EC: delete set_case and if_case
create_obj CYAN
activate_party 2 ; CB/A0F4, was char_party CYAN, 1
update_party
delete_obj SHADOW ; CB/A0F8, was char_party SABIN, 0
; CB/0BC4: delete char_party at CB/0BCC, CB/0BD1, CB/0BD4
create_obj SABIN
delete_obj CYAN
activate_party 1 ; CB/0BD9, was sort_obj
update_party
sort_obj
; CB/1337: delete char_party at CB/1334 and CB/133F
char_party CYAN, 0
delete_obj CYAN
create_obj SABIN
activate_party 1 ; new
update_party ; new
sort_obj
Credit: previously found and fixed by Leet Sketcher, my implementation
#229 Gungho's two conversations about Strago in Thamasa are now consistent about whether Strago is present.
In 1.0
Talking to Gungho in Thamasa, without Strago he ended by speaking to him as if he stood there, and with Strago in front of him he talked about him as if he were elsewhere.
Now
Gungho now talks about an absent Strago and speaks to one who is there.
For ROM hackers 1.0 addresses
Gungho's Thamasa talk. In _cb77da (CB/77DA), the branch without Strago, the dlg at CB/77E4 shows message 2854 ($0B26), which speaks to Strago; change its message number to 2853 ($0B25), same size. In _cb77e8 (CB/77E8), the branch with Strago, delete the dlg of message 2853 at CB/7819 (3 bytes), which speaks of him in the third person; in a binary patch, fill it with three $FD, the event command that only waits a frame.
Credit: previously found and fixed by Assassin, my implementation
#242 Pitching a tent no longer accidentally starts the Floating Continent scene.
In 1.0
Using a Tent plays the rest scene on the airship deck map with the party one tile above the trigger for the scene that leads to the Floating Continent, and one step down from there started that scene out of order.
Now
The scene now starts only when the story has reached it.
For ROM hackers 1.0 addresses
Map 3's trigger at (8,9) runs _ca5ade (CA/5ADE), which starts the Floating Continent scenes, and a tent puts the party at (8,8) on the same map, so one step down starts them. The story path _ca5ad4 (CA/5AD4) sets switch $02BD just before it loads map 3, whose start-up event _cae8f4 (CA/E8F4) clears it again before the trigger can fire. Insert if_switch $02BD=0, EventReturn (6 bytes; EventReturn is CA/5EB3) and switch $02BD=0 (2 bytes) as the first commands of _ca5ade, and remove the switch $02BD=0 at CA/E8FC from _cae8f4, or the guard stops the story's own flight too.
Credit: previously found and fixed by The Cutting Room Floor, my implementation
#244 The hidden Sprint Shoes in Narshe can now actually be reached; the tile beside them used to be inside the cliff.
In 1.0
A hidden Sprint Shoes in Narshe sat inside the cliff above the town, with no tile next to it that could be stood on, so it could not be picked up.
Now
The hidden spot moves one tile west, beside the path behind the houses, where the party can search it facing right.
For ROM hackers 1.0 addresses
Treasure data TreasureProp (ED/8634, 5 bytes a record: x, y, then switch, type and contents). Record 0, map 20's hidden Sprint Shoes, sits at (55,8) inside the cliff, and its one open neighbour (56,8) belongs to an area the town's walkable tiles never reach. Change its x byte at ED/8634 from $37 to $36: at (54,8) it borders the path at (53,8) and opens facing right.
Credit: previously found by Dragonsbrethren and fixed by DarkMage, my implementation
#254 Banon no longer flickers between himself and Esper Terra when riding a chocobo at the hidden stable.
In 1.0
Banon's sprite sheet stopped short of the two chocobo-riding sprites every party member has, so at the hidden chocobo stable he flickered between himself and an Esper.
Now
Banon now has riding sprites of his own, drawn for this ROM hack from his walking sprite.
For ROM hackers 1.0 addresses
Map sprite graphics, MapSpriteGfx (D5/0000). Banon's sheet (sprite graphics 17, at D6/7F60) has 171 tiles and no chocobo riding frames, so the field reads the riding tiles 171 to 180 past its end, from Esper Terra's sheet at D6/94C0. Add ten tiles (320 bytes) after the sheet, drawn anew from Banon's own walking frame in the pose of Strago's riding frame. The grown sheet (5,792 bytes) no longer fits in place: move it to free space inside one bank and repoint its entries in MapSpriteGfxPtrsLo (C0/D114), MapSpriteGfxPtrsHi (bank byte, C0/D25E), the battle's CharGfxPtrs (C2/CE76) and the menu's MenuCharGfxPtrs (CF/F955).
Credit: previously found and fixed by Leet Sketcher, my implementation
#256 Sabin's written line responding to Gerad denying he's Edgar now actually plays when Sabin is in the party.
In 1.0
In Nikeah, when Celes calls Gerad by Edgar's name and he denies it, Sabin said nothing even if he had rejoined the party, though the script holds a line for him.
Now
If Sabin has rejoined, his line now plays at that moment.
For ROM hackers 1.0 addresses
Event _ca921a (CA/921A), where Celes confronts Gerad at Nikeah. Dialogue 2346 ($092A), Sabin's line for this moment, is in the dialogue table but nothing calls it. Insert after dlg DLG_2344, before the wait_45f at CA/9238 (10 bytes): set_case AVAIL_CHARS, then if_switch $01A5=0 past the line (Sabin, character 5, not on the roster), then dlg DLG_2346.
set_case AVAIL_CHARS
if_switch $01A5=0, NoSabin ; Sabin not available
dlg DLG_2346
NoSabin: ; the wait_45f at CA/9238
Credit: previously found and fixed by Leet Sketcher, my implementation
#260 The Phoenix Cave spikes and Sealed Gate cave floor no longer damage a party member who has been turned to stone.
In 1.0
The Phoenix Cave spikes and the floor of the cave to the Sealed Gate took HP from a party member turned to stone, though stone takes no damage anywhere else.
Now
A party member turned to stone now takes no damage from these floors.
For ROM hackers 1.0 addresses
Event command $8B (change HP), EventCmd_8b at C0/AE7B. Its subtract path at C0/AEAE spares a character at 0 HP but never reads status, so the Phoenix Cave spikes and the Sealed Gate cave floor, both made with this command, hurt a petrified character. Insert at C0/AEAE, before the longa (7 bytes, A still 8-bit): lda $1614,y (status 1), and #$40 (Petrify), bne to C0/AED3, the command's exit.
lda $1614,y ; status 1
and #$40 ; petrify
bne @aed3 ; stone takes nothing
; the longa at C0/AEAE follows
Credit: previously found by Master ZED, my implementation
#262 A wandering NPC can no longer block the forced walk into the Returners' Hideout and lock up the game.
In 1.0
On the party's first arrival at the Returners' Hideout, a wandering NPC could block the walk in and soft lock the game.
Now
That NPC now stands still out of the way.
For ROM hackers 1.0 addresses
NPCProp (C4/1D52), 9 bytes per NPC. Record 505 at C4/2F13, the third NPC of map 109 (the Returners' Hideout, at (27,25)), is the only one on the map with random movement, three tiles from the forced walk of the first arrival, and a blocked forced step waits for ever. Byte 7 at C4/2F1A: change movement, bits 0 to 3, from 3 (random) to 0 (none).
Credit: as in the Pixel Remaster, my implementation
#264 Relm no longer gets stuck recovering in bed forever, and Owzer's Mansion no longer stays closed, if Shadow was left on the Floating Continent.
In 1.0
If Shadow was left on the Floating Continent, Relm's recovery could only be started from three tiles outside Strago's house, and players who missed them left Relm in bed and Owzer's Mansion shut.
Now
Leaving Thamasa by any exit now starts Relm's recovery, at most once per visit.
For ROM hackers 1.0 addresses
Thamasa, map 344. Leaving town, _cb7d69 (CB/7D69) spends the Relm recovery step armed in $0196, but only _cb7d5c (CB/7D5C), on three tiles near Strago's house, arms it. When Shadow was lost on the Floating Continent nothing else arms it, and the south exit, a row of six tiles below a seven tile wide walk at y 47 (x 19 to 25), can be reached without touching those tiles. Add an event in free space of the event banks (9 bytes): call _cb7d5c, call _cb7d69, return. Point all ten exit triggers at it: records C4/15FD to C4/162A, 5 bytes each (x, y, then the 3-byte event pointer, counted from CA/0000). $01F7 still allows one arming per visit.
MyExit: call _cb7d5c ; arm, at most once per visit
call _cb7d69 ; then spend
return
Credit: previously found by LeviathanMist, my implementation
#267 Shadow no longer wrongly appears in both the party and the statue cutscene if hired back after skipping him and beating Atma Weapon.
In 1.0
If you skipped Shadow at the start of the Floating Continent, beat Atma Weapon, then went back and hired him, he appeared twice at the statues: in the party and in the scene holding Kefka off.
Now
He is now removed from the party in the corridor between Atma Weapon's spot and the statues.
For ROM hackers 1.0 addresses
Floating Continent, map 394. _cad9fc (CA/D9FC), the step that takes Shadow out of the party, runs only after Atma Weapon falls with Shadow in the party, or when flying back to the airship early. Add the event below in free space of the event banks (18 bytes) and a trigger for it at (60,13), the corridor tile between Atma's spot and the statues. Insert its 5-byte record (x, y, event pointer counted from CA/0000) after map 394's last record, the one at C4/1940, moving the later records up 5 bytes; the trigger block has 18 spare bytes at its end in 1.0. Add 5 to every later EventTriggerPtrs entry, C4/0316 to C4/0340 (the end pointer).
MyFix: if_switch $035F=1, EventReturn ; Atma still stands
set_case PARTY_CHARS
if_switch $01A3=0, EventReturn ; Shadow not in the party
call _cad9fc
return
#269 Figaro Castle now surfaces exactly where it's drawn on the World of Ruin map, after every journey and at its first surfacing, and the party walks out below its left door.
In 1.0
In the World of Ruin, Figaro Castle came up one tile off where it is drawn on the world map, after each journey under the sand and at its first surfacing after the Tentacles, and walking out after a journey could leave the party in a doorway.
Now
The castle comes up where it is drawn, and the party walks out one tile below its left door.
For ROM hackers 1.0 addresses
World of Ruin Figaro Castle trips. The blocks lie 28 tiles apart in x and 27 in y, but each trip is one-byte diagonal steps, a tile each once the castle has sunk, so it surfaced a tile off; continue journey (CA/6969) reloads the world at (67, 71), a tile off each tail. To Kohlingen: change CA/6930 from $A3 (up-left) to $83 (left) and retarget the if_switch at CA/697F to CA/6930. To the desert: change CA/69AD from $A1 to $81, retarget the if_switch at CA/6979 to CA/69AE, and put dir UP ($CC) before the $FE, so the exit is below the left door: overwrite CA/69B6 to CA/69BB with a jump ($B0 on switch $0127, always clear) to 14 free bytes holding six $A1, $CC, $FE and a jump back to CA/69BD. First surfacing: y 84 at CA/6A0E becomes 85.
Credit: previously found by Master ZED, my implementation
#284 Sabin's flashback to leaving Figaro Castle no longer permanently removes four castle guards after being viewed once.
In 1.0
Sabin's flashback to leaving Figaro Castle hides four castle guards for the scene and nothing brought them back, so after the flashback had been seen they were missing for the rest of the game.
Now
The guards now stand at their posts again after the flashback.
For ROM hackers 1.0 addresses
Sabin's flashback clears switch $030E at CA/7316. That is the NPC switch of four Figaro Castle guards, and no later event sets it, so they stay gone. Insert switch $030E=1 (2 bytes) before the call _cacf96 at CA/7583, where the flashback returns to the real castle. The courtyard guard's switch $0315, cleared next to it at CA/7318, is #581.
Credit: previously fixed by DrakeyC, my implementation
#285 Two glass tube tiles in the Magitek Factory lab no longer change color with the Config font color setting.
In 1.0
Two tiles of the glass tubes in the Magitek Factory lab used the menu font's colours, so changing the font colour on the Config screen recoloured them on the map.
Now
Both tiles now use the tube's colours.
For ROM hackers 1.0 addresses
BG3 map graphics MAGITEK_TUBES, LZSS at E6/C300, pointer in MapGfxBG3Ptrs (E6/CD60). The decompressed block is $40 attribute bytes, one per 16x16 BG3 map tile with the palette in bits 2 to 4, followed by $1000 bytes of 2bpp graphics. Entries $01 and $17 hold $00, palette 0, which BG3 shares with the font, so those two tiles follow the Config font colour. Change both to $0C, the palette of their neighbours. Recompress; if it grows, move it to free space and repoint.
Credit: previously fixed by Gi Nattak, my implementation
#293 A party wiped out by losing a Colosseum wager can no longer keep walking the field afterward.
In 1.0
If every party member fought at the Colosseum and each one died, there was no Game Over: the dead party could walk around the field, and the next battle anywhere else ended at once in defeat.
Now
After a Colosseum match, a party with nobody still standing is now a Game Over.
For ROM hackers 1.0 addresses
Colosseum, _cb7972 (CB/7972), which every way out of colosseum_battle (CB/796C) passes after the match. Nothing checks the party there, so a party that lost the match walks the field dead. Insert if_party_dead CA/5EAE (4 bytes) at the head of _cb7972; CA/5EAE is the call GameOver inside _ca5ea9. Needs event command $66, if_party_dead, from #395: it jumps when every member of the active party is wounded, stone or a zombie.
Credit: previously found by Master ZED and Assassin, my implementation
#303 The Serpent Trench cave pool now drains completely and its top left water tile no longer reappears after opening the menu.
In 1.0
In the Serpent Trench cave, the switch drained the pool but stopped just short of empty, and opening and closing the menu then brought back two tiles of water at the pool's top left corner.
Now
The pool now drains completely and stays empty.
For ROM hackers 1.0 addresses
Serpent Trench pool, map 175. The drain event _ca8ac4 (CA/8AC4) scrolls BG3 one pixel short: change the vertical rate of the scroll_bg at CA/8AD3 from -8 to -9 (byte $F8 to $F7 at CA/8AD5). The map's startup event _caee1e (CA/EE1E) redraws the drained pool with the mod_bg_tiles BG3 at CA/EE24, which starts one column right of the pool, so the water at (45,39) and (45,40) returns after the menu. Make it {45, 38}, {8, 7} with 56 zero bytes: insert 7 bytes.
Credit: previously fixed by Gi Nattak, my implementation
#317 Cyan's treasure chest event now checks its permanent opened flag, so repeated interaction can't replay the opening scene.
In 1.0
Cyan's locked treasure chest did not check whether it was already open, so examining it again could replay the opening sound and scene.
Now
Examining the open chest again now does nothing.
For ROM hackers 1.0 addresses
Cyan's chest, event _cc42bf (CC/42BF). It tests only switch $0297 (unlocked), never its own opened switch $01D9, so talking to the chest again replays the sound and scene. Insert if_switch $01D9=1, EventReturn (6 bytes; EventReturn is CA/5EB3) at its head.
Credit: previously found by Novalia Spirit, my implementation
#318 A stationary bandit in World of Ruin South Figaro now stands correctly behind the table instead of on top of it.
In 1.0
In South Figaro in the World of Ruin, a bandit who never moves stood on top of a table.
Now
He now stands behind the table.
For ROM hackers 1.0 addresses
NPC data. The World of Ruin bandit in South Figaro (NPC switch $037A, event _ca802e) has its record at C4/2A03. Its Y position, byte 5 (C4/2A08, low 6 bits), goes from 22 to 21 ($16 to $15). That puts it behind the table, where the World of Balance soldier of the same map stands.
Credit: previously found by Novalia Spirit, my implementation
#319 Figaro's guards no longer welcome King Edgar back before he's ever left the castle.
In 1.0
When Edgar was in the party, Figaro's guards welcomed their king back on the first visit to the castle, before he had ever left it.
Now
The guards now save the welcome back for a real return later in the story.
For ROM hackers 1.0 addresses
Figaro Castle entrance guards, _ca71af (CA/71AF). With Edgar in the party the if_switch at CA/71B0 jumps to the welcome-back line at _ca71ba (CA/71BA) at any point in the story. Insert if_switch $0048=0, CA/71B6 (6 bytes) just before _ca71ba and point the if_switch at CA/71B0 at it, so the line also needs switch $0048, set in the Narshe scene where the party splits up. Otherwise the ordinary greeting at CA/71B6 plays. Other events also jump to _ca71ba; they keep their target.
Credit: previously found by Novalia Spirit, my implementation
#320 Imperial Base guards keep starting battles after you enter and leave the main building, instead of stopping once the exterior reloads.
In 1.0
At the Imperial Base, the nine guards outside started no more battles after the party went into the main building and back out.
Now
The guards now start their battles again after the party comes out, for as long as the story has them posted there.
For ROM hackers 1.0 addresses
Imperial Base exterior, map 377. _cb25b9 (CB/25B9, 29 bytes), reached from the entrance trigger, turns on the nine guards' collision activation once, guarded by switch $01B5, but every map load clears it, so after a visit to the main building the guards no longer start battles. Split the event as below (40 bytes: insert 11) and point map 377's startup entry, MapInitEvent+$46B (D1/FE6B), at MyInit. The $0076 test keeps the base empty during the Sealed Gate mission.
_cb25b9: ; CB/25B9
if_switch $01B5=1, EventReturn
call MyInit
switch $01B5=1
return
MyInit: ; also map 377's startup event
if_switch $0076=1, EventReturn
switch $01CA=1
collision_on NPC_1
; ... the same for NPC_2 to NPC_9
return
Credit: previously found by Novalia Spirit, my implementation
#392 In the Cave to the Sealed Gate, a sunk bridge piece no longer reappears as a walkable stray tile until its switch is flicked back.
In 1.0
In the Cave to the Sealed Gate, pressing the island's switch after the chest's switch had sunk a bridge piece into the lava brought that piece back as a stray tile the party could walk on.
Now
The piece now stays lava until the chest's switch is flicked back.
For ROM hackers 1.0 addresses
Cave to the Sealed Gate, basement 3. The island switch's press _cb33c9 (CB/33C9) and release _cb3506 (CB/3506) redraw a 5 by 13 block of BG1 at {109,10}, whose top row holds {113,10}, the bridge piece the chest switch changes (_cb386e, CB/386E, switch $01F7). Neither looks at $01F7, so the redraw undoes it. Insert if_switch $01F7=1, _cb3867 (6 bytes) before the return at the end of each, at CB/3505 and CB/362A. In 1.0 _cb3867 (CB/3867), which the map's start-up calls under the same switch (CB/2EA7), draws $AB, the tile _cb3882 gives the piece when the switch is cleared; this needs #165's repair of it, which makes it draw what _cb386e draws.
Credit: previously fixed by Assassin, my implementation
#393 Closing the menu in the doorway the South Figaro clock opens no longer breaks the tiles or lets the party walk through the wall.
In 1.0
In the basement of the South Figaro house where Celes is rescued, standing in the doorway the clock opens, then opening and closing the field menu, drew the party behind the door frame, garbled the tiles beside the clock and let the party walk through the wall into the jars.
Now
The room now stays as it was, with the wall still blocking the way.
For ROM hackers 1.0 addresses
LoadMap (C0/BEBC) calls DrawOpenDoor (C0/4D17) before the map's start-up event. DrawOpenDoor keeps only each opened door's position and picks the door graphic from the tile it finds there. At the clock room the door exists only because the start-up paints it, so it reads the layout's wall, falls through to the 3 by 2 big door and paints it over the jars. Replace the jsr DrawOpenDoor at C0/BF6D with three nop and insert it at SkipStartupEvent (C0/C0A4, 3 bytes). Since the tiles are then already in video memory, insert lda #$04, sta $055A (5 bytes, BG1 update) before its jsr ModifyMap at C0/4D48.
Credit: previously found by Master ZED and Xenomic and fixed by Leet Sketcher, my implementation
#394 The White Cape chest in the Returners' Hideout can now be opened from below, not only from the side.
In 1.0
In the Returners' Hideout, the White Cape chest at the end of the treasure room's secret passage opened only from its left side.
Now
Pressing the button from below now opens it too.
For ROM hackers 1.0 addresses
CheckTreasure refuses a chest to a party on the lower level standing on a bridge tile. The floor below the White Cape chest (map 110, {60,20}) is tile $52 of the caves_furniture set, marked bridge. Give the hideout its own copy: tile $46, unused wherever this tileset is used. Tileset caves_furniture (DE/CDED, LZSS): tile numbers at $46, $146, $246, $346 become $5D, $4E, $4F, $50 ($52's; the attribute bytes already match). Tile properties (D9/B0F7, LZSS): byte $46 becomes $02 (lower level). Layout returners_hideout_bg1 (DB/68DB, LZSS, 64 wide): byte $53C becomes $46. Recompress each; if one grows, move it to free space and repoint.
Credit: previously found by Master ZED and fixed by Leet Sketcher, my implementation
#395 If Cyan alone wins an Imperial Camp fight after Sabin's party falls, it is now a Game Over like anywhere else.
In 1.0
At the Imperial Camp, if Sabin and Shadow both fell and Cyan won a battle alone, the game carried on with the two of them at 0 HP.
Now
That now leads to the Game Over scene.
For ROM hackers 1.0 addresses
In the three Imperial Camp fights Cyan, an AI guest, counts as standing, so _ca5ea9 (CA/5EA9) sees no Game Over when he wins alone. Add event command $66 (unused in 1.0, table entry at C0/9926) as if_party_dead addr: for each of the 16 characters enabled in $1850 ($40) and in the active party (bits 0 to 2 equal $1A6D), test status 1 at $1614+$25*n with $C2. If one is clear, skip the command's 4 bytes; else jump like if_switch. Needs 62 bytes of free space in bank C0. After the call _ca5ea9 following each battle, at CB/14B0, CB/1551 and CB/15FE, insert if_party_dead to CA/5EAE, the call GameOver in _ca5ea9.
Credit: previously found by DrakeyC, my implementation
#396 Unequipping the party from the Falcon now also removes Umaro's two relics.
In 1.0
On the Falcon, choosing everyone, or only those not in the party, for "Unequip some party members?" stripped the others but skipped Umaro, so his relics stayed on.
Now
Umaro's two relics now come off and go to the bag, while his weapon and armour stay on.
For ROM hackers 1.0 addresses
The Falcon pilot's answers (_cc3510, CC/3510) have no remove_equip for Umaro. Give remove_equip (EventCmd_8d) a relics-only mode in bit 7 of its character byte: insert and #$7F after the lda $eb at C0/9FCE (2 bytes); after the ldx at C0/9FDD insert lda $eb, bmi to a routine that does inx four times (first relic slot, $1623), ldy #2 and branches past the esper removal to the slot loop at C0/9FE5 (13 bytes in all). Then insert set_case AVAIL_CHARS, if_switch $01AD=0 past it, and remove_equip $8D (Umaro, 13, plus bit 7) at _cc3591 (CC/3591); at _cc3664 (CC/3664) also skip it when set_case PARTY_CHARS finds him in the party.
Credit: previously found by Zak Undersn via the Final Fantasy Wiki, my implementation
#403 Terra's "M-M-Magic" scene and Ultros's farewell now wait for the party to actually be there to see them.
In 1.0
Terra's "M-M-Magic" scene after her first spell in battle played even when that spell had wiped out the party, and behind a black screen when it was Warp. Ultros's farewell on the Lete River had the same fault.
Now
Both scenes now play only while the party is still in the battle to see them. If Terra's first spell wipes the party or warps it out, her scene waits for her next spell.
For ROM hackers 1.0 addresses
Action command $23, battle event, at C2/4F57, queues a battle event script. Terra's scene (script 6) is queued with her first spell, which spends $3EBC bit 1 at the magic handler (C2/1741); Ultros's farewell (script $0A) as he dies. Replace the 5 bytes at C2/4F57 with a jml to a routine (4 bytes): for scripts 6 and $0A, when $3A74 (characters alive) is 0 or $3A6E is 2 (a Warp pending), skip, setting $3EBC bit 1 again for script 6, and jml to the rts at C2/4F56; otherwise lda $B6, xba, lda #GFX_CMD value as before and jml to _c262bf (C2/62BF).
Credit: previously found by Novalia Spirit, my implementation
#419 Terra at the Returners' Hideout, and Sabin and Cyan on the Phantom Train, if fallen, now get up with 1 HP before control returns.
In 1.0
The scene that leaves Terra alone at the Returners' Hideout, and the Phantom Train scene where the ghosts leave, could hand back control with Terra, or Sabin and Cyan (and Shadow, if he came), knocked out, so any battle was an instant Game Over.
Now
Before control returns, these scenes now bring the fallen back with 1 HP.
For ROM hackers 1.0 addresses
Two event ends hand control to a party with nobody alive. Hideout arrival, event _caf7dc (CA/F7DC): insert clr_status TERRA, DEAD / inc_hp TERRA, HP_1 (7 bytes) before the player_ctrl_on at CA/F938. Phantom Train, the ghosts leaving: at CB/AE20, CB/AE6E and CB/AEDA (events _cbadd4, _cbae29 and _cbae88, the last with Shadow along), replace call _cb2e2b with a call to a new subroutine (19 bytes of free event space): clr_status and inc_hp HP_1 for SABIN and for CYAN, then call _cb2e2b and return. Before the call at CB/AEDA also insert the same two commands for SHADOW (7 bytes).
Credit: previously found by Novalia Spirit, my implementation
#420 In Cyan's dream, the party can no longer walk past the two-soldiers flashback while its trigger tiles are switched off.
In 1.0
In Cyan's dream on the train, the flashback of the two soldiers gave control back while its trigger tiles were switched off, so the party could walk away during it and reach the roof.
Now
The party now waits in place until the flashback ends.
For ROM hackers 1.0 addresses
Event _cb9335 (CB/9335), the soldiers' flashback in Cyan's dream (map 143). It gives control back with player_ctrl_on at CB/9354, before the flashback plays, while its trigger tiles are off. Move that command to the end: delete it at CB/9354 and insert it before the return at CB/93AA, after switch $0197=1. The event keeps its size; the commands between move up 1 byte.
Credit: previously found by Novalia Spirit, my implementation
#421 In Cyan's dream, the Lump of Metal chest drawn back unpushed after the menu is now also counted as unpushed.
In 1.0
In Cyan's dream on the train, opening the menu after pushing the Lump of Metal chest drew it back in its old place while the game still counted it as pushed, so stepping away played the chest being pushed back though it was already there.
Now
The game now counts the chest as unpushed to match, so stepping off and back on pushes it again.
For ROM hackers 1.0 addresses
Event _cb91d3 (CB/91D3), the start-up of map 144 in Cyan's dream. It redraws the Lump of Metal chest from $017E and $0180, but a push made with $0187 clear sets only $01F8, so after the menu the chest is drawn in its old place while $01F8 still says pushed. Insert at the head of the event (8 bytes): if_switch $0187=1 to a label after the next command, then switch $01F8=0.
if_switch $0187=1, @redraw
switch $01F8=0 ; the chest is back in place: unpushed
@redraw: ; the original start-up follows
Credit: previously found by Novalia Spirit, my implementation
#422 The Cave to the Sealed Gate's ninja now lands facing down and his sound now waits for the actual landing.
In 1.0
In the Cave to the Sealed Gate, the ninja who falls from above played his landing sound before he touched down, and when put back he still faced right, so his next fall came down sideways.
Now
The sound now plays as he lands, and he faces down when put back.
For ROM hackers 1.0 addresses
Event _cb307e (CB/307E), the ninja's fall in the Cave to the Sealed Gate. The landing sound (sfx 181) follows a fixed wait_45f at CB/30A8, 12 frames before the 7-tile fall ends, and the obj_script at CB/30BF puts him back with pos only, so he keeps facing right. Rewrite the 5 bytes from CB/30A8 as wait_obj NPC_4 then sfx 181, one byte shorter with the wait_45f dropped. Insert dir DOWN (1 byte) before that obj_script's end at CB/30C4 and raise its length byte by 1. The event keeps its size; the commands between move up 1 byte.
Credit: previously found by Novalia Spirit, my implementation
#423 The Cave to the Sealed Gate's long bridge no longer returns control before it finishes building, so the party can't walk into the wall.
In 1.0
In the Cave to the Sealed Gate, the long bridge gave control back as soon as it began to build while the room's exits were switched off, so the party could step away, walk past an exit and get stuck in the wall.
Now
The party now waits until the bridge is finished.
For ROM hackers 1.0 addresses
Event _cb2f00 (CB/2F00), the long bridge in the Cave to the Sealed Gate (map 384). It gives control back with player_ctrl_on at CB/2F06, as the bridge begins to build, while an entrance trigger is off. Delete it there and insert it before the return at CB/2F64, after switch $01FB=1. The event keeps its size; the commands between move up 1 byte.
Credit: previously found by Novalia Spirit, my implementation
#424 Three leftover Kefka's Tower hook triggers now run the same clean-up as the real exits, so a later Warp no longer strands Setzer alone.
In 1.0
In Kefka's Tower, three leftover hook spots took the party back to the Falcon without the clean-up the real exit runs, so the tower was not reset and a later Warp put Setzer alone on the airship deck.
Now
They now run the same clean-up as the real exit.
For ROM hackers 1.0 addresses
Event _ca03e7 (CA/03E7), called by the three leftover hook triggers of Kefka's Tower (_ca03ba, _ca03c9, _ca03d8), leaves by call _cc2109 without _cc0f7d (CC/0F7D), the clean-up the tower's real exits run, which clears the tower's switches ($02C4 to $02CF) and the Warp bit ($02BE). Insert call _cc0f7d (4 bytes) before the call _cc2109 at CA/0400.
Credit: previously found by Novalia Spirit, my implementation
#425 The Phantom Train's aisle ghost now always triggers instead of being walkable around.
In 1.0
In the Phantom Train's seating car, a ghost blocks the door when the party steps on one of three tiles, but walking along the last column of the aisle missed all three, so the ghost never came.
Now
The ghost now always comes to the doorway.
For ROM hackers 1.0 addresses
Event triggers of map 145 in EventTrigger (records of 5 bytes: x, y, event address). The ghost at the car's door is called by triggers at (25,9), (26,8) and (27,9), event _cbb399 (CB/B399); the aisle at x 28 walks round them. Add a record {28, 9} to _cbb399 at the end of map 145's list, C4/0B08. Later records move 5 bytes, and the pointers of maps 146 on in EventTriggerPtrs (C4/0000; map 146 at C4/0124) grow by 5. The 1.0 block has 18 spare bytes at its end: enough for this or for #426, not for both.
Credit: previously found by Novalia Spirit, my implementation
#426 In World of Ruin Mobliz, the dog's walk to the basement can no longer be skipped, and a party member can no longer freeze it in view.
In 1.0
In Mobliz in the World of Ruin, the dog's walk down to the basement started from a single tile, so going round it skipped the walk and left the dog upstairs and downstairs, and a party member waiting on the dog's last tile stopped it in plain view.
Now
The walk now starts wherever the party crosses that row, and the dog passes through a party member in its way and vanishes at the end.
For ROM hackers 1.0 addresses
Mobliz in the World of Ruin, map 165. The dog's walk, event _cc4b0c (CC/4B0C), has one trigger, (11,24) at C4/0C7A. Add records (8,24), (9,24) and (10,24) to _cc4b0c after it, at C4/0C7F (15 bytes). Later records move, and the EventTriggerPtrs entries of maps 166 on, from C4/014C, grow by 15 (the block has 18 spare bytes, #425). In the event, insert pass_off NPC_4 (2 bytes) before the obj_script NPC_4 at CC/4B1B, so the dog walks through a party member on its path.
Credit: previously found by Novalia Spirit, my implementation
#428 Mashing A while riding a chocobo no longer keeps it stuck in its standing sprite instead of walking.
In 1.0
Tapping A rapidly while riding a chocobo on the world map moved it forward in its standing pose.
Now
Every step now begins on a walking sprite.
For ROM hackers 1.0 addresses
UpdateSpriteAnim_02 (EE/4606), the ridden chocobo on the world map. When the chocobo stands still, the code at EE/4633 stores the standing frame $25 but leaves the frame delay $7EB654 from the last step. After the frame store at EE/4668 that delay is copied to the countdown $7EB652, so a new step first waits out the old delay in the standing frame. Replace the two instructions at EE/4633 (7 bytes, same size): store A (0 there, 16-bit) to $7EB654, then lda #$0025; the frame is still stored at EE/4668.
; EE/4633, 16-bit A, A = 0 after the movement tests
sta $7eb654 ; clear the frame delay
lda #$0025 ; standing frame, stored at @4668
Credit: previously found by Novalia Spirit, my implementation
#430 The airship no longer shifts one tile each time the party returns to the deck and takes the wheel.
In 1.0
Each time the party went down to the deck while flying and then took the wheel again, the airship came back one tile away from where it had been, and one more tile with every further visit.
Now
The airship now comes back exactly where it was left.
For ROM hackers 1.0 addresses
EventCmd_6b (load map), C0/AB55. Loading the parent map calls RestoreParentMap (C0/1A46) at C0/ABCD, which steps the position one tile away from the stored facing $1FD2, as when the party walks out of a doorway. An airship load (bit 0 of $EF) takes that step too. Insert at C0/ABCD, the parent-map branch's target, before the jsr (5 bytes, 16-bit A): lda $EF, lsr, bcs to C0/ABD2, the previous-map path, which keeps the stored position and takes the facing from $1FD2.
; C0/ABCD, 16-bit A
@abcd: lda $ef
lsr ; bit 0: airship
bcs @abd2 ; keep the parent position
jsr RestoreParentMap
Credit: previously found by Novalia Spirit, my implementation
#431 An NPC with nothing to say no longer stops and turns when the party presses A at it, so the Fanatics' Tower procession no longer jams.
In 1.0
Pressing A at an NPC with nothing to say stopped it mid-step and turned it toward the party, so at the Fanatics' Tower the cultists' procession could jam with two cultists sharing one tile for good.
Now
NPCs with nothing to say now ignore the press and keep walking.
For ROM hackers 1.0 addresses
CheckNPCs, C0/46F4. At C0/47CC an NPC the party faces with A pressed is activated (it stops, turns and runs its event) even when its event is EventReturn (CA/5EB3), the empty one. Insert at C0/47CC (21 bytes): compare the object's event pointer $0889-$088B,y with $005EB3, its offset in the event bank, and branch to the rts at C0/47CB on a match.
; C0/47CC, 8-bit A
lda $0889,y ; event pointer, low byte
cmp #$b3
bne @talk
lda $088a,y
cmp #$5e
bne @talk
lda $088b,y
cmp #$00
beq @47cb ; EventReturn: leave the NPC alone
@talk: lda $087c,y ; the original test at @47cc
Credit: previously found by Novalia Spirit, my implementation
#433 Fixed invisible walls that blocked the party at map edges in Mobliz, Albrook and Cid's house on the Solitary Island.
In 1.0
In Mobliz, one column on the left and five on the right of the bottom edge hit hidden solid tiles instead of leaving town. Albrook had one such spot at its north-west corner, and two rows of the east edge outside Cid's house on the Solitary Island were blocked by a hidden tree and cliff.
Now
Those spots now let the party leave, except one Mobliz tile drawn as the cliff's edge.
For ROM hackers 1.0 addresses
Map layouts (BG1, LZSS), tiles below or beside the camera that refuse a step off the map. Mobliz exterior, D9/D82A (32 tiles wide, maps 148 and 157): on row 42, x 11 and 27 to 31 become $F0, a ground tile of that row. Albrook exterior, DA/0BF5 (64 wide): (0,3) becomes $E9, like the rest of the exit strip. Cid's house exterior, DC/DEBC (16 wide, so a step east from column 15 wraps to column 0): (0,4) and (0,12) become $F0. Recompress; if it grows, move it to free space and repoint in SubTilemapPtrs (D9/CD90).
Credit: previously found by Novalia Spirit, my implementation
#476 The Cave to the Sealed Gate's shifting bridge now redraws in its true phase, instead of a fixed layout that could drop the party.
In 1.0
In the Cave to the Sealed Gate, after the menu or a random battle, the shifting bridge was redrawn in one fixed layout whatever its real phase, so pieces about to give way looked solid and could drop the party.
Now
The redraw now shows the bridge as it actually stands.
For ROM hackers 1.0 addresses
Map 385's start-up event, CB/2B0F, also runs on every reload after the menu or a battle. It draws the moving bridge section in one layout whatever the phase of the swap timer, so in the $01F6 phase pieces about to drop show solid. Rewrite it to draw the section at rest first, then the moving one in the live phase's layout, as the timer events do: left CB/2B24 and right CB/2CBC go with $01F6, left CB/2BC9 and right CB/2D35 with $01F5. The new script is 21 bytes longer: put it in free event space and point the start-up there.
if_switch $01F0=1, Left
call _cb2bc9 ; left at rest
if_switch $01F1=0, EventReturn ; nothing moving
if_switch $01F5=1, _cb2d35 ; right moving, $01F5 phase
call _cb2cbc ; $01F6 phase
return
Left: call _cb2d35 ; right at rest
if_switch $01F5=1, _cb2bc9 ; left moving, $01F5 phase
call _cb2b24 ; $01F6 phase
return
#477 The sinking and surfacing Figaro Castle in the World of Ruin now shows its own Ruin colors instead of the World of Balance ones.
In 1.0
In the World of Ruin, Figaro Castle sank into the sand and rose again in blue-grey World of Balance colors, not the red-brown of the surfaced castle.
Now
It now uses the World of Ruin castle's own colors.
For ROM hackers 1.0 addresses
World event commands $FD and $FE (WorldCmd_fd EE/7F7F, WorldCmd_fe EE/808A) sink and raise Figaro Castle as a sprite in sprite palette 2, whose colours 1-10 copy the World of Balance castle tile. Insert jsr SetPal (3 bytes each) at EE/7FB3 and EE/80BE, after InitWorldSpriteMSB, and jsr RestorePal at EE/8050, where $FD clears up. $FE needs none: a load_map always follows it, and the world reloads its sprite colours. Both return at once when the map number $1F64 is 0 (World of Balance); otherwise they copy 20 bytes to $7EE142 (sprite palette 2, colours 1-10), SetPal from World2BGPal+$A2 (D2/EDA2, map palette 5) and RestorePal from World2SpritePal+$42 (D2/EF42). The two routines take 58 bytes of free space in bank EE.
SetPal: lda f:$001f64 ; 0 = World of Balance
beq @done
longa
ldx #0
@loop: lda f:World2BGPal+$a2,x
sta f:$7ee142,x
inx2
cpx #20
bne @loop
shorta
@done: rts
; RestorePal: the same, from World2SpritePal+$42
Credit: previously found by Novalia Spirit, my implementation
#478 A dark line flickering along the top of the grass below Nikeah's water is gone; the water now ends as in other towns.
In 1.0
In Nikeah, a flat dark line flickered along the top of the grass just below the water.
Now
The water now ends as it does in other towns.
For ROM hackers 1.0 addresses
Nikeah exterior BG3 layout, the wavy water layer (LZSS, DD/E395). Where the water meets the grass its bottom row uses the flipped shore-edge tile (tile 5), which shows above the grass whenever the layer bobs up. In the decompressed layout change the bytes at $A5, $B2, $B3, $BA, $BB, $10A and $128 from $85 to $0F (the one-line water strip), $40B from $C5 to $00 and $40C from $C5 to $0F. Recompress; if it grows, move it to free space and repoint in SubTilemapPtrs (D9/CD90).
Credit: previously found by Novalia Spirit, my implementation
#480 Changing party order right as the Sealed Gate bridge starts shifting no longer leaves the old leader walking in the new formation.
In 1.0
Changing the party order in the menu just before the Cave to the Sealed Gate's bridge shifted could leave the old leader walking in front.
Now
The new leader now always walks in front.
For ROM hackers 1.0 addresses
CheckTimer, C0/BBF1. A timer event can start in the fade before the menu opens; the map reload after the menu then skips choosing the new leader, since an event is running. Insert at C0/BBF1 (5 bytes): lda $59 (menu opening), beq on, rts. The timer stays at zero and fires once the menu has closed, as tile triggers already wait.
lda $59 ; menu opening?
beq :+
rts
: ldy $00 ; the original first line
Credit: previously found by Novalia Spirit, my implementation
#481 A chocobo walking below a party member no longer loses part of its head or tail behind the party member's torso.
In 1.0
When a chocobo walked sideways just below a party member, part of its upper body vanished behind the party member's torso: its head when facing left, its tail when facing right.
Now
The head and tail now stay in view.
For ROM hackers 1.0 addresses
DrawChoco, C0/60BA. A sideways chocobo reserves 3 top sprites (the sbc #12 on $D4 at C0/6124) and puts its upper left body piece in bottom sprite 2; every top sprite is drawn in front of every bottom one, so a character's torso above hides it. Change #12 to #16 and insert a jcc DrawNextObj (C0/5B34, 5 bytes) after the sbc, skipping the chocobo when the table is full. In DrawChocoRight (C0/62F1) and DrawChocoLeft (C0/63ED) make that piece top sprite 3: insert a second iny4 after the one at C0/6348 (left: C0/6450), store to $0340,y instead of $0400,y at C0/634C (C0/6454) and call SetTopSpriteMSB (C0/6721). Change sta $0409,y at C0/6384 (C0/648C) to $034D,y and sta $040A,y at C0/639D (C0/64A5) to $034E,y.
Credit: previously found by Master ZED, my implementation
#482 The World of Ruin Narshe snowfield's filler area is now solid, so the wandering Ice Dragon can no longer walk off the visible map.
In 1.0
The Ice Dragon wandering the Narshe snowfield in the World of Ruin could walk off the top of the map and spend most of its time out of sight.
Now
The dragon now stays on the part of the map the camera shows.
For ROM hackers 1.0 addresses
Map layout narshe_hills_maze_bg1 (BG1 of maps 22 and 34), LZSS at DB/19B8, pointer in SubTilemapPtrs (D9/CD90); 64 by 64 tiles, but the camera stops at column and row 41 and the map wraps. Tileset snowfields, LZSS at DE/8FA8, pointer in MapTilesetPtrs (DF/BA00): the unused tiles $0F, $10, $11, $12, $1B and $1C are already solid and blank; give them the arrangement bytes (8 per tile, byte k at k x 256 + tile) of $01, $81, $82, $91, $93 and $C0. In the layout, every walkable tile with x or y above 41 becomes its solid twin: $D4 becomes $05 (looks the same), the six become their new twins, 2,297 tiles. Recompress both; if one grows, move it to free space and repoint.
Credit: previously found by Novalia Spirit, my implementation
#483 With a full party, all thirteen rocks now fall to seal the Sealed Gate, instead of one staying up unseen.
In 1.0
With four party members, only twelve of the thirteen rocks fell to seal the Sealed Gate after the Espers blew Kefka away, and the last never appeared.
Now
All thirteen rocks now fall with a full party.
For ROM hackers 1.0 addresses
Event _cb39ca (CB/39CA), the Sealed Gate after the Espers. Near its end it deletes NPC_8 and NPC_9, then hides NPC_7, NPC_8 and NPC_9. A hidden object stays active, the field moves and draws only the first 24 active objects, and with four in the party the last rock is the 25th. Change hide_obj NPC_7 at CB/3B80 to delete_obj NPC_7 (same size). The two hides after it (the first at CB/3B82) act on objects already deleted.
Credit: previously found by Assassin and fixed by Leet Sketcher, my implementation
#484 The Cult of Kefka cultist in the corner now turns to face the party like the others, instead of standing with his back turned.
In 1.0
When the Cult of Kefka's followers ring the party after the Gem Box is taken, the one in the upper left corner stood with his back to the party while the others faced it.
Now
He now turns to face the party like the rest of the ring.
For ROM hackers 1.0 addresses
Event _cc544b (CC/544B), the cultists ringing the party after the Gem Box. The obj_script NPC_1 at CC/5490 walks the upper left cultist into place and ends (end at CC/5499) with him facing up. Insert dir RIGHT (1 byte) before that end and raise the obj_script's length byte by 1.
Credit: previously fixed by Leet Sketcher, my implementation
#485 On the Phantom Train, the camera no longer stays off-center after following Cyan during Sabin's dinner scene.
In 1.0
On the Phantom Train, if Cyan was leading when Sabin sat down to eat in the dining car, the camera followed Cyan one tile to the right and stayed there, leaving the view off center until the party left the car or opened the menu.
Now
The view now ends centered on the party.
For ROM hackers 1.0 addresses
Event _cbb193 (CB/B193), Sabin sitting down in the dining car. hide_obj SLOT_1 / show_obj SABIN makes Sabin the object the camera follows; the regroup at the end moves the party by script, which scrolls nothing, so the view stays where Sabin's walk left it. Insert lock_camera (1 byte) before the hide_obj SLOT_1 at CB/B197 and unlock_camera (1 byte) before the return at CB/B264 that ends the scene.
Credit: previously fixed by Leet Sketcher, my implementation
#486 The rest after Banon's story now heals the whole party as a Tent does, so a Wounded Terra no longer wakes still Wounded.
In 1.0
After Banon tells his story, the party rests, but the rest healed nobody, so a Terra who was Wounded when the party first spoke to Banon woke up still Wounded.
Now
The rest now heals the whole party the way a Tent does.
For ROM hackers 1.0 addresses
Event _caf7dc (CA/F7DC), Banon's story at the Returners' hideout. The fade_out and wait before party_chars TERRA (CA/F8EE) stand for the rest, but nothing heals. Insert call _cacfbd (4 bytes) before that party_chars; _cacfbd (CA/CFBD) is the rest a Tent runs.
Credit: previously fixed by Leet Sketcher, my implementation
#489 Stepping into a ghost door in Owzer's basement as it closes no longer leaves it drawn open while it is shut.
In 1.0
In Owzer's basement, stepping into one of the three ghost doors just as it closed could leave it drawn open even though the step was refused.
Now
A refused step no longer leaves the door looking open.
For ROM hackers 1.0 addresses
Owzer's basement door cycle, events _cb4987 (CB/4987), _cb49ab (CB/49AB) and _cb49cf (CB/49CF). Each paints a door shut with mod_bg_tiles and only then creates its blocker NPC (call _cb475a at CB/499A, call _cb474c at CB/49BE, call _cb4753 at CB/49E2). The object loop puts a new object on the object map up to four frames later, and a step into the door in that gap runs CheckDoor on the fresh closed tile, which draws it open. In each event move that call to the top, follow it with wait 8 (2 bytes), and lower the start_timer duration from 160 to 152 (CB/49A4, CB/49C8, CB/49EC) so each phase keeps its length.
Credit: previously found by Novalia Spirit, my implementation
#493 The party can no longer walk past a trigger tile while a timer event has triggers switched off; they now stop on it instead.
In 1.0
While a timed event played, such as a door closing, tile triggers were switched off but the party could keep walking, so a walking party could cross a trigger tile without it firing and slip past the guard in the Figaro Castle basement.
Now
A walking party now stops on a trigger tile until the event ends, then the trigger runs.
For ROM hackers 1.0 addresses
Tile triggers are checked only while no event runs, and a timer event holds whatever object $DA names, so the party walks over a trigger unchecked. Use one free byte, $0788 (unused in 1.0): CheckTimer (C0/BBF1) sets it to 1 before the ldx $DA at C0/BC48; EventCmd_fe clears it at the event stack's top (before C0/B917), DoTrigger after the sta $078E at C0/BCEA, InitNewGame after the sta $05C7 at C0/BEAC. Insert after the jsr CheckEventTriggers at C0/00D4 a jsr to a new routine (134 bytes): with $0788 set, $84 and $59 clear and an event running, a walking user-controlled party (movement type 2) square on a tile holding a trigger of map $82 is stopped as DoTrigger stops it, with movement type 0 in $087C and $087D.
Credit: previously found by duggie2, my implementation
#494 The beach by Cid's house no longer shows water washing over only Cyan, Shadow, Setzer, Mog and Umaro while everyone else stays dry.
In 1.0
On the beach by Cid's house, the water is laid over the bird and the fish so they look submerged, but it also covered Cyan, Shadow, Setzer, Mog and Umaro and left everyone else dry.
Now
The party is now drawn in colours the water does not touch, so everyone stands dry except while a magicite shows on the sand, and the bird and the fish still show beneath the surface.
For ROM hackers 1.0 addresses
Map 398 half-adds its water to sprites, which hits OAM palettes 4 to 7, so characters on palettes 4 and 5 get it. Two routines, 276 bytes in bank C0, and two free bytes, $0782 and $0787 (unused in 1.0). After jsr InitSpritePal in LoadMap (C0/BF91): set both to $FF; on map 398, collect the slots ($1F70,x) of the current party's characters, plus slot 2 while event bit $039B is set; for palette 4 and 5 in use, take the first free of slots 3, 1, 0, 2, copy the palette from MapSpritePal (E6/8000) into that slot of $7E7300 and $7E7500, and store the slot x2. In DrawObjNoVehicle (C0/5B8B) insert before C0/5C12: for a character object, swap palette bits 1 to 3 of $1B and $1D from 4 or 5 to the stored slot.
Credit: previously found by Novalia Spirit, my implementation
#498 A party member no longer takes an extra step off a ledge or past an exit when the Zone Eater's ceiling falls.
In 1.0
If the party was walking when the Zone Eater's ceiling fell, the lead party member took one more step, which could carry them off a ledge or past the room's exit.
Now
The party now stops where it stood when the ceiling fell.
For ROM hackers 1.0 addresses
EventCmd_3b (C0/9D2E), player_ctrl_off. The player code leaves a step count in $0886 (object 0's after a flat step, the leader's after a stair step), and this command makes the party script-controlled without clearing it, so the object loop takes one more step. Insert clr_a / sta $0886,y (4 bytes) after the ldy $0803 at C0/9D2E, before the lda at C0/9D31.
Credit: previously found and fixed by Leet Sketcher, my implementation
#500 The Zozo clock puzzle's three liars now leave only one correct answer for the second hand instead of two.
In 1.0
In Zozo's clock puzzle, three liars talk about the second hand, and their hints ruled out 20, 30 and 40 seconds but not 10, so 10 and 50 both fitted when only 50 is the answer.
Now
The liar who pointed at "four" now says "two", so only 50 fits.
For ROM hackers 1.0 addresses
Dialogue 1061 ($0425), at CD/AF4C (28 bytes), pointer at CC/EE4C in DlgPtrs. One of the three liars' hints gives the second hand as four; change it to two, so the lies exclude 10 as well as 20, 30 and 40. With the DTE pair for space-t the new word encodes to as many bytes as the old one, so the entry stays 28 bytes and nothing moves.
Credit: as in the Pixel Remaster, my implementation
#501 A Warp or Warp Stone no longer leaves the save-point flag set, so Save and Tent no longer linger on the field menu afterward.
In 1.0
Warping from a save point with Warp or a Warp Stone left Save and Tent on the field menu of the new map until the party's first step.
Now
Save and Tent now appear only where they normally would.
For ROM hackers 1.0 addresses
Event Warp (CA/0108), run by Warp and the Warp Stone. It clears several switches before it loads the destination but not $01BF, the save-point flag ($1EB7 bit 7). Insert switch $01BF=0 (2 bytes) after switch $02BC=0 at CA/0136, before CA/0138.
Credit: previously found by ffdic, my implementation
#502 Jidoor's exterior now switches to its World of Ruin colors, matching South Figaro, Tzen and Albrook.
In 1.0
In the World of Ruin, Jidoor kept its World of Balance colours while South Figaro, Tzen and Albrook changed to Ruin colours.
Now
Jidoor now takes the same Ruin colours in the World of Ruin.
For ROM hackers 1.0 addresses
LoadMapProp, C0/1CAD, copies the 33-byte record from MapProp (ED/8F00) to $0520; $0539 is its palette. Jidoor (maps 198 and 392) has no World of Ruin record of its own. Insert before the rts at C0/1CCD (34 bytes): when $82 is 198 or 392 and $1E94 bit 4 (switch $00A4, World of Ruin) is set, store 41 in $0539.
longa
lda $82 ; map index
cmp #198
beq @wor
cmp #392
bne @done
@wor: lda $1e94
and #$0010 ; World of Ruin
beq @done
shorta0
lda #41
sta $0539 ; map palette
rts
@done: shorta0
rts
Credit: as in the Pixel Remaster, my implementation
#504 A step on a diagonal staircase can no longer walk through an NPC standing on the destination tile.
In 1.0
A diagonal step on a staircase never checked for an NPC on the next tile, so the party could walk onto an NPC standing on the stairs.
Now
The party can no longer step onto that NPC.
For ROM hackers 1.0 addresses
UpdatePlayerMovement, stair branch: sta $087E,y / sta $B3 at C0/493F start the step without reading the object map ($7E2000). Replace them (5 bytes) with a jsr to a routine (86 bytes) that calls GetObjMapAdjacent (C0/7D03) with direction 5 to 8. An object there: drop the return addresses, clear $087E, run UpdateOverlay (C0/C8A5), CheckNPCs (C0/46F4) and CheckTreasure (C0/4B83), rts. Otherwise claim the tile with the party's object number x2, as a flat step does (not on a bridge tile, $7E7600 bit 2, when $B2 is 2), then do the two stores. AdjacentXTbl (C0/7D20) and AdjacentYTbl (C0/7D25) need diagonal entries 5 to 8: move both to free space as 9-byte tables and repoint C0/7D0B and C0/7D17.
Credit: previously found and fixed by assassin17 and Lenophis, my implementation
#515 Opening and closing the menu while an NPC is mid-step no longer lets the party walk through it, which let Locke slip past South Figaro's guard (the Kutan or Moghan glitch).
In 1.0
Opening the menu while an NPC was midway through a step, and closing it with a direction held, let the party walk straight through the NPC (the Kutan or Moghan glitch). Speedrunners use it in Locke's scenario to pass the patrolling soldier in South Figaro and leave town without meeting Celes.
Now
An NPC caught mid-step now blocks the party.
For ROM hackers 1.0 addresses
InitNPCMap (C0/5238) rebuilds the object map ($7E2000) after a menu from each NPC's pixel position through GetObjMapPtr (C0/7CE1), which names one tile. A stepping NPC holds two, the one it left ($087A/$087B, untouched during the step) and the one it is entering, so the rebuild frees one. Replace the four instructions from C0/5263 (12 bytes) with a jsr to a routine (about 95 bytes of free space in bank C0). An object with a sub-tile part in $0869/$086A or $086C/$086D claims the tile at $087A and, for direction $087E 1 to 4, the next tile through AdjacentXTbl (C0/7D20) and AdjacentYTbl (C0/7D25), wrapped with $86/$87. An object on a tile takes the old path.
Credit: previously found by Lime, my implementation
#533 In the South Figaro basement, the clock's secret door no longer shows a strip beside the cabinet before the clock is wound.
In 1.0
In the basement of the rich man's house in South Figaro, a strip of the clock's secret door showed in the gap beside the cabinet before the clock was wound.
Now
The gap beside the cabinet is now plain wall.
For ROM hackers 1.0 addresses
Map layout south_figaro_basement_bg1 (BG1 of the rich man's basement), LZSS compressed at DB/2565, pointer in SubTilemapPtrs (D9/CD90). The layout is 64 tiles wide. The tiles at (15, 51) and (15, 52) hold a copy of the wall's top edge and a piece of the open clock door, though the clock event draws the open door itself. Decompress, set offset $0CCF to $A0 and offset $0D0F to $B0 (the wall tiles of the rest of the row), and recompress; if it grows, move it to free space and repoint.
Credit: previously found and fixed by Leet Sketcher, my implementation
#534 In Zozo, the wall behind the tall clock no longer shows two black slits either side of the clock.
In 1.0
In Zozo, two black slits showed either side of the lower half of the tall clock.
Now
The wall now carries on behind the clock's sides.
For ROM hackers 1.0 addresses
Map layout zozo_int_bg2 (BG2 of the Zozo interiors), LZSS compressed at DB/E717, pointer in SubTilemapPtrs (D9/CD90). The layout is 128 tiles wide. The tall clock's BG1 tiles have open sides, and BG2 behind it at (125, 44) holds tile $00, so the backdrop shows through. Decompress, set offset $167D to $A1, the same wall tile BG1 draws beside the clock, and recompress; if it grows, move it to free space and repoint.
Credit: previously found and fixed by Leet Sketcher, my implementation
#535 In the Fanatics' Tower, an opened door no longer shows the clouds drifting past through the doorway.
In 1.0
In the Fanatics' Tower, the clouds drifting past outside showed through the top of an opened doorway, on the ring floors' doors, the top floor's door and the secret door.
Now
The top of an open doorway is now solid dark like the rest of it.
1.0
Now
For ROM hackers 1.0 addresses
Map tileset zozo_bg2 (BG2 of the Fanatics' Tower), LZSS compressed at DE/AE7F, pointer in MapTilesetPtrs (DF/BA00). Tile $13 sits only behind the tower's door tops and its lower half is blank, so with the door open (BG1 tile $04, clear in its lower half) the BG3 fog shows through. Decompress and give tile $13's bottom-left and bottom-right quarters tile $01's backing: graphics index $01 at offsets $213 and $313, attribute $04 (palette 1) at $613 and $713. Recompress; with the disassembly's compressor the block grows by 4 bytes, so it goes to free space and its pointer changes.
Credit: previously found and fixed by Leet Sketcher, my implementation
#554 The mine cart tunnel's right-hand wall no longer shows dark specks.
In 1.0
The right-hand wall of the mine cart tunnel showed dark specks.
Now
The wall is now clean.
1.0
Now
For ROM hackers Method
The ride unpacks its wall patterns into $7E2000 and builds pattern types 17 and 18 as mirror images of types 0 and 1. InitWorldSprites (EE/4015) then writes $E0 into every fourth byte of $7E6B31-$7E6D2D, and InitTrainSpriteMSB (EE/4074) fills $7E6D30-$7E6D4F; both ranges lie over types 17 and 18, so parts of both get dark or missing pixels. Our fix is part of #553: its generated tile routines are made at build time from clean mirror images.
#555 At the end of the mine cart ride, the tunnel straightens as the screen fades out instead of drifting to the left.
In 1.0
At the end of the mine cart ride, the tunnel drifted to the left during the fade-out, though the last stretch of the course is straight.
Now
The tunnel now holds straight during the fade-out.
The fade-out at the end of the ride, brightened so it can be seen: before on the left, now on the right.
For ROM hackers 1.0 addresses
_ee235b, EE/235B, places the tunnel's layers for each step. It starts with lda #$00 at EE/235D in 8-bit A, which leaves the accumulator's high byte as it was. After the end-of-course command that byte is 1, and the yaw loop's tax into 16-bit X then writes the step's x centres into the yaw table instead. Replace lda #$00 with tdc and nop (2 bytes, in place), which clears the whole accumulator.
#571 A rat left alive on the Opera House rafters is gone once the opera succeeds, so a later visit can no longer lose to it and replay the opera's failure.
In 1.0
Any rat the party left alive on the Opera House rafters during the opera stayed there afterward, so a later visit could run into it, and losing that fight replayed the opera's failure and left the party outside the Opera House.
Now
Every rat still on the rafters is now taken away once the opera succeeds.
For ROM hackers 1.0 addresses
Event _cac128, CA/C128, the opera's success. The rafters rats are switches $034C to $0350, cleared one by one as each is beaten, and nothing clears the rest when the opera ends. Insert five commands, switch $034C=0 through switch $0350=0 (10 bytes), before the call _caf011 at CA/C12C, after switch $034B=0. The English script already clears them where the Falcon rises.
Credit: previously found by CAvery, RPGClassics, Master ZED, Xenomic via the Final Fantasy Wiki, Lapse Pointer, urawaza.in and ホーミング, my implementation
#572 After Gau leaps on the Veldt and comes back behind a new leader, the camera keeps following the leader instead of stopping on the next map.
In 1.0
If Gau led the party through a town, then leapt on the Veldt, and someone else was moved to the head of the party with Order before he came back, two party members answered the pad on the next map and the camera stopped following the leader.
Now
Only the leader now answers the pad, and the camera follows.
For ROM hackers 1.0 addresses
GetTopChar, C0/6F67. A character who leaves in battle (Gau's Leap) keeps movement type 2 (pad-controlled) in $087C bits 0 to 3; a battle or menu restores only the leader's. If Order puts someone else first before he rejoins, two objects answer the pad. In the party loop, insert 21 bytes before the phx at C0/6FA1: an active member other than the previous leader ($1E, saved at the routine's start) loses type 2, and bits 4 to 7 stay.
lda $087c,x ; movement type in bits 0-3
and #$0f
cmp #$02 ; pad-controlled?
bne @keep
cpx $1e ; the previous leader
beq @keep
lda $087c,x
and #$f0
sta $087c,x
@keep:
Credit: previously found by エディ and 嵯峨, my implementation
#575 When Shadow leads the party and leaves after a fight an enemy started by touching it, such as a rafters rat, the next party member takes the lead and is seen.
In 1.0
If Shadow led the party and left after a won battle that an enemy started by touching the party, such as a rafters rat, no new leader was picked and nobody was drawn on screen.
Now
The next party member now takes the lead and is drawn.
For ROM hackers 1.0 addresses
ExecBattle, C0/C13E. The jsr PopCharFlags at C0/C16B restores the party flags after a battle, but no new leader is picked when the object the pad steers ($0803) belongs to a character who left in battle: only update_party or a map load with no event running calls GetTopChar (C0/6F67), and a touch battle's event goes on without either. Replace that jsr with a jsr to the routine below (25 bytes of free space in bank C0). PopCharFlags is at C0/7077.
jsr MyFix ; replaces jsr PopCharFlags at C0/C16B
MyFix: jsr PopCharFlags
ldy $0803 ; object the pad steers
cpy #$0290 ; a character object?
bcs @done
lda $0867,y
and #$07 ; its party
cmp $1a6d ; still the active party?
beq @done
jsr GetTopChar
@done: rts
Credit: previously found by エディ, my implementation
#576 At Thamasa, a recruited Mog's level no longer counts in the level the party members who stayed behind are raised to after the Esper Caves.
In 1.0
At Thamasa, after the Esper Caves, the party members who stayed behind are raised to the party's average level, but a recruited Mog's level counted in that average, so a low-level Mog left them below the level of Terra, Locke, Strago and Relm and a high-level Mog lifted them above it.
Now
They now reach the average level of Terra, Locke, Strago and Relm, whatever Mog's level.
For ROM hackers 1.0 addresses
Thamasa return scene. It clears the available switches of the seven who stayed behind, runs norm_lvl on each, which averages over the characters still available, then calls _cc0935 (CC/0935) to raise Mog apart. Mog's switch $02FA was still set during the averaging. Insert switch $02FA=0 (2 bytes) before the norm_lvl CYAN at CC/08E2. In _cc0935, change if_switch $02FA=0 to if_switch $02EA=0 (Mog recruited; same size), since $02FA is now always clear there; the routine sets $02FA again after raising him.
Credit: previously found by Djibriel, my implementation
#580 At the Narshe and Tzen inns, a leader chosen with Order who rests before taking a step walks to the bed at the other inns' pace instead of crawling.
In 1.0
At the Narshe and Tzen inns, a leader put at the head with Order who rested before taking a step crawled to the bed.
Now
Both inns now start the walk at the same fast pace as the other inns.
For ROM hackers 1.0 addresses
Inn rest events _cc5cae (CC/5CAE, Tzen) and _ccd2b2 (CC/D2B2, Narshe). Their obj_script SLOT_1 walk to the bed (at CC/5CB7 and CC/D2BB) sets no speed, and GetTopChar does not give a leader chosen with Order the old leader's speed, so the leader keeps its own, 0 after power-on. Insert speed FAST (1 byte, $C3) as the first command of each obj_script, at CC/5CB9 and CC/D2BD, and raise the script's length byte (the obj_script command's second byte) by 1. The other inns' walks already start with it.
Credit: previously found by エディ, my implementation
#581 After Sabin's flashback, the guard who walks the Figaro Castle courtyard comes back with the other four instead of staying gone for the rest of the game.
In 1.0
After Sabin's flashback to leaving Figaro Castle, the guard who walks the courtyard never showed again, in either world.
Now
He now comes back along with the other four guards.
For ROM hackers 1.0 addresses
Sabin's flashback clears switch $0315 at CA/7318, beside #284's $030E. It is the NPC switch of map 55's NPC_4, the guard who walks the Figaro Castle courtyard from (24,16). Its last setter, at CA/7006 on the night of Kefka's visit, always runs before the flashback, so the guard stays gone. Insert switch $0315=1 (2 bytes) after #284's switch $030E=1, before the call _cacf96 at CA/7583.
#586 After the Thamasa regroup, a Gau left on the Veldt stays there until fetched, instead of turning up on the Blackjack and the Lineup without the trip.
In 1.0
At Thamasa, after the Esper Caves, a Gau who had leapt on the Veldt and not been fetched turned up in the Blackjack's lower cabin and on the Lineup for the Floating Continent without the trip.
Now
He now stays on the Veldt until fetched.
For ROM hackers 1.0 addresses
Thamasa return scene. It clears the available switches of the seven who stayed behind, runs norm_lvl on each, then sets six back without a test, Gau's $02FB among them. A Leap records nothing but $02FB clear, so a Gau still on the Veldt is made available again and the Veldt never arms his return. Replace switch $02FB=0 and switch $02F6=0 at CC/08DE with a call to MyFixA, and switch $02FB=1 and switch $02F6=1 at CC/08F8 with a call to MyFixB (4 bytes each, same size). The routines need 34 bytes of free space in bank CA or later. Switch $01FF, a per-map switch that LoadMap clears, carries Gau's state from one to the other.
Credit: previously found by FF6スレまとめ@Wiki, my implementation
#589 In the World of Balance, Figaro Castle's trips between Kohlingen and the desert now end with the castle where it is drawn and the party below its left door.
In 1.0
In the World of Balance, Figaro Castle's trip from Kohlingen to the desert sank and surfaced a tile right of where the castle is drawn, and walking out after either trip left the party in a doorway.
Now
The castle sinks and comes up where it is drawn, and the party walks out one tile below its left door.
For ROM hackers 1.0 addresses
World of Balance Figaro Castle trips. _ca6899 (CA/6899), Kohlingen to the desert, loads the world at (31, 48), the castle block's bottom right tile, so the castle sinks and surfaces a tile right of the block; change the x byte at CA/68A7 from $1F to $1E. Each trip's last step records the parent facing sideways, so walking out leaves the party on a door tile: put dir UP ($CC) before the $FE at CA/688A (_ca685a) and at CA/68C9. In a binary patch, overwrite the last six steps (CA/6884 to CA/6889, CA/68C3 to CA/68C8) with a jump ($B0 on switch $0127, always clear) to 14 free bytes holding the six steps, $CC, $FE and a jump back to CA/688B or CA/68CA.
#592 Owzer's basement door timers can no longer be carried out of the mansion by a well-timed room change, making NPCs on other maps appear and vanish.
In 1.0
In Owzer's mansion in the World of Ruin, before Relm rejoins, changing rooms just as a basement door swung could carry the door timers out, making NPCs on later maps appear and vanish every few seconds.
Now
The mansion's rooms now stop the door timers every time.
For ROM hackers 1.0 addresses
Same routine as #480. CheckTimer (C0/BBF1) runs through an entrance's fade-out, while $84 (map load pending) is set, so a timer that runs out there starts its event on the old map. The new map's start-up then stops the timers, but the event resumes after it and can arm the next timer: Owzer's door chain leaves the mansion that way. After #480's lda $59 at the head, insert ora $84 (2 bytes), the flag CheckEventTriggers (C0/BC6F) also refuses on. The timer waits at zero and fires after the new map's start-up. Without #480, insert lda $84, beq on and rts (5 bytes) at C0/BBF1.
lda $59 ; #480: menu opening
ora $84 ; or a map load pending
beq :+
rts
: ldy $00 ; the original first line
Credit: previously found by エディ, my implementation
#594 After a random battle in Owzer's basement door room, the camera no longer drifts out of step with the party as the doors swap.
In 1.0
After a random battle in Owzer's basement door room, walking about while the doors swapped could put the camera further out of step with the party at each swap, until the screen's left edge showed the wrong tiles.
Now
The camera stays on the party.
For ROM hackers 1.0 addresses
Map 207, the door room in Owzer's basement. After a random battle the leader's saved movement type is 0, and a door's timer event that ends mid-step copies it back, so the camera runs 4 pixels past the leader. In the map's entrance event _cb4604 (CB/4604), which runs again on the reload after a battle, change the sort_obj at CB/4608 to player_ctrl_on ($3A), which writes 2 into the leader's movement type and its saved copy. Same size. The start-up also runs inside the five basement scenes with battle 151 and handed the pad back mid-scene: replace the call _ca5ea9 after each battle (CB/4A2A, CB/4A68, CB/4AA8, CB/4AE7, CB/4B26) with a call to call _ca5ea9 / player_ctrl_off / return (6 bytes of free space).
Credit: previously found and fixed by Gi Nattak, my implementation
#597 Letting the intro play through before New Game no longer puts Wedge and Vicks ahead of Terra in the party order.
In 1.0
When the title's intro and its credits played through before New Game was chosen, Wedge and Vicks led the party list and Terra was fourth, though Terra was the one drawn.
Now
Terra leads, then Wedge and Vicks, as when Start is pressed at once.
For ROM hackers 1.0 addresses
Event GameStart (CA/5E33). The intro's end goes back with if_switch $02FF=0, GameStart at CA/5E99, and GameStart's char_party TERRA, WEDGE and VICKS, 1 run again; the party command takes the battle slot from FindEmptyCharSlot, which counts each one's old slot as taken, so Terra goes to slot 3. Point that jump at a new routine in free event space: char_party TERRA, 0, the same for WEDGE and VICKS, then if_switch $02FF=0, GameStart. Same size at CA/5E99.
Credit: previously found by Master ZED, my implementation
#598 The Vector child's "And...CURE!!!" and the Lete River raft no longer give HP or MP to a dead, petrified or zombie party member.
In 1.0
In Vector, the child who shouts "And...CURE!!!" gave 1 HP to a dead, petrified or zombie party member, so a dead one showed as Wounded with HP 1. The raft on the Lete River gave such a member full HP and MP the same way.
Now
Neither heals a dead, petrified or zombie party member.
For ROM hackers 1.0 addresses
EventCmd_8b (C0/AE7B), event command $8B, adds or sets HP whatever the status. Insert before the longa_clc at C0/AE97, after the add path's bmi at C0/AE95, and again at the set-to-maximum path's start C0/AEC9 (7 bytes each): lda $1614,y, and #$C2 (Wound, Petrify, Zombie), bne to the command's end. The same 7 bytes go before the jsr CalcMaxMP at C0/AF49 in EventCmd_8c (C0/AF3E), the MP command. Then, same size, move the status clear ahead of the heal in every scene that heals and then revives: _ccaaba (inc_hp at CC/AABA, and_status at CC/AAC6), _ccc8e7 (CC/C8E7, CC/C8F3), _cc4990 (loop at CC/49DB, and_status at CC/49E1), RestoreParty (CC/E499) and seventeen scenes, most of them a party member joining (note).
Credit: previously found and fixed by Imzogelmo, my implementation
#600 After the espers scene at the Magitek Research Facility, the camera is centred on the party again.
In 1.0
After the scene at the Magitek Research Facility where the party receives the espers, the camera stayed two tiles off the party until the menu or the next map.
Now
The camera is centred on the party when the scene ends.
For ROM hackers 1.0 addresses
Event _cc7a60 (CC/7A60), the espers scene on map 274. Its camera moves end at the switch, two tiles above the leader, and unlock_camera at CC/7C79 only clears $0559, after which the screen moves at the leader's speed and keeps that gap. Replace give_genju SHOAT, unlock_camera and wait_1s at CC/7C77 (4 bytes) with a call to a routine in free event space that runs give_genju SHOAT, obj_script CAMERA with move DOWN, 2, unlock_camera, wait_1s and return.
Credit: previously found by Novalia Spirit, my implementation
#601 After the banquet, Albrook's cafe plays Kids Run Through the City like the streets outside.
In 1.0
Back in Albrook after the banquet, the streets played Kids Run Through the City while the cafe still played the occupation's Under Martial Law.
Now
The cafe plays what the streets play.
For ROM hackers 1.0 addresses
Event _cc5f36 (the cafe's song routine, called from map 330's start-up). After the banquet ($007D set) its if_switch at CC/5F3C jumps to CC/5F56, Under Martial Law. Point that jump at _cc5f09 (CC/5F09), the town's own song routine, which ends with its own return: same size, only the target changes.
Credit: previously found by Novalia Spirit, my implementation
#602 In the Cave to the Sealed Gate, the rock edge stops the party at two gaps where it could walk under the edge and vanish.
In 1.0
In the Cave to the Sealed Gate, the party could step onto five black tiles beside the rock edge, and the edge was drawn over the party leader.
Now
The edge stops the party there.
For ROM hackers 1.0 addresses
Map layout cave_to_sealed_gate_bg1 (BG1 of the Cave to the Sealed Gate, map 386), LZSS compressed at DA/9B98, pointer in SubTilemapPtrs (D9/CD90). The layout is 128 tiles wide. Tile $01, the black outside the cave, is walkable in this tileset; at (71, 55), (71, 56), (87, 54), (88, 55) and (88, 56) it sits inside the fence of blocking $11 tiles, and BG2's wall pieces there draw over the sprite. Decompress, set offsets $1BC7, $1C47, $1B57, $1BD8 and $1C58 to $BF, the blocking black tile, and recompress; if it grows, move it to free space and repoint.
Credit: previously found by Novalia Spirit, my implementation
#603 Doma Castle's bedroom asks "Need a rest?" whichever way the party steps onto the tile by the door.
In 1.0
Doma Castle's bedroom asked "Need a rest?" only when the party stepped onto the tile by the door facing north.
Now
It asks from any side.
For ROM hackers 1.0 addresses
Event _cb827d (CB/827D), the bedroom trigger at (4, 12) on map 123. Its if_any returns unless the party faces up ($01B0), has not been asked on this map ($01F0) and Cyan has found his family ($0032). The first condition at CB/827E is B0 01; write F0 81, a second test of $01F0=1, which never adds a case: same size, nothing moves.
Credit: previously found by Novalia Spirit, my implementation
#604 After the Guardian at Thamasa's southern exit, Leo no longer walks into the flower bed.
In 1.0
After the Guardian at Thamasa's southern exit, the scene moved Leo two tiles north, into the flower bed when he had stepped onto the westmost exit tile.
Now
From that tile he steps two tiles right, onto the grass.
For ROM hackers 1.0 addresses
Event trigger table of map 341 (Thamasa in flames). The record for (19, 46) at C4/15BC points at _cc094c (CC/094C): the M.Tek Trooper's line, battle 75 (the Guardian), then move UP 2, which from that tile enters the flower bed. Change the record's third byte, its pointer's low byte, from 4C to 42, so it runs _cc0942 (CC/0942), the west exit's same scene with move RIGHT 2.
Credit: previously found by Novalia Spirit, my implementation
#607 At the Opera House, when Gau is the only one of Cyan, Edgar, Sabin and Gau with Locke, he no longer walks onto Celes's tile as Setzer comes in.
In 1.0
At the Opera House, when Gau was the only one of Cyan, Edgar, Sabin and Gau with Locke, he walked onto Celes's tile as Setzer came in after the kidnapping.
Now
Gau stops beside Locke, as a lone Edgar does.
For ROM hackers 1.0 addresses
Event _cb1ca1 (CB/1CA1), Gau's place in the Opera House kidnap scene. Cyan, Edgar and Sabin each start at (101, 51) when none of the earlier ones is in the party (switches $01A2, $01A4, $01A5) and at (103, 51) otherwise; Gau's pos is always (103, 51), so a lone Gau stands on Celes's tile. Change the if_switch at CB/1CA1 to if_switch $01AB=1 to the routine below in free event space (28 bytes), and the obj_script at CB/1CA7 (6 bytes) to if_switch $01AB=0, _cb1cd5 (CB/1CD5). Same size.
GauPos:
obj_script GAU
pos {101, 51}
end
if_all
switch $01A2=0
switch $01A4=0
switch $01A5=0
goto GauBack
obj_script GAU
pos {103, 51}
end
GauBack:
if_switch $01AB=1, GauPlaced ; GauPlaced: the char_party GAU at CB/1CAD
Credit: previously found by Novalia Spirit, my implementation
#609 At the Opera House, a menu opened while Locke sits in his seat no longer brings his upper body in front of the seat back.
In 1.0
At the Opera House, after the first act, opening the menu while Locke sat in his seat brought his upper body in front of the seat back.
Now
Locke stays behind the seat back.
For ROM hackers 1.0 addresses
InitZLevel, C0/467E, sets the party's sprite priorities on every map load, the return from the menu among them. A tile whose properties in $B8 have bit 2 set is a bridge tile, and on the upper z-level the routine puts the top half at priority 3, over BG priority 1. The Opera seat is tile type $F7, which only an event puts the leader on, so after a menu Locke's top half showed over the seat back. Insert lda $B8, cmp #$F7, beq to C0/46B0 (6 bytes) before the lda at C0/4698: on $F7 both halves get priority 2, and $B4 keeps the upper z-level pointer.
lda $B8 ; tile properties
cmp #$F7 ; the Opera seat
beq LowerPrio ; LowerPrio: the lower path's priority writes at C0/46B0
Credit: previously found by Novalia Spirit, my implementation
#613 In Kefka's Tower, a conveyor belt no longer freezes the game when another party stands where it sets the party down.
In 1.0
In Kefka's Tower, a party carried down a conveyor belt froze the game when another party stood where the belt set it down.
Now
The belt carries the party onto that tile.
For ROM hackers 1.0 addresses
Event routines _cc1008 to _cc1046 (CC/1008 to CC/1046), nine back to back, carry the party along the belts of Kefka's Tower with obj_script SLOT_1 and a straight move that is not ASYNC, so it waits for its landing tile. When the other party stands there the move never ends and the game freezes. Put pass_off SLOT_1 ($78, the object walks through others) before each obj_script and pass_on SLOT_1 ($36) after it, 4 bytes per routine. There is no room in place: copy the nine, wrapped, to free event space (105 bytes) and replace the start of each with call to its copy and return (5 bytes; each routine has 7 or more).
Belt1008:
pass_off SLOT_1
obj_script SLOT_1 ; the routine's own moves, unchanged
...
end
pass_on SLOT_1
return
#614 In Owzer's mansion, a door that starts to close as the party reaches its doorway no longer traps the party inside the shut door.
In 1.0
In Owzer's mansion, a door that started to close just as the party reached its doorway let the party walk on into the shut door, where it stood until the door opened again.
Now
The door waits until the party has gone through.
For ROM hackers 1.0 addresses
CheckTimer, C0/BBF1, runs before CheckEntrances, C0/18A3, which refuses while an event runs. A timer that runs out as the party arrives square on a short entrance starts its event first, and a held direction walks the party past the door. 1.0 has the fault: 8 timings with Sprint Shoes at Owzer's door 88. Insert jsr EntranceDue, bcc over an rts (6 bytes) before the ldx at C0/BC20, after #179's test where present. EntranceDue (about 100 bytes of free space in bank C0) first applies CheckEntrances's tests ($85, $56, the bridge z-level). EntAt is CheckShortEntrance's loop, C0/1A77, comparing SrcPos with A. With #592 the waiting timer fires after the next map's start-up, unless that stops timers (Owzer's maps 207 to 209 do).
EntranceDue: ; carry set: leave the frame to CheckEntrances
phy ; Y is CheckTimer's slot index
longa
lda $af ; the tile under the party
jsr EntAt
bcs Done
ldy $0803
lda $087e,y ; moving direction, 1 to 4 straight
and #$001f
dec
cmp #4
bcc :+
clc ; still or diagonal
bra Done
: asl ; carry clear
tax
lda $af
adc f:StepTbl,x ; the tile straight ahead
jsr EntAt
Done: shorta
ply
rts
StepTbl: .word $ff00, $0001, $0100, $ffff ; up, right, down, left
#615 In the Phoenix Cave, walking into the other party during a rock crossing or a lava jump no longer freezes the game.
In 1.0
In the Phoenix Cave, a party that walked into the other party during a rock crossing or a lava jump froze the game.
Now
A rock crossing runs only while a party stands on the floor switch, and a jump carries the party onto a landing tile the other party stands on.
For ROM hackers 1.0 addresses
A straight obj_script move that is not ASYNC retries every frame while the object-map cell ahead is taken (ExecObjScript, C0/76F8), so the event never ends. Map 315's rock crossings, _cc23d1 (CC/23D1) and _cc23dc (CC/23DC), test only the facing: a party that arrives facing away and turns later can press into the other party. Insert the if_all below (8 bytes) after each one's if_switch; the rocks are open only while a party stands on the floor switch at (23,19). The 17 lava jumps of maps 313 and 315 freeze the same way on a taken landing tile: insert pass_off and pass_on (2 bytes each), as CC/238D already has. #70 covers Master ZED's party switch on the step.
; _cc23d1 and _cc23dc, after the if_switch:
if_all ; $C9
switch $02D2=0
switch $02D3=0
goto EventReturn
; _cc216f _cc2191 _cc21b1 _cc21d1 _cc21fb _cc2225
; _cc223f _cc2259 _cc2279 _cc2299 _cc22b7 _cc22d5
; _cc22f1 _cc230d _cc232d _cc234d _cc236d:
pass_off SLOT_1 ; $78 $31, before the first obj_script
; ...
pass_on SLOT_1 ; $36 $31, before player_ctrl_on
Credit: previously found by Master ZED, my implementation
#616 In the World of Ruin, the clock passage under the rich man's house in South Figaro no longer has Imperial soldiers, only the enemies of the cave beside the town.
In 1.0
In the World of Ruin, the clock passage under the rich man's house in South Figaro kept the Imperial soldiers of Locke's scenario, Vector Pups and Commanders, who appear nowhere else in the World of Ruin.
Now
The passage has the enemies of the cave beside the town.
For ROM hackers 1.0 addresses
CheckBattleSub, C0/C32D, takes a map's random battle group from SubBattleGroup (CF/5600) by map index alone. Map 87, the clock passage, is the same map in both worlds, so its group $41 (formations $176, $175 and $07D) stays in the World of Ruin. Replace the lda at C0/C3D4 (4 bytes) with jsr MyFix and nop; MyFix needs 23 bytes of free space in bank C0 (or end it with rtl and reach it with jsl from any bank). While switch $00A4 is set, the World of Ruin, it gives map 87 group $8A, the cave of map 68. The battle background comes from $0522, the map's own, so it stays.
; C0/C3D4, in place of the lda (.a8 .i16):
jsr MyFix
nop
MyFix: lda f:SubBattleGroup,x ; the replaced lda
cpx #87 ; the clock passage
bne Done
pha
lda $1E94 ; switch $00A4, bit 4
and #$10
beq Keep
pla
lda #$8A ; map 68's group
rts
Keep: pla
Done: rts
Credit: previously found by Master ZED, my implementation
#619 In the Magitek Research Facility, a poisoned party no longer sees Cid through the floor, standing on the lowered lift before his scene.
In 1.0
In the Magitek Research Facility, Cid stood hidden on the lowered lift in the room where the Espers are kept, and with a poisoned party member every step's screen pulse showed him through the floor for a few frames.
Now
Cid is hidden until the scene that brings him up on the lift.
For ROM hackers 1.0 addresses
Map 274's Cid (NPC_13, object $1C) stands on the lowered lift at (20,17) from the room's first frame, under the floor. A poisoned party member's step starts the field mosaic pulse (DoPoisonDmg, C0/4A3F), and for about six frames of it he shows through. In the start-up _cc79fc (CC/79FC), hide him while $0068 is clear: insert hide_obj (2 bytes) before the mod_bg_tiles at CC/7A11, which runs only then. In the scene _cc7a60 (CC/7A60), show him before the camera moves: insert show_obj and sort_obj (3 bytes) after the lock_camera at CC/7AC2.
Credit: previously found by urawaza.in, my implementation
#621 At the Emperor's audience in Vector, a party member no longer slides off the throne room and leaves the player stuck off the map.
In 1.0
At the Emperor's audience in Vector, the party member the scene set apart could slide off the throne room; the audience played on with three, and control came back off the map, where the party could not move.
Now
Every party member starts the audience standing still, and the party lines up below the throne.
For ROM hackers 1.0 addresses
GetTopChar, C0/6F67 (event command $47, update_party), puts every active member on the leader's tile and clears the sub-pixel bytes $0869 and $086C, but keeps the speed $0871-$0874. The audience's _cac6ac (CA/C6AC) runs _cac766 (update_party) and then party_chars on the first of Terra, Locke, Cyan... in the party; a member left with a speed slides off the room, and the scene's closing update_party puts the whole party on his tile. Replace the lda $087A,x / sta $20 at C0/6F93 (5 bytes) with a jsl to the routine below and a nop (12 bytes of free space in any bank).
; C0/6F93 (.a16 .i16), in place of lda $087A,x / sta $20:
jsl MyFix
nop
MyFix: lda $087A,x
sta $20
stz $0871,x ; horizontal speed
stz $0873,x ; vertical speed
rtl
Credit: previously found by LCC and MisterMV, my implementation
#624 A soldier beaten while walking in the moogle defence or the Battle for the Frozen Esper no longer leaves an invisible obstacle on the floor.
In 1.0
In the moogle defence and the Battle for the Frozen Esper, a soldier beaten while walking could leave an invisible obstacle on the tile it was stepping into, a floor tile nobody could walk onto for the rest of the scene.
Now
A hidden NPC leaves no tile taken behind it.
For ROM hackers 1.0 addresses
An NPC that walks holds two tiles in the object map ($7E2000, object number x 2): the one at $087A/$087B, which it is leaving, and the one ahead in its direction $087E, claimed by the move. Hide object, EventCmd_42 (C0/A336), and delete object, EventCmd_3e (C0/9E67), clear only the first, so an NPC hidden mid-step leaves the second taken by an invisible object. After each one's clear at $087A, call a routine (34 bytes of bank C0) that frees the tile ahead, clipped with $86/$87, when it still holds this object.
MyFix: lda $087e,y ; direction, 0-4 (0: no offset)
cmp #$05
bcs @done
xba
lda #$00
xba
tax
lda $087a,y ; the tile ahead
clc
adc f:AdjacentXTbl,x
sta $1e
lda $087b,y
clc
adc f:AdjacentYTbl,x
sta $1f
lda $eb ; object number x 2
asl
ldx $1e
cmp $7e2000,x
bne @done ; another object's
lda #$ff
sta $7e2000,x
@done: rts
#49 Life 2's description no longer says it restores MP, which the spell never did.
In 1.0
The Life 2 description said the spell restores life and "HP/MP", though it brings back a fallen party member with HP and gives back no MP.
Now
The description now says HP only.
For ROM hackers 1.0 addresses
Spell descriptions, MagicDesc (D8/C9A0), entry 49 ($31, Life 2), which says MP is restored as well. Change the slash at D8/CDFD to the terminator $00, which ends the text before the MP part; the two bytes after it go unused. Rewriting the entry 3 bytes shorter instead moves every later entry and its pointer in MagicDescPtrs (D8/CF80).
Credit: previously found and fixed by Imzogelmo, my implementation
#53 Metamorph pack 4 now gives the correct Mithril Shield instead of a Heavy Shield.
In 1.0
Metamorph's item pack 4 held the mithril set, MithrilBlade, Mithril Helm and Mithril Mail, but a Heavy Shld in place of the Mithril Shld.
Now
The pack now holds the Mithril Shld.
For ROM hackers 1.0 addresses
MetamorphProp (C4/7F40), four item numbers per pack. Pack 4, the mithril set, record at C4/7F50: change its fourth byte from item 91 ($5B, Heavy Shld) to item 92 ($5C, Mithril Shld).
Credit: previously found and fixed by Assassin, my implementation
#112 Celes now learns Haste, Muddle and Berserk in the right order.
In 1.0
Celes learns Haste and Muddle at level 32, but if level averaging raised her to a level from 32 to 39 as she joined, she learned Haste and skipped Muddle for good.
Now
She now learns both at level 32 however she gets there.
For ROM hackers 1.0 addresses
NaturalMagic (EC/E3C0), 16 (spell, level) pairs per character. In Celes's list the pairs at EC/E3F0 and EC/E3F2 are Berserk at 40, then Muddle at 32. Learning by average level on joining walks the list until it meets a level above hers, so it stops at Berserk and never reaches Muddle. Swap the two pairs.
Credit: previously found and fixed by Djibriel, my implementation
#125 The battle counter towards the Paladin Shld now advances even while Gogo wears the Cursed Shld.
In 1.0
The Cursed Shld turns into the Paladin Shld after 256 battles, but when Gogo wore it those battles never counted, so his shield stayed cursed for good.
Now
Battles now count while Gogo wears the shield.
For ROM hackers 1.0 addresses
WinBattle. The Cursed Shld's battle count ($3EC0) is kept inside LearnItemMagic (C2/5FEF), which runs only for characters below Gogo, so Gogo's shield never counts. Insert jsr MyFix (3 bytes) at C2/5E59, before the actor number is read; X still holds the character data pointer. MyFix (33 bytes of free space in bank C2) walks the six equipment bytes at $161F,x and counts the shield there, as LearnItemMagic did. Change the bne at C2/5FFC to bra so LearnItemMagic no longer counts it too.
Credit: previously found and fixed by Imzogelmo and Leet Sketcher, my implementation
#155 Setzer's starting Bandana, once removed, can now be equipped again.
In 1.0
Setzer joins wearing a Bandana, but once you took it off him it could never go back on.
Now
Setzer can now equip it again.
For ROM hackers 1.0 addresses
ItemProp (D8/5000), item 110 (Bandana), 30 bytes a record. Setzer starts wearing one but his bit is clear in the item's equip mask (bytes 1 and 2, bit n for character n). Set bit 9: the byte at D8/5CE6, bit 1.
Credit: previously found and fixed by Coraline Violet, my implementation
#185 Fire Beam, Bolt Beam and Ice Beam can no longer be reflected, fixing an attacker sprite that used to drift after a bounce.
In 1.0
Fire Beam, Bolt Beam and Ice Beam could be reflected, unlike the other Magitek attacks. When one bounced off Reflect, no green flash showed and the attacker stayed out of place for the rest of the battle.
Now
The three beams now pass through Reflect like the other Magitek attacks.
For ROM hackers 1.0 addresses
Spell properties, MagicProp (C4/6AC0), 14-byte records. Fire Beam (131), Bolt Beam (132) and Ice Beam (133) lack the Not Reflectable flag the other Magitek attacks have. In byte 3 of each record set bit 1 (flag bit 9, NO_REFLECT): C4/71ED, C4/71FB and C4/7209.
Credit: previously found and fixed by Assassin, my implementation
#212 A thrown Heal Rod, which deals damage, now starts aimed at the enemies instead of at your own party.
In 1.0
A thrown Heal Rod deals damage, but the Throw cursor started on your own party, where every other throwable weapon starts on the enemies.
Now
The Throw cursor now starts on the enemies.
For ROM hackers 1.0 addresses
Throw's item list, on confirming an item: the targeting byte is copied from the list's entry to $7A84 by the lda at C1/8722 and the sta at C1/8725. Weapons have one targeting byte for swinging and throwing, and the Heal Rod's aims at the party. Insert ora #$40 (default to enemies, 2 bytes) between them, so every thrown item starts on the enemy side.
Credit: previously found by Master ZED, my implementation
#238 Flash Rain is now Water element only; it wrongly also carried Ice, so the wrong enemies resisted or absorbed it.
In 1.0
Flash Rain was also marked as Ice, so enemies that resisted or absorbed Ice also resisted or absorbed it.
Now
Flash Rain is now Water only.
For ROM hackers 1.0 addresses
Spell data MagicProp (C4/6AC0, 14 bytes a record), spell 190 ($BE, Flash Rain). Its element byte, the record's second byte at C4/7525, has Ice and Water ($82); change it to $80, Water alone.
Credit: as in the 2014 mobile release and the Pixel Remaster, my implementation
#240 Gogo can no longer equip the Merit Award, which gave him nothing while wasting a relic slot.
In 1.0
Gogo could equip the Merit Award, which did nothing for him since he can already equip everything, and used up a relic slot.
Now
Gogo can no longer equip the Merit Award.
For ROM hackers 1.0 addresses
Item data ItemProp (D8/5000, 30 bytes a record), item 218 ($DA, Merit Award). Its equip word at D8/698D (low byte first) lets every character equip it, Gogo included, for whom it does nothing. Clear Gogo's bit, bit 4 of the high byte at D8/698E: $3F becomes $2F, so the word reads $2FFF.
#241 The Angler Whelk now always pays both its Dragon Claws, not one unless both of its parts fell to one blow.
In 1.0
The Angler Whelk (called the Presenter in this translation) drops a Dragon Claw and 1000 GP from each of its parts, but when one part died the other was taken away without paying, so the fight gave one claw and 1000 GP unless both parts fell to one blow.
Now
Both parts pay whichever dies first: two Dragon Claws and 2000 GP every time.
For ROM hackers 1.0 addresses
AI scripts of the Presenter (monster 257, the Angler Whelk's shell) and the Whelk Head. When one part dies, its script runs boss_death (AI command $F5, mode 1), which takes the other part away without setting Wound, and the victory code pays items, exp and gil only for a wounded monster. Change the mode byte of each command from $01 to $05 (hide and set Wound, same animation, unused in 1.0): CF/A053 in the Presenter's script, CF/B309 in the Whelk Head's.
Credit: as in the Pixel Remaster, my implementation
#540 An esper's 30 percent hit point bonus at a level-up now adds 30 percent; it added about 30.5.
In 1.0
An esper's 30 percent hit point bonus at a level-up came out at about 30.5 percent instead of 30.
Now
It now adds 30 percent.
For ROM hackers 1.0 addresses
The +30% HP and MP esper level-up bonus, C2/6174, multiplies the level's gain by lda #$4E, 78/256, where the +10% and +50% bonuses use the nearest 256ths (26 and 128). Change that operand to $4D (77/256), 1 byte, in place.
Credit: previously found and fixed by Leet Sketcher, my implementation
#608 In Leo's fight with Kefka at Thamasa, the enemy name window shows Kefka instead of an empty row.
In 1.0
At Thamasa, in Leo's fight with Kefka, the enemy name window showed an empty row.
Now
The window reads Kefka.
For ROM hackers 1.0 addresses
MonsterName, CF/C050, 10 bytes per monster. Monster 371, Kefka in Leo's fight at Thamasa, has a blank record at CF/CECE, so the enemy name window shows an empty row. Copy monster 282's record (Kefka, CF/CB54) over it.
#30 The sound driver's tremolo effect now works as the composer wrote it.
In 1.0
The sound driver read the wrong half of a song's tremolo setting, the volume wobble some songs ask for, so the wobble often came out shallower than written.
Now
The setting is now read as written.
For ROM hackers 1.0 addresses
Used as is: everything8215's TREMOLO_BUGFIX in the disassembly's sound driver. The SPC700 driver sits at C5/070E (a size word, then code loaded to SPC RAM $0200). InitTremolo (SPC $13B6, at C5/18C6) masks the tremolo byte $0190+x with and a,#$70 at C5/18E0, keeping bits 4 to 6. The fix keeps bits 0 to 2 and swaps nibbles (and a,#$07, xcn a) before the call to CalcVibratoDelay, and swaps back (xcn a) before the store to $0190+x. The driver grows by 2 bytes: move the code after InitTremolo and adjust its absolute addresses, or put both edits in subroutines at the driver's end, and raise the size word.
#91 The Opera House lobby music no longer plays at full volume from the world map.
In 1.0
The Opera House lobby music is meant to sound muffled, but entering from the world map played it at full volume.
Now
It now sounds muffled straight away.
For ROM hackers 1.0 addresses
Map 237's start-up event _caf182 (CA/F182) muffles the lobby music with spc_cmd, but it does not take effect when the party arrives from the world map. Add an event trigger at (60, 51) on map 237 that runs _caf182: insert its 5 bytes after map 237's two triggers, at C4/0DF1 in EventTrigger, and add 5 to every EventTriggerPtrs word from map 238's (C4/01DC) to the end. The table has 18 unused bytes before C4/1A10, so it fits.
Credit: previously found and fixed by Leet Sketcher, my implementation
#141 Shadow's theme no longer keeps playing at the well after he has left the party.
In 1.0
In Sabin's scenario, once Shadow had left the party, his theme still played by the well outside a house although he was no longer there to find.
Now
The theme now plays at the well only while Shadow is standing there.
For ROM hackers 1.0 addresses
Event _cb0b6b (CB/0B6B), the Imperial Camp well. It skips Shadow's theme only while Shadow is in the party (if_switch $01A3=1 at CB/0B6C), so the theme plays after he has left. Test the Shadow NPC's own switch instead: change the switch word at CB/0B6D from $81A3 ($01A3 set) to $0426 ($0426 clear; bit 15 is the tested value). Same size.
Credit: previously found and fixed by Novalia Spirit, my implementation
#144 The tunnel ambience to the Lete River after Banon joins no longer plays too loud.
In 1.0
After Banon joins at the Returners' Hideout, the ambient sound in the tunnel to the Lete River played too loud until you opened the menu or left the area and came back.
Now
It now plays at its intended volume right away.
For ROM hackers 1.0 addresses
Event _cafff0 (CA/FFF0), the scene after Banon joins that loads map 112. Its spc_cmd $10, $1E, $80 at CB/0014 starts the tunnel ambience with volume $80 (128), where the intended volume is 100. Change the command's last byte at CB/0017 to $64.
Credit: previously found and fixed by Novalia Spirit, my implementation
#429 The world map music now restarts properly after a Game Over, instead of resuming mid-song.
In 1.0
After a Game Over in a world map battle, the world map song picked up where the battle had cut it off once the game reloaded.
Now
The song now starts from the beginning after the reload.
For ROM hackers 1.0 addresses
GameOver event, CC/E566. The battle left the world song in the sound driver's paused slot, so the reloaded save's world song resumed from there. Insert play_song SILENCE, PAUSE_CURRENT (2 bytes) at the head of GameOver: the battle's song takes the paused slot, and the song the load_map after it plays is silence. Change the play_song REST_IN_PEACE at CC/E5D5 to add PAUSE_CURRENT (bit 7 of its song byte, same size), which parks that silence in the paused slot, so nothing is left there to resume.
Credit: previously found by Novalia Spirit, my implementation
#434 In a two-player game, the "your turn" sound now follows the controlling player instead of always the first party slot.
In 1.0
In a two-player game, the party member in the first party slot always played player 1's turn sound, even when player 2 controlled them.
Now
The first slot now plays the sound of the player who controls it.
For ROM hackers 1.0 addresses
UpdateScrollHDMA, the window 2 block at C1/07F0. The longa at C1/07F2 makes the read and clear of the update flag $6197 16 bits wide, so the stz at C1/07F9 also clears $6198, slot 0's controller byte. Move the longa to just after that stz, and point the beq at C1/07F7 at a new longa placed before C1/081E, so the code after it still runs with 16-bit A (2 bytes more).
shorti
lda near w7e6197 ; 8-bit now
beq @skip
stz near w7e6197
longa ; moved here
clr_ax
...
@skip: longa ; new
@081e: stz $34
Credit: previously found by Novalia Spirit, my implementation
#537 At the auction, the ZoneSeek magicite another bidder buys now leaves the table with the magicite sound, as the Golem one does.
In 1.0
At the Jidoor auction, when the player passed on the ZoneSeek Magicite and an NPC in the audience bought it, the stone left the table in silence, unlike the Golem Magicite.
Now
It now plays the Magicite pick-up sound, in both worlds.
For ROM hackers 1.0 addresses
Jidoor auction, the two ZoneSeek scenes where a bidder buys the lot: _cb53cc (CB/53CC) and _cb5af1 (CB/5AF1). The Golem scenes play sfx 141 (MAGICITE_PICKUP, $8D) as the stone leaves the table; these two do not. Insert sfx 141 (2 bytes) before the delete_obj NPC_15 at CB/53E1 and before the one at CB/5B06.
Credit: previously found by mblock129, my implementation
#623 After four scenes (the Figaro gate, the Figaro Cave mouth, the ferry, the elder's house in Narshe) the place's own music returns instead of the scene's song playing on.
In 1.0
After four scenes, the scene's song played on until the next door or the menu instead of the place's own: Locke's Theme at the Figaro Castle gate, "Awakening" at the Figaro Cave mouth and in the elder's house in Narshe, and "Shore" on the South Figaro docks after the ferry.
Now
The scene's song fades out and the place's own song fades in; in Narshe the town's song picks up where it left off.
For ROM hackers 1.0 addresses
PlayMapSong plays the map's song on a map load or after the menu, or the last event song while switch $01CC is set; clearing $01CC leaves the event song playing to the next load. Insert fade_out_song ($F2) and fade_in_song ($F1, the map's song) where each scene ends its mood: _ca715b (CA/715B, the Figaro guard, castle song $0A), _ca75ee (CA/75EE, the cave mouth, song $06), _ca8cf7 (CA/8CF7, the World of Balance ferry, on arrival, docks song $2A). In _ca931c (CA/931C) set the arrival's STARTUP_EVENT flag (byte CA/932F, $00 to $80), so the Nikeah docks' start-up _caee8a (CA/EE8A) plays Under Martial Law. In _cc70ab (CC/70AB) set PAUSE_CURRENT on play_song AWAKENING (CC/7237, $05 to $85), add fade_out_song before the wait_90f at CC/7255 and resume_song ($F3) after the screen fades back in.
; _ca715b, _ca75ee, _ca8cf7, after the scene's switch $01CC=0:
fade_out_song $40 ; $F2 $40
fade_in_song FIGARO, 48 ; $F1 $0A $30 (the map's own song)
; _cc70ab:
play_song AWAKENING, PAUSE_CURRENT ; $F0 $85 at CC/7237
fade_out_song $40 ; before wait_90f / fade_out 2 at CC/7255
resume_song $40 ; $F3 $40, before fade_in
Credit: previously found by Novalia Spirit, my implementation
#626 In Zozo, after Ramuh's scene, opening the menu no longer swaps the rain for the town's music until the party leaves the room.
In 1.0
In Zozo, after Ramuh's scene at the top of the tower, the rain plays on, but opening the menu there brought back the town's music until the party spoke to the others and left.
Now
The rain plays on through the menu until the party leaves the room.
For ROM hackers 1.0 addresses
PlayMapSong plays the map's song on every map load, the field menu's reload included, unless switch $01CC is set. Ramuh's scene at the top of Zozo (map 226) leaves the rain alone (song $40) playing, so a menu there brought the map's song back until the exit talk _caa890 (CA/A890) played the rain again. Repoint map 226's start-up entry, MapInitEvent+$2A6 (D1/FCA6), to the event code below (21 bytes of free space). Between the end of Ramuh's scene (switch $0053 set) and the exit talk ($0054 set) it sets $01CC for the load only: clearing $01CF ends the start-up loop before the song is chosen.
MyFix: call _caf004 ; the map's own start-up
if_any
switch $0053=0
switch $0054=1
goto EventReturn
switch $01CC=1 ; keep the playing song
switch $01CF=0 ; end the start-up loop
wait 1
switch $01CC=0
return
#68 Every game over no longer leaks a frame of the event stack.
In 1.0
Enough game overs in a row on the world map could freeze the game, start broken cutscenes or even jump to the ending.
Now
Repeated game overs no longer cause any of this.
For ROM hackers 1.0 addresses
EventCmd_4f (C0/A5F3) only sets $11F1; the field loop then restores the save at C0/00F3, setting the event pointer to NoEvent. The call frame the game over script sat in is never returned from, so the event stack pointer $E8 keeps it, and each game over leaks one three-byte frame. Insert stz $E8 and stz $E9 (4 bytes) before the jsr RestartGame at C0/0104, emptying the stack as InitEvent does for an idle event machine.
Credit: previously found and fixed by Leet Sketcher, my implementation
#69 Going to the airship's deck in flight and then losing on the Floating Continent no longer brings the save back with the party flying the airship.
In 1.0
Pressing X in flight to go to the airship's deck, then flying to the Floating Continent a second time and losing a battle there, could bring the save back with the party flying the airship, from any point in the story.
Now
A restored save no longer starts with the party flying the airship.
For ROM hackers 1.0 addresses
The world map sets $11F2 bit 15 when X is pressed in flight (EE/6EAD, the jump to the airship's deck), meaning come back to the world map in the airship; VehicleMain clears it. The wheel's trip to the Floating Continent after the Air Force loads map 394 with no world map, so the bit survives to a game over there. $11F2 is outside the saved game, so after a restore the flag can still be set and the next world map load hands over the airship. Insert stz $11F2 and stz $11F3 (6 bytes) at the head of InitSavedGame (C0/BD20), which both the game over restore and the file menu's load pass through.
Credit: previously found and fixed by Novalia Spirit, my implementation
#162 Leftover positions of NPCs on the map are now cleared when a save loads, so a reset can no longer freeze a scene.
In 1.0
After a console reset, loading a save put NPCs at their old positions instead of the saved ones, which could soft lock a scene.
Now
A loaded game now puts everyone where the save and the scene put them.
For ROM hackers 1.0 addresses
InitSavedGame, C0/BD20, runs on every route into a saved game (after RestartGame for event command $4F, and after the title's file menu for $AC). The field object array $0867-$1068 is not saved and nothing clears it, so after a reset a character object keeps the enabled, visible and animation state of the session before. Insert at the head (17 bytes): clear the array, then jsr PopPartyMap (C0/6EBF) to put back the map index the clear wiped.
Credit: previously found and fixed by Madsiur, my implementation
#432 A highlighted save file's leftover timer no longer counts down into a New Game; New Game now clears all four timers.
In 1.0
Highlighting a save file on the file screen loaded any timer running in that save, and starting a New Game afterwards kept it and let it count down over the opening.
Now
New Game now clears all four timers.
For ROM hackers 1.0 addresses
Save select menu, New Game choice at C3/2A07. A file chosen in the menu has its four timers copied into $1188-$119F by PopTimers (C3/1595), and nothing clears them when New Game is chosen afterwards. Insert after the jsr ResetGameTime, before C3/2A0D (9 bytes, 8-bit A, 16-bit X): clear the 24 bytes.
ldx #$0017
@clear: stz $1188,x ; the four timers
dex
bpl @clear
Credit: previously found by duggie2, my implementation
#503 A save slot whose checksum happens to sum to zero is no longer mistaken for an empty slot.
In 1.0
A save whose checksum happened to add up to zero (about one in 65,536) was taken for an empty slot, and started a New Game instead of loading.
Now
Such a save now loads normally.
For ROM hackers 1.0 addresses
CheckSaveSlotChecksum, C3/19EB, returns the checksum itself in Y as the slot's exists flag, so a real save whose sum is $0000 reads as blank. CalcSaveSlotChecksum (C3/19D1): insert stz $E9 (2 bytes) after stz $E8 at C3/19D3, and pha / ora $E9 / sta $E9 / pla (6 bytes) after the lda $1600,x at C3/19D8, which keeps the OR of every byte. In CheckSaveSlotChecksum, the 9 bytes from the bne at C3/19F2 on become the 20 below: Y = 1 on a match with $E9 nonzero, Y = 0 otherwise.
bne @no ; from C3/19F2: mismatch
shorta0
lda $e9 ; OR of every byte
beq @no ; all zero: a blank slot
ldy #$0001
bra @out
@no: ldy #$0000
@out: shorta
rts
Credit: previously found by gcgx.games, my implementation
#542 In the demo that plays when the title screen is left alone, A closes Vicks and Wedge's lines again; leftover memory at power-on could turn every button into L.
In 1.0
In the demo that plays when the title screen is left alone, leftover memory at power-on could make every button act as L, so A could not close Vicks and Wedge's lines on the cliff.
Now
A now closes their lines again.
For ROM hackers 1.0 addresses
Reset, C0/0019. At power-on the button map is built by the jsl InitCtrl_ext at C0/0053 from the Config byte $1D54 before any save is loaded, so it reads leftover RAM; with bit 6 (custom layout) set every button maps to L in the title demo. Insert stz $1D54 (3 bytes) before that jsl. A loaded save still brings its own $1D54.
Credit: previously found by Novalia Spirit, my implementation
#567 A New Game now starts the random-battle count from zero; memory left over from power-on or a Reset could start a fight on the first step into the Narshe mines.
In 1.0
The random-battle counter was never set when a New Game began, so it started from leftover memory, and a high leftover started a fight on the first or second step into the Narshe mines.
Now
A New Game now starts the counter from zero.
For ROM hackers 1.0 addresses
Reset, C0/0019. The random-battle counter $1F6E-$1F6F is never set for a new game, so it keeps whatever RAM held. The two stz at C0/0071 and C0/0074 clear $115A and $1159, which ClearRAM (C0/0525) has already cleared and nothing reads. Change their operands to $1F6E and $1F6F: same size, nothing moves. A loaded save still brings its own counter, since the file menu comes later.