lushen124 / Universal-FE-Randomizer

Properly universal this time.
MIT License
98 stars 28 forks source link

Enhancement request: GBAFE Utility Modes (Paragon / Casual) #408

Closed Geeene closed 1 year ago

Geeene commented 1 year ago

In our Previous email conversation lushen124 mentioned that something to consider to make the randomizer a bit more approachable to some players would be options to

  1. Add a paragon mode
  2. Add a casual mode

I certainly agree with that assessment, there are probably some players of newer titles that would like to come back to older games but don't want to paly with perma death, and having casual mode be possible to add with the randomizer could certainly be helpful rather than them having to go out of their way to find a patch / patcher and then apply the patch themselves.

I had a look through the FEBuilderGBA Patch list and found the following values to change:

FE8 Combat: 0x002c37c = 31 -> X (Author / Source: Not mentioned) FE8 Healing: 0x2C676 = 10 -> X (Author / Source: Not mentioned) FE8 Steal, dance, summon etc: 0x2C6CC, 0x2C6D2 10 -> X (Author / Source: 7743) FE8 Casual Mode: 0x18418=0x00 0x4B 0x18 0x47 (Author / Source:circleseverywhere, Vesly)

FE7 Combat: 0x029DB0 = 31 -> X (Author / Source: Not mentioned) FE7 Healing: 0x02A032 = 10 -> X (Author / Source: Not mentioned) FE7 Steal, dance etc: 0x02A086, 0x2A08C 10 -> X (Author / Source: 7743) FE7 Casual Mode: 0x17E9E=0x00 0x00 0x00 0x4B 0x18 0x47 (Author / Source: circleseverywhere)

FE6 Combat: 0x0257C4 = 31 -> X (Author / Source: Not mentioned) FE6 Healing: 0x025976 = 10 -> X (Author / Source: Not mentioned) FE6 Steal, Dance, etc: There is no patch for this it seems, but I found it to be this value: 0x0259C6 10 -> X. (idk why there is no patch, submitted a question on FEU just incase) FE6 Casual Mode: 0x17BEC = 0x00 0x4B 0x18 0x47 (Author / Source: circleseverywhere, 7743)

Further more something else that could be considered is that in FE6 gaining weapon EXP is miserably slow since all different weapons give 1 base WEXP, and the only way to really improve that is with brave weapons / landing the killing blow. Both of which aren't an option for Staves, so they take literally forever. These following values improve the speed of gaining WEXP for FE6:

FE6 increase Staff WEXP = 0x025916 = 1 -> X (Author / Source: CedAodh https://discord.com/channels/144670830150811649/470029781795078175/865345288024358932) FE6 WEXP per Attack = 0x24e74 1 -> X (Author / Source: Not mentioned) FE6 WEXP bonus per Kill = 0x24ff4 1 -> X (Author / Source: Not mentioned)

all these changes should be easy enough to do by just applying them as a new diff

lushen124 commented 1 year ago

It's funny because I was looking into this a few months back for the GBA titles and I found the changes needed for all of these (except for WEXP). I'd have to check my own notes again, but it doesn't seem to be exactly the same as these. Granted, I haven't tested my changes extensively yet, so I don't know if it breaks other things, but IIRC, the change for "casual" mode was to set the status flag to "undeployed" (0x09 if I recall correctly) instead of "dead" (0x05). The paragon change just did a left shift on the last step of calculations whenever EXP was gained, prior to the 0 < EXP < 100 check. It shouldn't be too hard to change. I may have some time in April to roll in all of the changes and bugfixes over the last several months into a 0.9.5 release, even though I was targeting Radiant Dawn and 1.0 initially.

Geeene commented 1 year ago

Hi, good to hear from you again,

I'm sure there are other ways of accomplishing these, I just thought that taking the already pre-existing and tested patches from FEBuilder would be the easiest way.

the EXP change here doesn't go by leftshifting, but rather changes the base value for exp for the calculation from 31 to 62 (or whatever is picked I guess). Won't be much of a difference probably. Though it might have more flexibility in giving the user the ability to select how much change rather than just predeciding for 2x. Could even go for 50% exp, could probably be interesting too.

From what I understand yes casual mode swaps them fom 5 to 9 as you said. Though I did see now that the part in the top post isn't all it does when installing through FEBuilder there is another .dmp that is added too, would have to doublecheck unless you already happened to have your own version.

lushen124 commented 1 year ago

50% EXP is basically just changing the left shift to a right shift 🙂

It sounds like the EXP change from 0x31 to 0x62 is much earlier in the EXP calculation formula, which to be honest, I'm unfamiliar with beyond knowing that it probably involves the level delta, whether it's a kill or not, and something about class power, IIRC. I think the end result may not always end up a value you'd expect, especially at the lower bound, meaning it's still possible to only get 1 XP if the other parameters were extreme enough.

The left shift solution does what I think most people would expect: double the final result of the EXP calculations. But as you said, it's a bit less flexible because shifting only adjusts by powers of 2, so doubling and halving (rounded down). But you can always substitute the shift with a multiply operation if you wanted 3x, but you are messing with more CPU cycles now, and it's still only with integers. Alternatively, you can always add a flat amount to it with an add instruction, but that sounds completely broken since it'd make missing (or other 1 XP events) grant the flat amount. Or you can do something clever and store a shift right result into a different register and add it to the final result for a 1.5x EXP, but I don't remember how many extra bytes I had for more instructions. 🙃

Yeah, as I said, I haven't extensively tested my changes for casual mode beyond making sure that killing a character still has them show up in the next chapter and that killing the lord character still causes a game over. I have no idea as to how it affects, say battle records or the epilogue or any other death checks later on.

I'll revisit my notes when I get the chance this weekend to see what I actually wrote down. I had all of the research done and ready to add, but I never got around to adding it 😅

Geeene commented 1 year ago

I looked at the exp formular again and you are totally correct, right / left shift would certainly be more what the player would expect as the base value change wouldn't apply to the kill bonus. So I guess the only thing from the OP still relevant as it is, would be the FE6 wexp part

lushen124 commented 1 year ago

It's still good to know since it can provide hints on other parts of the code. I looked at my notes again for EXP gain, and realized I only solved for FE8 thus far. I found the code for FE7, but it's different and therefore, needs some more creative solutions to squeeze in a left/right shift somewhere in it. I'm pretty sure FE6 is similar to FE7, so the challenge remains for that too.

lushen124 commented 1 year ago

Ok, so based on my notes for the casual mode, mine is actually a little different, but it's probably close enough to the above solutions that it doesn't matter which one we use. As far as I can tell, the changes above just don't update the flags in the SRAM on death, whereas my change is to update the flag to being "not deployed" (0x9) instead of "dead" (0x5). I'm curious what happens with the above change if you resume the chapter, because it seems like it would respawn them again (almost like Phoenix mode?)

In any case, Casual mode would be:

FE6: 0x17BEA - Change 0x05 to 0x09
FE7: 0x17EA0 - Change 0x05 to 0x09
FE8: 0x1841A - Change 0x05 to 0x09

Technical explanation:

The code is the same in all three games, so to use FE7 as an example:

08017EA0        movs r1, 5h
08017EA2        orrs r0, r1
08017EA4        str r0,[r2, 0Ch]

This code runs whenever a playable character is killed. This part specifically writes a status flag to SRAM (i.e. your save data) to mark the character as being dead. The flag for a character being dead is 0x05, as it shows in the first instruction. The code ORs this value with any other character data to be written. The actual write to SRAM happens on the last line, as the result of the OR operation is stored into the address at r2, offset by 0xC (for reference, this is 0x202be34). This code is identical in the other games, except it lives at a different address and the SRAM structure and address may be different and the actual working register may be different.

The change then, is to mark it as 0x9 instead of 0x5, indicating that the character was never deployed to begin with.

08017EA0        movs r1, 9h
08017EA2        orrs r0, r1
08017EA4        str r0,[r2, 0Ch]

You can observe your party data in the SRAM. Here's an example:

Eliwood     - 01 01 00 00 00 00 40 00 09 28 6B 03 00 00 12 12 00 00 47 00 00 00 00 00 00 00 48 0A 00 19 14 19 00 00 00 00 E1 59 4A 39 05 1C 00 00 00 00 00 00 00 00 00 00
Marcus      - 1A 2A 00 00 42 00 40 00 03 1E 16 1E 00 00 1F 1F 00 00 B5 B5 79 00 00 00 00 00 19 00 14 0F 14 00 00 00 00 00 21 32 DE 5B 0A 21 00 00 00 00 00 00 00 00 00 00
Lowen       - 19 28 00 00 00 00 40 00 01 2E 1C 14 00 00 17 17 00 00 1F 1F 00 00 00 00 00 00 14 14 0A 0F 05 00 00 00 00 00 E2 49 4E 39 07 0C 00 00 00 00 00 00 00 00 00 00
Rebecca     - 0E 19 00 00 00 00 40 00 2C 2D 6B 03 00 00 11 11 00 00 00 00 00 1F 00 00 00 00 1E 05 00 23 00 00 00 00 00 00 01 4A 48 31 23 10 00 00 00 00 00 00 00 00 00 00

Note the 5th byte in the sequence. 0x42 is for a character that has already acted. If you kill off Rebecca (and after Bartre and Dorcas join in Eliwood Ch. 11), then it becomes:

Eliwood     - 01 01 00 00 00 00 40 00 09 28 6B 03 00 00 12 12 00 00 47 00 00 00 00 00 00 00 48 0A 00 19 14 19 00 00 00 00 01 3A 4A 39 05 1C 00 00 00 00 00 00 00 05 00 00
Marcus      - 1A 2A 00 00 00 00 40 00 03 1E 16 1E 00 00 1F 1F 00 00 B5 B5 79 00 00 00 00 00 19 00 14 0F 14 00 00 00 00 00 21 32 DE 5B 0A 21 00 00 00 00 00 00 00 05 00 00
Lowen       - 19 28 00 00 00 00 40 00 01 2E 1C 14 00 00 17 17 00 40 1F 1F 00 00 00 00 00 00 14 14 0A 0F 00 00 00 00 00 00 02 2A 4E 39 07 0C 00 00 00 00 00 00 00 05 00 00
Rebecca     - 0E 19 00 00 05 00 40 00 2C 2D 6B 03 00 00 11 00 02 41 00 00 00 1F 00 00 00 00 1E 00 00 23 00 00 00 00 00 00 E1 31 48 31 23 10 00 00 00 00 00 00 00 05 00 00
Dorcas      - 08 12 00 00 00 00 40 00 20 1D 5B 01 00 00 1E 13 0A 00 00 00 49 00 00 00 00 00 0F 00 00 00 00 00 00 00 00 00 03 1A CE 31 03 0C 00 00 00 00 00 00 00 05 00 00
Bartre      - 09 12 00 00 00 00 40 00 1F 2D 28 14 00 00 1D 1D 00 40 00 00 1F 00 00 00 00 00 0F 00 00 05 00 00 00 00 00 00 22 1A 52 19 04 10 00 00 00 00 00 00 00 05 00 00

For an example of an un-deployed character, see this example. Nils and Lucius were not deployed in this chapter:

Lyn         - 03 02 00 00 42 00 20 00 01 0D 0A 2C 6B 02 12 12 2D 00 4E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 59 4C 62 02 20 00 00 00 00 00 00 00 09 00 00
Sain        - 18 28 00 00 00 00 20 00 01 13 14 2B 6B 03 17 0F 2A 00 2A 33 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E7 48 56 41 48 18 00 00 10 E0 02 00 00 09 00 00
Kent        - 17 28 00 00 00 00 20 00 01 06 14 2A 6B 03 19 19 25 00 56 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 06 71 10 5A 46 08 00 00 D0 36 00 00 00 09 00 00
Florina     - 1D 32 00 00 00 00 20 00 14 1D 15 0C 6B 02 14 14 04 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 60 0E 52 85 20 00 00 00 00 00 00 00 09 00 00
Wil     - 0D 18 00 00 00 00 20 00 2C 20 00 00 00 00 15 15 3C 40 00 00 00 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 69 8C 31 25 18 00 00 00 00 00 00 00 09 00 00
Dorcas      - 08 12 00 00 42 00 20 00 1F 2A 28 10 6B 03 1E 1E 45 40 00 00 51 00 00 00 00 00 0F 00 00 00 00 00 00 00 00 00 C4 60 0E 32 03 10 00 00 00 00 00 00 00 09 00 00
Erk     - 13 20 00 00 00 00 20 00 37 21 00 00 00 00 12 12 44 41 00 00 00 00 00 28 00 00 14 00 19 0F 02 00 00 00 00 00 E2 58 CA 41 82 10 00 00 00 00 00 00 00 09 00 00
Serra       - 11 1D 00 00 00 00 20 00 4A 1B 00 00 00 00 11 11 21 06 00 00 00 00 25 00 00 00 0F 05 00 00 0A 00 02 00 00 00 01 61 44 41 A2 18 00 00 00 00 00 00 00 09 00 00
Rath        - 1C 2E 00 00 00 00 20 00 32 14 6B 03 6B 03 19 19 18 40 00 00 00 51 00 00 00 00 00 00 00 00 00 00 00 00 00 00 A7 60 50 52 47 14 00 00 00 00 00 00 00 09 00 00
Matthew     - 23 3C 00 00 00 00 20 00 01 2E 6A 0C 5A 01 12 12 00 00 1F 00 00 00 00 00 00 00 14 00 0A 05 05 00 00 00 00 00 E2 78 08 59 03 08 00 00 E0 20 01 00 00 09 00 00
Nils        - 26 41 00 00 09 00 20 00 6B 03 00 00 00 00 0E 0E 0A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E1 4F 00 60 85 28 00 00 00 00 00 00 00 00 00 00
Lucius      - 10 1C 00 00 09 00 20 00 3E 20 00 00 00 00 12 12 20 00 00 00 00 00 00 00 23 00 1E 05 00 00 00 00 00 00 00 00 E3 27 8E 51 C1 08 00 00 00 00 00 00 00 00 00 00
lushen124 commented 1 year ago

Paragon/Anti-Paragon (Renegade?) mode is a bit more involved, but only because there's 3 different pieces to modify, but the changes themselves are not too bad.

First is to update the combat EXP handler. FE8 is slightly different, but only slightly.

    0x29F64 (for FE7), 0x258D0 (for FE6)
        Original: 24 18 64 2C 00 DD 64 24 00 2C 00 DA 00 24 20 1C 70 BC 02 BC 08 47 00 00
            To Double EXP: 24 18 64 00 64 2C 00 DD 64 24 00 2C 00 DA 00 24 20 1C 70 BC 02 BC 08 47
            To Halve EXP: 24 18 64 10 64 2C 00 DD 64 24 00 2C 00 DA 00 24 20 1C 70 BC 02 BC 08 47
    0x29F50 (for FE7), 0x258BC (for FE6)
        Original: 10 E0
        New: 11 E0
    0x29F3E (for FE7), 0x258AA (for FE6)
        Original: 19 E0
        New: 1A E0
    0x29F4E (for FE7), 0x258BA (for FE6) - Only to double the Miss EXP from 1 to 2.
        Original: 01 20
        New: 02 20

In the case of FE8, this was modified slightly, but the change is a bit easier to make:

    0x2C58A
        Original: 00 99 09 18 00 91 64 29 01 DD 64 20 00 90 00 98 00 28 01 DC 01 20 00 90
            To Double EXP: 00 99 09 18 48 00 64 28 00 DD 64 20 00 28 00 DC 01 20 00 00 00 00 00 90
            To Halve EXP: 00 99 09 18 48 10 64 28 00 DD 64 20 00 28 00 DC 01 20 00 00 00 00 00 90

To update staff EXP, the change is universal across all games (at different offsets, obviously):

FE6: 0x25988
FE7: 0x2A044
FE8: 0x2C688

    Original: 00 28 02 D0 D0 0F 10 18 42 10 64 2A
        To Double EXP: 00 28 01 D0 52 10 00 00 52 00 64 2A
        To Halve EXP: 00 28 01 D0 52 10 00 00 52 10 64 2A

Finally, for Steal/Dance EXP, this is primarily lifted from the OP, so credit to whoever found those values. These changes are also somewhat universal, but the bytes in between are a little different.

FE6: 0x259C6
    Original: 0A 20 08 70 18 1C 0A 30
        Replace both instances of 0x0A to your desired EXP amount for stealing and dancing. 0x05 for Half EXP and 0x14 for Double EXP.
FE7: 0x2A086
    Original: 0A 20 08 70 60 7A 0A 30
        Replace both instances of 0x0A to your desired EXP amount for stealing and dancing. 0x05 for Half EXP and 0x14 for Double EXP.
FE8: 0x2C6CC
    Original: 0A 20 08 70 60 7A 0A 30
        Replace both instances of 0x0A to your desired EXP amount for stealing and dancing. 0x05 for Half EXP and 0x14 for Double EXP.

Technical Explanation:

FE6 and FE7 are a bit more complex because there's not as much room to insert a shift left or right to double or half the EXP. Here's the original code for clamping the combat EXP:

8029F64         adds r4, r4, r0
8029F66         cmp r4, 64h
8029F68         ble Lxx_8029F6Ch
8029F6A         movs r4, 64h
8029F6C         cmp r4, 0h
8029F6E         bge Lxx_8029F72h
8029F70                 movs r4, 0h
8029F72                 adds r0, r4, 0
8029F74                 pop r4-r6
8029F76         pop r1
8029F78         bx r1
8029F7A                 (00 00) <-- That's convenient!

That nop at the end literally saved us since the subroutine that follows this one has to start on a word-aligned address. We have exactly one instruction to work with, and that's all we need. The updated code:

8029F64         adds r4, r4, r0
8029F66                 lsl r4, r4, 1h (or asr r4, r4, 1h for halving)
8029F68         cmp r4, 64h
8029F6A         ble Lxx_8029F6Ch
8029F6C         movs r4, 64h
8029F6E         cmp r4, 0h
8029F70                 bge Lxx_8029F72h
8029F72                 movs r4, 0h
8029F74                 adds r0, r4, 0
8029F76         pop r4-r6
8029F78         pop r1
8029F7A                 bx r1

Since we shifted everything by 1 instruction, there are a couple branches above this block that need to be adjusted to still branch to the right place. Additionally, one of the branches above handles the case where the result is 1 EXP (usually from missing or dodging an attack). That also needs to be doubled to complete the coverage of the Paragon mode for combat EXP (but isn't necessary if you're halving the EXP).

FE8 is a bit simpler because the the EXP routine was modified a bit to seemingly be a bit less efficient, meaning it took more instructions to do the bounds check to clamp the EXP between 0 and 100. The original FE8 logic was:

802C58A                 ldr r1, [sp]
802C58C         adds r1, r1, r0
802C58E         str r1, [sp]
802C590         cmp r1, 64h
802C592         ble 
802C594         movs r0, 64h
8025C96         str r0,[sp]
8025C98         ldr r0,[sp]
8025C9A                 cmp r0, 0h
8025C9C         bgt
8025C9E         movs r0, 1h
8025CA0         str r0,[sp]

As far as I can tell, there's no real reason for 802C58E to store it because it doesn't get read again before being overwritten and for 8025C96 to store it only for it to load it in the next line, into the same register no less. So the change is to get rid of those 802C58E, 8025C96, and 8025C98 and use one of those to do the right shift or left shift to halve or double the EXP, respectively.

The new logic would then be:

802C58A                 ldr r1, [sp]
802C58C         adds r1, r1, r0
802C58E                 lsl r0, r1, 1h
802C590         cmp r0, 64h
802C592         ble 802C596
802C594         movs r0, 64h
802C596         cmp r0, 0h
802C598         bgt 802C59C
802C59A         movs r0, 1h
802C59C         nop
802C59E         nop
802C5A0         str r0, [sp]

Thankfully, the staff EXP routines and the steal/dance EXP values are much more universal and straightforward. Again, using FE7 as an example, the staff EXP code looks something like this just prior to applying bound checking to the value:

0802A044        cmp r0, 0h
0802A046        beq Lxx_802A04Eh
0802A048        lsrs r0, r2, 1Fh
0802A04A        adds r0, r2, r0
0802A04C        asrs r2, r0, 1h
0802A04E        cmp r2, 64h <-- This begins the bounds check to cap EXP at 100.

There's actually 0 space remaining in this routine, so we have to be a bit more creative. This particular chunk after the beq instruction actually seems to be handling halving the EXP gain if the character is promoted. I couldn't figure out the significance of the instruction at 802A048 and 802A04A, because the base EXP gain is stored in r2. The logical right shift is so extreme that it's virtually guaranteed to make any EXP gain to 0, which then is added to the base EXP gain itself for some unknown reason. My only guess is that this is to check for underflow scenarios since the bound check doesn't check the lower bound in this routine. But the result of both of these instructions doesn't seem to change the final EXP gain in most cases. The actual halving effect takes place at 802A04C.

So the "solution" is to get rid of the two instructions that look like they don't have any effect and leave the halving instruction in place. We'll move the shift right instruction to right after the beq instruction, followed by a nop. Then at 802A04C, we do our paragon/renegade shift operation. This also means that the beq instruction, instead of branching to 802A04E, would now branch to our shift instruction at 802A04C in order to apply universally. The final code looks like:

0802A044        cmp r0, 0h
0802A046        beq Lxx_802A04C
0802A048        asrs r2, r2, 1h
0802A04A        nop
0802A04C        lsls r2, r2, 1h <-- This is our doubling/halving shift.
0802A04E        cmp r2, 64h

Finally, the steal/dance EXP is just hard coded, so we just change the values. The code (in FE7):

0802A086            movs r0, 0Ah
0802A088            strb r0,[r1]
0802A08A            ldrb r0, [r4, 9h]
0802A08C            adds r0, 0Ah
0802A08E            strb r0, [r4, 9h]

I don't exactly know how this code works yet, but I confirmed that these values are being written to the same RAM addresses as the other EXP routines, so chances are good that all "fixed" EXP sources that give 10 EXP normally come through here. The change is to just update the hard coded immediate values at 802A086 and 802A08C from 10 (0xA) to anything else.

lushen124 commented 1 year ago

Merged with #409.