News: 11 March 2016 - Forum Rules
Current Moderators - DarkSol, KingMike, MathOnNapkins, Azkadellia, Danke

Author Topic: Dragon Warrior 2 Disassembly  (Read 5435 times)

Choppasmith

  • Full Member
  • ***
  • Posts: 132
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #20 on: July 16, 2019, 02:43:45 pm »
Oh I see now. It loads the amount of groups into memory and does string 0 (# Monster,) first and then when it reaches the last monster (or if there's just one) does "And Monster appeared"

I was comparing how it's handled in the Mobile version which is basically

Single Enemy: A(n) (monster) appears
Single/First Group: Some (monsters) appear
Second group: And a/some (monster/s)
Third group: AND a/some (monster/s)

I was wondering how close I could get it to that, even skipping the all caps AND and changing the number words (over One) to "Some", but the code there looks packed and I don't think there's a way to do it without it looking off or wonky.
« Last Edit: July 16, 2019, 02:59:54 pm by Choppasmith »

abw

  • Sr. Member
  • ****
  • Posts: 289
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #21 on: July 16, 2019, 08:07:45 pm »
Oh I see now. It loads the amount of groups into memory and does string 0 (# Monster,) first and then when it reaches the last monster (or if there's just one) does "And Monster appeared"
Not quite - if there's only 1 group, there's no "And".

I was comparing how it's handled in the Mobile version which is basically

Single Enemy: A(n) (monster) appears
Single/First Group: Some (monsters) appear
Second group: And a/some (monster/s)
Third group: AND a/some (monster/s)

I was wondering how close I could get it to that, even skipping the all caps AND and changing the number words (over One) to "Some", but the code there looks packed and I don't think there's a way to do it without it looking off or wonky.
Hmm, let's see...
  • "One" vs. "Some": this is easy, you can just change the text for each of "Two" through "Eight" to say "Some" instead.
  • "A" vs. "An": the code for printing the cardinal number starts at $02:$BEF0, there's a chunk of empty space after its data, and the monster name has already been loaded into $6119 by the time the code for printing the cardinal number runs, so you've got options there. You could actually combine both of these changes into some code like this (completely untested; adjust the vowel checks based on your monster names if necessary):
Code: [Select]
table dw2.tbl,rtl

LDY $8F    ; number of monsters in group
DEY        ; count from 0 instead of 1
BEQ one     ; 0 => only one monster => handle "A" vs "An"
LDX #$00    ; read index
LDY #$00    ; write index
loop:
LDA some,X ; Monster Counts text
STA $60F1,Y ; start of text variable buffer
INY       
INX       
CMP #$FA    ; [end-FA]
BNE loop  ; if not end token, keep copying
done:
SEC        ; SEC to trigger read of [end-FA]-terminated string from $60F1, CLC to use A
RTS       
some:
.db "Some[end-FA]" ; or spell it out as $36,$18,$16,$0E,$FA if you don't want to bother with a table
one:
; at this point we know Y = 0
LDA #$24 ; "A"
STA $60F1,Y ; start of text variable buffer
LDA $6119 ; first letter of monster name
CMP #$24 ; "A"
BEQ an
CMP #$28 ; "E"
BEQ an
CMP #$2C ; "I"
BEQ an
CMP #$32 ; "O"
BEQ an
CMP #$38 ; "U"
BNE no_change
an:
LDA #$17 ; "n"
INY
STA $60F1,Y ; start of text variable buffer
no_change:
LDA #$FA ; [end-FA]
STA $60F1,Y ; start of text variable buffer
BNE done
  • "appear" vs. "appears": well, there's a reason DW2 used "appeared" :P. But hey, didn't we fix that [(s)] control code earlier? This sounds like the perfect place to put that to good use ;D. On the other hand, the original battle text is almost exclusively written in past tense, so introducing a present tense here might seem odd :-\.
  • Having different text for each group: strings #$55 and #$56 are empty and probably unused, so if you initialized $A8 to #$52 and let the code keep incrementing $A8 for every group added to battle like it already is, you could replace
Code: [Select]
0x011530|$04:$9520:A6 A8    LDX $A8    ; total # of non-empty groups processed
0x011532|$04:$9522:CA      DEX       
0x011533|$04:$9523:D0 04    BNE $9529  ; branch if we processed more than one non-empty group
0x011535|$04:$9525:A9 54    LDA #$54    ; String ID #$0054: [cardinal #] [monster(s)][line]appeared.[end-FC]
0x011537|$04:$9527:D0 06    BNE $952F 
; control flow target (from $9523)
0x011539|$04:$9529:A9 53    LDA #$53    ; String ID #$0053: And [cardinal #] [monster(s)][line]appeared.[end-FC]
0x01153B|$04:$952B:D0 02    BNE $952F 
    with
Code: [Select]
LDA $A8
BNE $952F
    which gives you 9 bytes to play with; that's probably enough. This way, you'd still have string #$01 for battles with a single group, and then for multi-group battles you'd get string #$53 for group #1, #$54 for group #2, #$55 for group #3, and #$56 for group #4. Update the text of those strings as desired.
So yes, it will take a little bit of work, but nothing too drastic, and I think that lets you match the mobile version perfectly (except for whatever you do with the text for group #4 ["AND THEN, as if that wasn't enough, a/some (monster/s)"]).

Choppasmith

  • Full Member
  • ***
  • Posts: 132
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #22 on: August 05, 2019, 02:03:27 am »
Sorry for the wait. In the middle of summer my computer room/office can become UNBEARABLE stuffy making it harder to find time for this stuff. There's a lot to unpack here, but lets look at this Monster appearing rewrite you graciously offered.

LDA $A8 ; total # of non-empty groups processed
BNE $952F ; LDA=Text String code?


Am I right in assuming that because we changed the first line from LDX to LDA it's just a mater of adding lines until it's done counting? Like this

LDA #$53 ; [monster] appears
LDA #$54 ; And [monster]
LDA #$55 ; AND [monster]
LDA #$55 ; AND [monster]

Note There's nothing unique in the mobile script for a 4th group, I would assume (if it's even possible) it just uses "AND [monster]" again

I'm not supposed to add a branch for each and every string am I because otherwise it'd be impossible to keep it under 9 bytes.

Quote
On the other hand, the original battle text is almost exclusively written in past tense, so introducing a present tense here might seem odd :-\.

I've noticed this in my rewrite. Modern DQ seems to go for present tense and my script is updated accordingly.

abw

  • Sr. Member
  • ****
  • Posts: 289
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #23 on: August 05, 2019, 11:55:58 am »
Sorry for the wait. In the middle of summer my computer room/office can become UNBEARABLE stuffy making it harder to find time for this stuff.
Eww :P. That's probably not too good for your computer either!

There's a lot to unpack here, but lets look at this Monster appearing rewrite you graciously offered.
I think you missed the part about initializing $A8 to #$52 and letting the existing code continue to increment $A8 for every non-empty group; if you do that, then for multi-group battles, $A8 should have the value you want at the time you want it, so you can just use it as the string ID directly. No need for CMP, LDA, or a large number of branches. I'd start by making a little ASM file that reproduces the existing code from $04:$94FC - $04:$9547, and then start editing by changing
Code: [Select]
LDA #$00
STA $A8    ; initialize total # of non-empty groups processed
STA $A7    ; initialize total # of empty groups processed
to
Code: [Select]
LDA #$52
STA $A8    ; initialize string ID for # of non-empty groups processed
LDA #$00
STA $A7    ; initialize total # of empty groups processed

I've noticed this in my rewrite. Modern DQ seems to go for present tense and my script is updated accordingly.
Ah, alright, in that case, no worries!

Chicken Knife

  • Sr. Member
  • ****
  • Posts: 296
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #24 on: August 05, 2019, 02:33:53 pm »
@abw,

I was quickly reading through your disassembly and looking for the section that handles how character sprites are displayed. I have nothing left to do with DW2 but I figure if I can learn how it all works based on your disassembly of 2, it would probably work almost the same for 1 and 3 where I do have work to do. It's possible you didn't spend time detailing that since it was of minimal relevance, and that's fine.

abw

  • Sr. Member
  • ****
  • Posts: 289
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #25 on: August 05, 2019, 03:32:48 pm »
At this point it's not really a question of relevance, just that there's a lot of code to go through and I haven't wandered into much of the code for displaying sprites. If you want to start digging into it, I can give you a few places to start from, like where the per-map pointers to their data are ($02:$A539), where that data gets stored (starting at $053C), and where their graphics are ($00:$A043). Scripted motion (e.g. everything in the prologue) happens at $0F:$CCF1, and the data for the movements starts at $07:$8010. Monster graphic/palette pointers start at $04:$90FC.

As for how close DW2's sprite handling will be to DW1 or DW3, well, who knows? Games can be crazy :P.

Choppasmith

  • Full Member
  • ***
  • Posts: 132
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #26 on: August 10, 2019, 06:33:45 pm »
Eww :P. That's probably not too good for your computer either!
I think you missed the part about initializing $A8 to #$52 and letting the existing code continue to increment $A8 for every non-empty group; if you do that, then for multi-group battles, $A8 should have the value you want at the time you want it, so you can just use it as the string ID directly. No need for CMP, LDA, or a large number of branches. I'd start by making a little ASM file that reproduces the existing code from $04:$94FC - $04:$9547, and then start editing by changing
Code: [Select]
LDA #$00
STA $A8    ; initialize total # of non-empty groups processed
STA $A7    ; initialize total # of empty groups processed
to
Code: [Select]
LDA #$52
STA $A8    ; initialize string ID for # of non-empty groups processed
LDA #$00
STA $A7    ; initialize total # of empty groups processed
Ah, alright, in that case, no worries!

So I'm a little stumped. Does that mean, if I had something like this:

Code: [Select]
LDA #$52   
STA $A8
LDA #$00   
STA $A7   
; control flow target (from $9538)
index:
JSR $9EEE  ; given an index (in A) into the array of structures at $0663, set $B5-$B6 to the address of the corresponding item inside that structure
LDY #$09   
LDA ($B5),Y
STA $8F   
BEQ $9532 
INC $A8   
LDY #$00   
LDA ($B5),Y
STA $0161  ; current monster ID
LDX #$00   
JSR $9CD6  ; write monster name in A (+ monster number within its group in X, if > 0) to $6119
DEC $60D8 
BNE first 
LDA $A8           
BNE flow 

flow:
JSR $9CCA

first:
LDA $53 ;"[cardinal #] [monster(s)] appear!"

This would just repeat until all the monsters/groups are counted?

Now I know the "first:" should have something that would just load A, but I'm not quite sure of the right code. A being the right string which starts at 52 (so that 1 enemy group loads string 53, 2 loads 54 and so on.

abw

  • Sr. Member
  • ****
  • Posts: 289
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #27 on: August 11, 2019, 11:52:28 am »
The original code cares about 2 things when deciding which per-group string to use: 1) whether the group being printed is the last non-empty group or not, and 2) if the current group is the last non-empty group, whether there was more than one non-empty group or not. If the current group isn't the last non-empty group, it uses string ID #$0001, otherwise it uses #$0053 if there are multiple non-empty groups or #$0054 if there's only 1 non-empty group.

For 1), if you search the code for $60D8, you'll see that it gets initialized to #$00 ($04:$94E7), gets incremented for every non-empty group ($04:$94F4), after which point it tells you the total number of non-empty groups, and then decremented every time a non-empty group gets processed ($04:$951B), so at that point it tells you how many non-empty groups are remaining, and thus whether the group being printed is the last non-empty group or not.

2) works similarly but in reverse: $A8 gets initialized to #$00 ($04:$94FE) and then incremented every time a non-empty group gets processed ($04:$950D), so it tells you which non-empty group is currently being processed and thus whether there was more than one non-empty group or not.

But in your case, you want to change that logic around to care about 1) whether there is more than one non-empty group or not, and 2) if there is more than one non-empty group, which non-empty group is currently being processed.

For 1), you can just take out the DEC $60D8 and then $60D8 will tell you whether there is more than one non-empty group or not.

For 2), $A8 already tells you this.

Does that help any?

Spoiler:
If you're still stuck, here's the version I whipped together:
Code: [Select]
norom

org $1150C
base $94FC

LDA #$52
STA $A8 ; initialize per-group string ID to use in multi-group battles
LDA #$00
STA $A7 ; initialize total # of empty groups processed
process_group_string:
JSR $9EEE ; given an index (in A) into the array of structures at $0663, set $B5-$B6 to the address of the corresponding item inside that structure
LDY #$09
LDA ($B5),Y
STA $8F ; number of monsters in this group
BEQ done_display_string ; 0 monsters => no string to print
INC $A8 ; string ID to use for this group
LDY #$00
LDA ($B5),Y
STA $0161 ; current monster ID
LDX #$00
JSR $9CD6 ; write monster name in A (+ monster number within its group in X, if > 0) to $6119
LDA $60D8 ; total number of non-empty enemy groups in the current battle
CMP #$01
BEQ display_string ; if there's only 1 group, then use string ID #$0001 (change the text to be appropriate)
LDA $A8 ; otherwise use the per-group string ID (also change those texts to be appropriate)
display_string:
JSR $9CCA ; for A < #$60, display string ID specified by A; for A >= #$60, display string ID specified by A + #$A0
done_display_string:
INC $A7
LDA $A7
CMP #$04
BCC process_group_string
LDA $60D8 ; total number of non-empty enemy groups in the current battle
BNE next_section
LDA #$02 ; String ID #$0002: But it wasn't real.[end-FC]
JSR $9CCA ; for A < #$60, display string ID specified by A; for A >= #$60, display string ID specified by A + #$A0
LDA #$FD
STA $98 ; outcome of last fight?
JMP $9685
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP

next_section:

Choppasmith

  • Full Member
  • ***
  • Posts: 132
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #28 on: August 24, 2019, 12:12:43 am »
The original code cares about 2 things when deciding which per-group string to use: 1) whether the group being printed is the last non-empty group or not, and 2) if the current group is the last non-empty group, whether there was more than one non-empty group or not. If the current group isn't the last non-empty group, it uses string ID #$0001, otherwise it uses #$0053 if there are multiple non-empty groups or #$0054 if there's only 1 non-empty group.

For 1), if you search the code for $60D8, you'll see that it gets initialized to #$00 ($04:$94E7), gets incremented for every non-empty group ($04:$94F4), after which point it tells you the total number of non-empty groups, and then decremented every time a non-empty group gets processed ($04:$951B), so at that point it tells you how many non-empty groups are remaining, and thus whether the group being printed is the last non-empty group or not.

2) works similarly but in reverse: $A8 gets initialized to #$00 ($04:$94FE) and then incremented every time a non-empty group gets processed ($04:$950D), so it tells you which non-empty group is currently being processed and thus whether there was more than one non-empty group or not.

But in your case, you want to change that logic around to care about 1) whether there is more than one non-empty group or not, and 2) if there is more than one non-empty group, which non-empty group is currently being processed.

For 1), you can just take out the DEC $60D8 and then $60D8 will tell you whether there is more than one non-empty group or not.

For 2), $A8 already tells you this.

Does that help any?

Spoiler:
If you're still stuck, here's the version I whipped together:
Code: [Select]
norom

org $1150C
base $94FC

LDA #$52
STA $A8 ; initialize per-group string ID to use in multi-group battles
LDA #$00
STA $A7 ; initialize total # of empty groups processed
process_group_string:
JSR $9EEE ; given an index (in A) into the array of structures at $0663, set $B5-$B6 to the address of the corresponding item inside that structure
LDY #$09
LDA ($B5),Y
STA $8F ; number of monsters in this group
BEQ done_display_string ; 0 monsters => no string to print
INC $A8 ; string ID to use for this group
LDY #$00
LDA ($B5),Y
STA $0161 ; current monster ID
LDX #$00
JSR $9CD6 ; write monster name in A (+ monster number within its group in X, if > 0) to $6119
LDA $60D8 ; total number of non-empty enemy groups in the current battle
CMP #$01
BEQ display_string ; if there's only 1 group, then use string ID #$0001 (change the text to be appropriate)
LDA $A8 ; otherwise use the per-group string ID (also change those texts to be appropriate)
display_string:
JSR $9CCA ; for A < #$60, display string ID specified by A; for A >= #$60, display string ID specified by A + #$A0
done_display_string:
INC $A7
LDA $A7
CMP #$04
BCC process_group_string
LDA $60D8 ; total number of non-empty enemy groups in the current battle
BNE next_section
LDA #$02 ; String ID #$0002: But it wasn't real.[end-FC]
JSR $9CCA ; for A < #$60, display string ID specified by A; for A >= #$60, display string ID specified by A + #$A0
LDA #$FD
STA $98 ; outcome of last fight?
JMP $9685
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP

next_section:

First of all, weather's cooling down and I have more free time, so I'm confident there'll be no more long breaks between posts like this for a while. Also I've been playing Builders 2 partially for research into possible changes that might make it worth updating the script for (which I've already done in one instance) and it was nice to see a part of the game in the third chapter where there's definitely a section that uses the mobile script 1:1

Secondly, I SWEAR to you abw, I looked at your post for well over 2 hours trying to figure it out on my own, and I just couldn't. I didn't want to just jump to the code all "Yeah yeah thanks" but really figure it out on my own. I'm sorry  :'(

Thankfully your notes help explain what you did. I'm still getting a hang for just what all these commands do too. Most of the time my brain was just like "Duhhhhhhhh" the whole time (which is embarrassing and frustrating, I guess that's what I get for being out of school for so long). I'm not sure I'm cut out for a lot of this ASM stuff but at least I know WAYYYY more than I did when I started this whole thing, so thanks for that.

One question with the code you provided above. I get you added the NOPs as filler so that old code isn't used, should I really add code after "next_section:" ($954A) or do you think it should end there?

abw

  • Sr. Member
  • ****
  • Posts: 289
    • View Profile
Re: Dragon Warrior 2 Disassembly
« Reply #29 on: August 24, 2019, 06:06:24 pm »
Also I've been playing Builders 2 partially for research into possible changes that might make it worth updating the script for (which I've already done in one instance) and it was nice to see a part of the game in the third chapter where there's definitely a section that uses the mobile script 1:1
It's always great when you can mix business with pleasure ;).

Thankfully your notes help explain what you did. I'm still getting a hang for just what all these commands do too. Most of the time my brain was just like "Duhhhhhhhh" the whole time (which is embarrassing and frustrating, I guess that's what I get for being out of school for so long). I'm not sure I'm cut out for a lot of this ASM stuff but at least I know WAYYYY more than I did when I started this whole thing, so thanks for that.
Yeah, there's a saying that goes along with that - if you don't use it, you lose it. Sad but true :(. However, having gained an ability once should make regaining it easier, right?

If you haven't already, another thing you can try is taking a section of code where you know what the code is doing and then walking through it line by line, looking up each opcode you don't fully understand, and figure out exactly how the code is doing what it's doing. Going through that exercise a few times should get you very familiar with the most common opcodes, which should make trying to figure out a new section of code easier (not necessarily easy, depending on the complexity of the code itself, but at least you'll know what the individual steps are doing). Like most other things in life, it takes practice to get really good at.

One question with the code you provided above. I get you added the NOPs as filler so that old code isn't used, should I really add code after "next_section:" ($954A) or do you think it should end there?
The NOPs were filler, but that was more about getting the next_section label at the right place than removing the old code. You could easily omit those and do e.g. "org $1155A" instead, I just liked showing the wall of NOPs to illustrate the number of saved bytes :P. But yes, that is where the ASM patch ends; we're not modifying any of the later code, we just need to identify where it starts so that the assembler knows what number to write when assembling the "BNE next_section" line.