Expansion of Treasure Chest subroutine
07-01-2018, 06:33 PM
(This post was last modified: 08-17-2018, 04:34 AM by NPCnextdoor.)
Hello,
I started to learn ASM and I'm having lots of fun. I have an idea to expand the treasure chest subroutine. Currently, it offers the following possibilities:
I want to change it so it would also be possible to:
While the first two are technically already possible (as a small test, I changed the JSR branching of getting an item to the one that gives a summon and it worked. The message box was wrong, but I got the summon.), the other ones require an additional byte in the chest information. The Treasure Chest data is composed of 5 bytes starting at $ED/8634.
Here is the beginning of the subroutine:
The thing is that the X register contains the relative value of the first byte of the current treasure chest. It is 5 times larger than the chest "ID". So, it gave me an idea. There is absolutely no need the keep the encoding byte/bit as hard-coded information if it can be found by a simple division (I know it costs many cycles, so it is not that simple! ). In that way, you ensure that every chest has its own encoding bit.
Personally, I never liked the whole "not opening treasure chests so they offer more powerful items in the future" thing. I want to open ALL the chests. Additionally, it frees a whole byte.
Please note this is only a proof-of-concept. I know the code is really not optimized. I just wanted to move as less stuff as possible around. Now, this works. The chest is obtainable once and no longer available after that. But when the map is reloaded (after opening/closing the menu, finishing a battle or leaving the map and coming back), the chest is closed once more (its content can't be retaken though). My first question would be: Where in the map generation code the subroutine that decides if a chest is open or closed? Thanks in advance!
I started to learn ASM and I'm having lots of fun. I have an idea to expand the treasure chest subroutine. Currently, it offers the following possibilities:
- Granting an item.
- Granting a multiple of 100 gold, up to 25500 gold.
- Starting a Monster-in-a-box encounter with a monster pack between #256 and #511.
- Being empty.
I want to change it so it would also be possible to:
- Grant a rare item. [DONE]
- Grant a summon. [DONE]
- Grant between 1 and 99 items of one type. [DONE]
- Grant a multiple of 10 gold, up to 655350 gold (the value of 10 is arbitrary, of course). [DONE]
- Start a Monster-in-a-box encounter with any encounter pack.
- Start a MiaB encounter with a monster pack between #256 and #511 then grant an item, some gold, a rare item or a summon upon winning the battle.
While the first two are technically already possible (as a small test, I changed the JSR branching of getting an item to the one that gives a summon and it worked. The message box was wrong, but I got the summon.), the other ones require an additional byte in the chest information. The Treasure Chest data is composed of 5 bytes starting at $ED/8634.
- Coordinates of the chest (X, Y)
- Encoding byte and bit (byte is added to $1E40)
- Treasure type (0x08: empty, 0x20: Monster-in-a-box, 0x40: Item, 0x80: Gold)
- Treasure value (MiaB encounter pack, item type or gold quantity)
Here is the beginning of the subroutine:
Code:
C0/4C06: C220 REP #$20 (16 bit accum./memory)
C0/4C08: BF3886ED LDA $ED8638,X (Load chest item index / gold / encounter # in A)
C0/4C0C: 851A STA $1A (Store A in $1A)
C0/4C0E: BF3686ED LDA $ED8636,X (Load relative encoding byte/bit and treasure type in A)
C0/4C12: 851E STA $1E (Store A in $1E)
C0/4C14: 290700 AND #$0007 (A = A AND #$0007 => Load encoding bit in A)
C0/4C17: AA TAX (Transfer A to X)
C0/4C18: A51E LDA $1E (Load relative encoding byte/bit and treasure type in A from $1E)
C0/4C1A: 29FF01 AND #$01FF (A = A AND #$01FF => Load encoding byte/bit in A)
C0/4C1D: 4A LSR A
C0/4C1E: 4A LSR A
C0/4C1F: 4A LSR A (Getting rid of encoding bit, keeping relative encoding byte in A)
C0/4C20: A8 TAY (Transfer A to Y)
The thing is that the X register contains the relative value of the first byte of the current treasure chest. It is 5 times larger than the chest "ID". So, it gave me an idea. There is absolutely no need the keep the encoding byte/bit as hard-coded information if it can be found by a simple division (I know it costs many cycles, so it is not that simple! ). In that way, you ensure that every chest has its own encoding bit.
Personally, I never liked the whole "not opening treasure chests so they offer more powerful items in the future" thing. I want to open ALL the chests. Additionally, it frees a whole byte.
Code:
C0/4C06 C2 20 -- -- REP #$20
C0/4C08 20 13 D6 -- JSR $D613 (Jumping to subroutine at $D613)
C0/4C0B BF 36 86 ED LDA $ED8636,X (Loading encoding byte/bit and treasure chest in A)
C0/4C0F 85 1E -- -- STA $1E (Storing A in $1E, item type / gold quantity / encounter number is in $1F, just as before)
C0/4C11 AD 16 42 -- LDA $4216 (Loading quotient in A)
C0/4C14 29 07 00 -- AND #$0007 (A = A AND 0x0007, keeping encoding bit)
C0/4C17 AA -- -- -- TAX (Transfer A to X)
C0/4C18 AD 16 42 -- LDA $4216 (Reloading quotient in A)
C0/4C1B 4A -- -- -- LSR A
C0/4C1C 4A -- -- -- LSR A
C0/4C1D 4A -- -- -- LSR A (Shifting data three bits to the right, keeping only encoding byte in A)
C0/4C1E A8 -- -- -- TAY (Transfer A to Y)
C0/4C1F EA -- -- -- NOP
C0/4C20 EA -- -- -- NOP (Chilling for 4 cycles, then resuming original subroutine)
C0/D613 8E 04 42 -- STX $4204 (Storing X as dividend)
C0/D616 A9 00 05 -- LDA #$0500 (Loading 0x0500 in A)
C0/D619 8D 06 42 -- STA $4206 (Storing 0x05 as divisor)
C0/D61C BF 38 86 ED LDA $ED8638,X (Putting item type / gold quantity / encounter number in A)
C0/D620 85 1A -- -- STA $1A (Storing A in $1A)
C0/D622 60 -- -- -- RTS
Please note this is only a proof-of-concept. I know the code is really not optimized. I just wanted to move as less stuff as possible around. Now, this works. The chest is obtainable once and no longer available after that. But when the map is reloaded (after opening/closing the menu, finishing a battle or leaving the map and coming back), the chest is closed once more (its content can't be retaken though). My first question would be: Where in the map generation code the subroutine that decides if a chest is open or closed? Thanks in advance!
The treasure chest bits are at $7E1E40-$7E1E7F. The code is inevitably in bank $C0. Make a search for "$1E40", $C01614, $C04C24 (chest opening) and $C0BB14 (RAM / SRAM init) will come up. You answer should be $C01614, but I have not really looked closely at that code.
BTW, this is a cool work. Nothing stops you once you found all relevant code to expand a data structure with any mean of programming, python or C family languages. A quick script can automate the data shuffling and the rest is regular ASM work and data relocation in some other ROM spot (expanding usually mean it won't fit in original ROM place unless you cut back on an amount of entries).
BTW, this is a cool work. Nothing stops you once you found all relevant code to expand a data structure with any mean of programming, python or C family languages. A quick script can automate the data shuffling and the rest is regular ASM work and data relocation in some other ROM spot (expanding usually mean it won't fit in original ROM place unless you cut back on an amount of entries).
07-01-2018, 11:20 PM
(This post was last modified: 07-01-2018, 11:52 PM by NPCnextdoor.)
Found it! Thanks Madsiur!
And yes, I already made a script to randomize the treasure chests. It will only be a matter of fooling around with the function to write information back to the ROM.
Code:
Checking chest coordinates and event bit
15F1 20 13 D6 -- JSR $D613 Preparing the division
15F4 BF 34 86 ED LDA $ED8634,X Load (X, Y) coordinates of chest into A
15F8 85 2A -- -- STA $2A Store X coordinate in $2A, Y coordinate into $2B
15FA DA -- -- -- PHX Push X to stack
15FB EA EA EA -- NOP x 3 Being haunted by the ghosts of your past for 6 cycles.
15FE 20 1D D6 -- JSR $D61D
1601 4C 11 16 -- JMP $1611
1604 FF FF FF FF
1608 FF FF FF FF
160C FF FF FF FF
1610 FF -- -- --
Opening a treasure chest
C0/4C06 C2 20 -- -- REP #$20
C0/4C08 20 13 D6 -- JSR $D613
C0/4C0B BF 38 86 ED LDA $ED8638,X
C0/4C0F 85 1A -- -- STA $1A
C0/4C11 BF 36 86 ED LDA $ED8636,X
C0/4C15 85 1E -- -- STA $1E
C0/4C17 20 1D D6 -- JSR $D61D
C0/4C1A 4C 21 4C -- JMP $4C21
C0/4C1D FF FF FF FF
Division & Retrieving event byte/bit
C0/D613 8E 04 42 -- STX $4204 Store X as dividend
C0/D616 A9 00 05 -- LDA #$0500
C0/D619 8D 06 42 -- STA $4206 Store 0x05 as divisor
C0/D61C 60 -- -- -- RTS
C0/D61D AD 16 42 -- LDA $4216 Load quotient into A
C0/D620 29 07 00 -- AND #$0007 A = A AND 0x07, keeping encoding bit
C0/D623 AA -- -- -- TAX Transfer A to X
C0/D624 AD 16 42 -- LDA $4216 Reload quotient into A
C0/D627 4A -- -- -- LSR A
C0/D628 4A -- -- -- LSR A
C0/D629 4A -- -- -- LSR A Shift 3 bits to the right, keeping encoding byte
C0/D62A A8 -- -- -- TAY Transfer A to Y
C0/D62B 60 -- -- -- RTS
And yes, I already made a script to randomize the treasure chests. It will only be a matter of fooling around with the function to write information back to the ROM.
(07-01-2018, 11:20 PM)NPCnextdoor Wrote: And yes, I already made a script to randomize the treasure chests. It will only be a matter of fooling around with the function to write information back to the ROM.
I was more referring to expand a structure in a general way, like from 5 bytes to 6 bytes in this case (though it is not needed in this hack). Biggest obstacles when this is wanted is that people don't have the tools to reorganize and edit the data. This is what is cool about being able to code in higher languages :)
Is it a total chest randomization or are you keeping some structure for increase in loot types? As an example, preventing the paladin shield or top tier treasure to be found at beginning?
07-02-2018, 10:52 AM
(This post was last modified: 07-02-2018, 04:31 PM by NPCnextdoor.)
I thought about using 6 bytes if I was not going to be successful in eliminating the need for the event byte. But thanks to you, I found a way to do it!
Regarding chest randomization, I have three options in mind:
I also want to randomize secret item locations, e.g. the clock, the armor, the fireplace and wherever Arvis checks when he takes the Slave Crown in his house could potentially hold an item. On top of that, since the game currently has a little more than 270 chests, there is still place for at least 200 other secret items. I want to see how many more of these could be added before it starts to feel ridiculous.
Regarding chest randomization, I have three options in mind:
- Shuffling every chest among themselves. (Kind of boring)
- Randomizing chest contents but offering items or gold from the same tier as the original item, as you mentioned. (Mirage Vest in the Cave to the Sealed Gate, perhaps?)
- Anything goes on! (My personal favorite)
I also want to randomize secret item locations, e.g. the clock, the armor, the fireplace and wherever Arvis checks when he takes the Slave Crown in his house could potentially hold an item. On top of that, since the game currently has a little more than 270 chests, there is still place for at least 200 other secret items. I want to see how many more of these could be added before it starts to feel ridiculous.
07-14-2018, 11:42 PM
(This post was last modified: 07-15-2018, 12:33 AM by NPCnextdoor.)
Update: I started the expansion with gold treasure chests.
When I asked about how Final Fantasy VI handled 16 by 8-bit multiplication, Assassin wrote to look at C2/47B7. I must admit, I did not fully understand how it worked. Also, I noticed that the division process I used to get the chest ID did not work properly. By the time I arrive at the Returner's Hideout, most of the chests and pots are already empty. So, I decided to wrote my own subroutines instead of relying of the special registers that may or may not be used by something else. I have yet to write the long division, but here is my multiplication subroutine. (I love to say subroutine, I feel like I'm in Star Trek! )
I first experimented with hardcoding the multiplier, 10 in this case.
So, of course, I wanted to automate the process for any 16-bit value so that I can reuse this subroutine. Here is what I got:
I still don't understand why DEC $1C seems to be a 16-bit operation but INC $24 is only a 8-bit one. Does JSR has something to do with it? Feel free to propose code optimization. This is the result of moving pieces of code back and forth, hence the messy look. Also, is it possible to insert tables? I spend so much time trying to get the formatting right, but the tabulation in the editor doesn't act the same once it's published!
When I asked about how Final Fantasy VI handled 16 by 8-bit multiplication, Assassin wrote to look at C2/47B7. I must admit, I did not fully understand how it worked. Also, I noticed that the division process I used to get the chest ID did not work properly. By the time I arrive at the Returner's Hideout, most of the chests and pots are already empty. So, I decided to wrote my own subroutines instead of relying of the special registers that may or may not be used by something else. I have yet to write the long division, but here is my multiplication subroutine. (I love to say subroutine, I feel like I'm in Star Trek! )
I first experimented with hardcoding the multiplier, 10 in this case.
Code:
C2 21 REP #$21 16-bit mode + clear Carry bit
64 24 STZ $24 Store zeroes in bytes 4 ($24) and 3 ($25)
A5 1A LDA $1A Load multiplicand (base GP value) in A
0A -- ASL -- Left-shift A, therefore doubling base GP value
90 02 BCC 2ND_MULT If the multiplication doesn't set the Carry bit (or, does the multiplication of the first two-bytes smear into byte 3?), skip next operation
E6 25 INC $25 Add one to byte 3
06 25 ASL $25 -=2ND_MULT=- Left-shift byte 3 (No left-shift of byte 4 is made, because there is no need when multiplying a 16-bit value by 10.)
0A -- ASL -- Left-shift A, multiplication factor is now 4.
90 02 BCC ADDITION If the multiplication doesn't set the Carry bit, skip next operation
E6 25 INC $25 Add one to byte 3
C2 01 REP #$01 -=ADDITION=- Clear Carry bit, making sure it doesn't mess the next operation, an addition
65 1A ADC $1A Add base GP value to A, multiplication factor is now 5.
90 02 BCC 3RD_MULT If the addition doesn't set the Carry bit, skip next operation
E6 25 INC $25 Add one to byte 3
06 25 ASL $25 -=3RD_MULT=- Left-shift byte 3
0A -- ASL -- Left-shift A, multiplication factor is now 10.
90 02 BCC END If multiplication doesn't set the Carry bit, skip next operation
E6 25 INC $25 Add one to byte 3
85 22 STA $22 Store A back into $22
60 -- RTS -- -=END=-
So, of course, I wanted to automate the process for any 16-bit value so that I can reuse this subroutine. Here is what I got:
Code:
C0/D700 C2 31 -- REP #$31 16-bit accumulator mode, 16-bit X and Y registers and clear Carry bit
C0/D702 64 22 -- STZ $22 Store zeroes in byte 2 and 1
C0/D704 64 24 -- STZ $24 Store zeroes in byte 4 and 3
C0/D706 A4 1A -- LDY $1A Load base GP value (multiplicand) into Y
C0/D708 A6 1C -- LDX $1C Load multiplier in X
C0/D70A F0 37 -- BEQ END If X = 0, skip to the end
C0/D70C A9 10 00 LDA #$000F Load (15) into A, the maximum amount of left-shifts possible of a 16-bit value minus 1 before is it guaranteed to become 0
C0/D70F 85 1C -- STA $1C Store A into $1C
C0/D711 8A -- -- TXA -- Transfer X to A
C0/D712 0A -- -- ASL -- -=FIRST_LOOP_START=- Left-shift A
C0/D713 B0 04 -- BCS ADD_ONCE If the Carry bit is set (or, was the previous bit we lost due to left-shifting an 1?), continue into the subroutine
C0/D715 C6 1C -- DEC $1C Decrement $1C, the number of left-shifts remaining.
C0/D717 80 F9 -- BRA FIRST_LOOP_START Go back to the start of the loop.
C0/D719 C6 1C -- DEC $1C -=ADD_ONCE=- Decrement $1C.
C0/D71B 84 22 -- STY $22 Store base GP value into $22, the multiplication factor is now 1.
C0/D71D AA -- -- TAX -- Transfer A to X
C0/D71E A5 1C -- LDA $1C -=SECOND_LOOP_START=- Load remaining number of left-shifts into A, setting or clearing the Zero bit
C0/D720 F0 21 -- BEQ END If A = 0, skip to the end
###MULTIPLICATION BY 2##
C0/D722 A5 24 -- LDA $24 Load bytes 4 and 3 (high bytes) into A
C0/D724 0A -- -- ASL -- Left-shift the high bytes
C0/D725 85 24 -- STA $24 Store the high bytes back into $24
C0/D727 A5 22 -- LDA $22 Load bytes 2 and 1 (low bytes) into A
C0/D729 0A -- -- ASL -- Left-shift the low bytes
C0/D72A 85 22 -- STA $22 Store the low bytes back into $22
########################
C0/D72C B0 16 -- BCS INC_HIGH_BYTE1 If the Carry bit is set (or was the high bit of the low bytes before the left-shifting an 1?), increment the high bytes
C0/D72E 8A -- -- TXA -- -=LSHIFT=- Transfer X to A
C0/D72F 0A -- -- ASL -- Left-shift the multiplier, doubling the multiplication factor
C0/D730 B0 03 -- BCS ADD_MULTIPLICAND If the Carry bit is set, add the multiplicand, incrementing the multiplication factor by 1
C0/D732 AA -- -- TAX -- Transfer A back to X
C0/D733 80 0A -- BRA SECOND_LOOP_END Skip the addition of the multiplicand
###ADDING THE MULTIPLICAND###
C0/D735 AA -- -- TAX -- -=ADD MULTIPLICAND=- Transfer A back to X
C0/D736 98 -- -- TYA -- Transfer the base GP value to A
C0/D737 C2 01 -- REP #$01 Clear the Carry bit
C0/D739 65 22 -- ADC $22 Add to the base GP value the current GP value
C0/D73B 85 22 -- STA $22 Store the sum back into $22
C0/D73D B0 0A -- BCS INC_HIGH_BYTE2 If the Carry bit is set, increment the high bytes
#############################
C0/D73F C6 1C -- DEC $1C -=SECOND_LOOP_END=- Decrement the number of remaining left-shifts
C0/D741 80 DB -- BRA SECOND_LOOP_START Go back to the start of the loop
C0/D743 60 -- -- RTS -- -=END=-
C0/D744 20 4E D7 JSR INC_HIGH_BYTE_START -=INC_HIGH_BYTE1=- Jump to the "Increment the high bytes subroutine"
C0/D747 80 E5 -- BRA LSHIFT Goes back to where we were in the code
C0/D749 20 4E D7 JSR INC_HIGH_BYTE_START -=INC_HIGH_BYTE2=- Jump to the "Increment the high bytes subroutine"
C0/D74C 80 F1 -- BRA SECOND_LOOP_END Goes back to where we were in the code
C0/D74E E6 25 -- INC $25 -=INC_HIGH_BYTE_START=- Increment byte 3
C0/D750 90 02 -- BCC INC_HIGH_BYTE_END If the Carry bit is clear, skip the next operation
C0/D752 E6 24 -- INC $24 Increment byte 4
C0/D754 60 -- -- RTS -- -=INC_HIGH_BYTE_END=-
I still don't understand why DEC $1C seems to be a 16-bit operation but INC $24 is only a 8-bit one. Does JSR has something to do with it? Feel free to propose code optimization. This is the result of moving pieces of code back and forth, hence the messy look. Also, is it possible to insert tables? I spend so much time trying to get the formatting right, but the tabulation in the editor doesn't act the same once it's published!
C0/D722:
for accuracy: switch the order of the $22 and $24 operations, so you can then rotate the Carry from the low bytes into the high.
for speed and size: also, there's no need to load them into A.
----
C0/D737: unless you plan to alter other CPU flags here, "CLC" is smaller.
----
for a similar but smaller way to do your non-special-register multiplication, look at C1/1867 and C1/18F4. here's the former since Slick is down for an eternity now:
for accuracy: switch the order of the $22 and $24 operations, so you can then rotate the Carry from the low bytes into the high.
for speed and size: also, there's no need to load them into A.
Code:
ASL $22
ROL $24
----
C0/D737: unless you plan to alter other CPU flags here, "CLC" is smaller.
----
for a similar but smaller way to do your non-special-register multiplication, look at C1/1867 and C1/18F4. here's the former since Slick is down for an eternity now:
Code:
(Multiply 16-bit $2E by 8-bit $2C, and put result in 32-bit $30-$33.
Wow, this and C1/18F4 are total clones.)
C1/1867: 64 30 STZ $30
C1/1869: 64 32 STZ $32
C1/186B: 64 34 STZ $34
C1/186D: A2 08 00 LDX #$0008
C1/1870: 46 2C LSR $2C
C1/1872: 90 0D BCC $1881
C1/1874: A5 30 LDA $30
C1/1876: 18 CLC
C1/1877: 65 2E ADC $2E
C1/1879: 85 30 STA $30
C1/187B: A5 32 LDA $32
C1/187D: 65 34 ADC $34
C1/187F: 85 32 STA $32
C1/1881: 06 2E ASL $2E
C1/1883: 26 34 ROL $34
C1/1885: CA DEX
C1/1886: D0 E8 BNE $1870
C1/1888: 60 RTS
07-15-2018, 07:56 AM
(This post was last modified: 07-15-2018, 08:03 AM by NPCnextdoor.)
Thanks!
I made the modifications. However, with
ASL $22
ROL $24
(And also removing the BCS INC_HIGH_BYTE1 operation)
It didn't work. The result was way too low. So I thought that perhaps ROL was 8-bit some how, even though the M flag is 0 (16-bit mode). So, I did
ASL $22
ROL $25
ROL $24
And the result was way too high. However,
ASL $22
ROL $25
Works perfectly! And I don't understand why.
I did try first this approach (well, when I learnt eventually about the ROL opcode ) and because of this weird behavior, I took another route that I knew would work until I understood it better. I concluded that if some operations directly on the data were somehow always 8-bit, I would prefer to put the values in the A, X or Y registers first and put the results back.
I made the modifications. However, with
ASL $22
ROL $24
(And also removing the BCS INC_HIGH_BYTE1 operation)
It didn't work. The result was way too low. So I thought that perhaps ROL was 8-bit some how, even though the M flag is 0 (16-bit mode). So, I did
ASL $22
ROL $25
ROL $24
And the result was way too high. However,
ASL $22
ROL $25
Works perfectly! And I don't understand why.
I did try first this approach (well, when I learnt eventually about the ROL opcode ) and because of this weird behavior, I took another route that I knew would work until I understood it better. I concluded that if some operations directly on the data were somehow always 8-bit, I would prefer to put the values in the A, X or Y registers first and put the results back.
07-15-2018, 05:44 PM
Quote:I still don't understand why DEC $1C seems to be a 16-bit operation but INC $24 is only a 8-bit one. Does JSR has something to do with it?
what makes you think this? i never see your code change register size mid-function. so i think it's all 16-bits.
------
re C0/D744:
1) branching to this, and then returning by branching out to different places depending on the caller, is a mess. let's get rid of that. so instead of "BCS function", then a horrible mess inside "function", we'd do "BCC no_call_function / JSR function / no_call_function:". and inside of "function", we'd always return with an "RTS"; no spaghetti code branching.
2) you're in 16-bit mode, so there's no reason to increment both $24 and $25. just do $24. and chuck the "BCC", because the callers already limited when we'd reach here by usage of "BCS".
3) there's no point for a function anymore, anyway. just do the necessary incrementing inline. at C0/D737, have this:
Code:
CLC
ADC $22 ; Add to the base GP value the current GP value
STA $22 ; Store the sum back into $22
BCC no_overflow
INC $24 ; propagate any carry into the top word
no_overflow:
DEC $1C
[...]
this change #3 supersedes the suggestions in #1 and #2. but i kept those anyway so you can see the motivation and process for refining or fixing code.
07-18-2018, 05:45 AM
(This post was last modified: 07-18-2018, 05:51 AM by NPCnextdoor.)
(07-15-2018, 05:44 PM)assassin Wrote:Quote:I still don't understand why DEC $1C seems to be a 16-bit operation but INC $24 is only a 8-bit one. Does JSR has something to do with it?
what makes you think this? i never see your code change register size mid-function. so i think it's all 16-bits.
Nevermind, I messed up the endianness in my head so I uselessly added operations to put $25 into $24 later in the code... and I just forgot about it a week later!
I applied your suggestions. They work perfectly and the code is now 24 bytes smaller!
« Next Oldest | Next Newest »
Users browsing this thread: 2 Guest(s)