How To Expand the Life Counter in Sonic 2 SMS

Discussion in 'Engineering & Reverse Engineering' started by Ravenfreak, Aug 23, 2011.

  1. Ravenfreak

    Ravenfreak

    Is actually a guy. Tech Member
    2,980
    115
    43
    O'Fallon Mo
    Hacking Sonic Drift, Writer at Sonic Cage Dome
    How to expand the life counter in Sonic 2 sms
    Alright I always hated that you couldn't have more than 9 lives in Sonic 2, so I decided to change that. Note: this is from the wiki version of the disassembly. First look for CapLifeCounterValue in S2.asm. It should look like this:
    LABEL_25AC:
    ld a, ($D292)
    or a
    ret nz
    ld a, (LifeCounter)
    cp $09 ;cap the life display at 9
    jr c, +
    ld a, $09
    +: rlca
    and $1E
    add a, $10
    ld ($DBA9), a
    ret


    Now, delete every line after "ret nz" and add this:
    ld a, (LifeCounter)
    and $0F
    rlca
    and $1E
    add a, $10
    ld ($DBA9), a


    This sets up the last nibble of the life counter. This is what your code should look like at this point:
    ld a, ($D292)
    or a
    ret nz
    ld a, ($D292)
    or a
    ret nz
    ld a, (LifeCounter)
    and $0F
    rlca
    and $1E
    add a, $10
    ld ($DBA9), a


    Alright time for a brief explination, the rlca operand allows the display to be updated, while the other parts of the code updates the display.
    We need to change it to where it can set another nibble for the counter. Right after "ld ($DBA9), a" add this:
    ld a, (LifeCounter)
    and $F0
    rrca
    rrca
    rrca
    and $1E
    add a, $10
    ld ($DBAF), a


    This will allow the "x" on the counter to become the other nibble. That does allow the counter to display the proper numbers, but when Sonic gains
    a life after 9, it still displays the wrong value in the wrong nibble. So we need to change that.
    Find Collision_Monitor_Life in the source code, it should look like this:
    res 1, (hl)
    ld a, ($D298)
    inc a
    ld (LifeCounter), a
    ld a, SFX_ExtraLife
    ld ($DD04), a
    jp LABEL_25AC


    First, bellow res 1,(hl) add these lines:
    ld a, (LifeCounter)
    cp $99
    ret z


    This will allow the game to cap the life counter after 99 lives. Next we need to change these lines:
    ld a, ($D298)
    inc a
    ld (LifeCounter), a


    To this:
    ld a, (LifeCounter)
    add a, $01
    daa (LifeCounter),a

    The "daa" operand is used for counters, which is why the "inc" operand wouldn't work here. After that, save build and enjoy.
    This is what the routine looks like after everything is added:
    CapLifeCounterValue:
    LABEL_25AC:
    ld a, ($D292)
    or a
    ret nz
    ld a, (LifeCounter)
    and $0F
    rlca
    and $1E
    add a, $10
    ld ($DBA9), a
    ld a, (LifeCounter)
    and $F0
    rrca
    rrca
    rrca
    and $1E
    add a, $10
    ld ($DBAF), a
    ret
    Collision_Monitor_Life:
    res 1, (hl)
    ld a, (LifeCounter)
    cp $99
    ret z
    add a, $01
    daa
    ld (LifeCounter), a
    ld a, SFX_ExtraLife
    ld ($DD04), a
    jp LABEL_25AC
     
  2. MarkeyJester

    MarkeyJester

    A D V A N C E Resident Jester
    2,098
    229
    43
    Japan
    Nice work! One thing I am curious of is; does the game lag slightly more with the extra digit? That's the only reason I could think of as to why they never allowed more than 9 lives (the process of converting hex to decimal for that extra digit), but if it doesn't lag then what other reason is there to not have an extra digit?
     
  3. Sik

    Sik

    Sik is pronounced as "seek", not as "sick". Tech Member
    6,719
    0
    0
    being an asshole =P
    I really doubt it adds any lag, given that this code is only run the moment the number of lives changes.

    I suppose it may have to do with the fact that they wanted to put the cross there. Sonic's face eats up 2 sprites, the cross eats up another and the digit another one, making up for 4 sprites on a single line - 50% of the sprite limit for any given line. Making it any larger would leave little room for other sprites to appear in the same lines as the HUD. The same applies to the timer and the rings counter.
     
  4. MarkeyJester

    MarkeyJester

    A D V A N C E Resident Jester
    2,098
    229
    43
    Japan
    Ah I see, thanks very much for clearing that up, it had boggled me for years strangly enough...
     
  5. Ambil

    Ambil

    I want that heinous hedgehog hammered! Member
    893
    0
    0
    Spain
    Hello. I downloaded the Sonic 2 disassembly and edited the code. However, in MEKA, it slows the game and causes problems. From the beginning, it works, but the upper half of the screen blinks in the background cyan colour (see screenshot). I wonder which is the problem. After a few seconds, the game restarts or crashes.

    I would like to point out a mistake in your code. The instruction
    Code (Text):
    1. daa (LifeCounter),a
    does not exist. At the end of the post, you wrote it correctly:
    Code (Text):
    1. daa  
    2. ld (LifeCounter), a
    Also, after
    Code (Text):
    1. ld a, (LifeCounter)
    2. and $F0
    3. rrca
    4. rrca
    5. rrca
    6. and $1E
    7. add a, $10
    8. ld ($DBAF), a
    9.  
    you forgot

    Code (Text):
    1. ret
    Thank you.
     
  6. Glitch

    Glitch

    Tech Member
    164
    3
    18
    I'm only guessing here but it sounds like something in the code is jumping into nothing. Empty space in the rom is filled in with $FF bytes, which the Z80 decodes as a "RST $38". This has the effect of repeatedly calling into the VDP interrupt handler. You might want to place a breakpoint on the code so that you can step through it and see where it's going.
     
  7. Ravenfreak

    Ravenfreak

    Is actually a guy. Tech Member
    2,980
    115
    43
    O'Fallon Mo
    Hacking Sonic Drift, Writer at Sonic Cage Dome
    Oops, I posted this early in the morning and forgot to proof read it. :v: Also I ported the code from Sonic Chaos if that matters. But I didn't test the ROM in MEKA, but in Fusion and Gens+. (My gf doesn't want me to download any more emulators. :\) It works fine in both emulators. Does MEKA handle VRAM differently from Fusion/Gens+ since it has debugging options?
     
  8. nineko

    nineko

    I am the Holy Cat Tech Member
    6,104
    341
    63
    italy
    Maybe.
     
  9. Ambil

    Ambil

    I want that heinous hedgehog hammered! Member
    893
    0
    0
    Spain
    Perhaps. I quote myself from the screenshot topic:

    The edges of the levels disappear. And if I poke the code of the monitors, the game crashes.

    You might want to place a breakpoint on the code so that you can step through it and see where it's going.


    Well, I did this in MEKA debugger, but I didn't find anything.
     
  10. Sik

    Sik

    Sik is pronounced as "seek", not as "sick". Tech Member
    6,719
    0
    0
    being an asshole =P
    No, that's NTSC/PAL shenanigans, and I think Meka defaults to PAL (while usually Fusion defaults to NTSC). That said, gotta love how that snippet Maxim posted wouldn't work anyways (the OR should be an AND). Otherwise that code should work even with interrupts disabled, since the F flag gets set when vblank starts, regardless of the interrupt status (and gets cleared after the port is read).
     
  11. nineko

    nineko

    I am the Holy Cat Tech Member
    6,104
    341
    63
    italy
    Perhaps you should post the corrections in that thread, guests can post on smspower. It's not good if a snippet which is supposed to be a reference is wrong... Don't be shy, we're all friends on smspower :)
     
  12. Ambil

    Ambil

    I want that heinous hedgehog hammered! Member
    893
    0
    0
    Spain
    Well, I think it will be good if everyone knows what I'm doing. So I'll link to the source code. I'm using Glitch's disassembly and only modified the main "s2.asm" file.

    Download (56 KB)
    Password: sonicsez

    It contains 2 files, the "s2.asm" itself and a list of differences with the original, made with a .BAT file, called "diff.asm". However, in "s2.asm" I marked all my modifications with the text
    Code (Text):
    1. ;***CHANGED***
    before it, so you can search for this word in your editor.

    Still, I don't know why the limits of the level don't work, if I haven't touched anything related to them.
     
  13. Ambil

    Ambil

    I want that heinous hedgehog hammered! Member
    893
    0
    0
    Spain
    After I add some lines of code, this happens:

    [​IMG]

    Using MEKA Debugger, I caught the "offending" code. That damn $FF byte is at 0x75C00. Nearly the end of the ROM [​IMG] Normally, the counter shouldn't go there.

    What makes the counter go to the end of the ROM? Still I don't know, but I guess what happens. When assembling, the command line gives me these message:
    Code (Text):
    1. Assembling...
    2. MEM_INSERT: 2. write into $3d16 (old: $22, new: $00).
    3. MEM_INSERT: 2. write into $3d17 (old: $11, new: $00).
    4. MEM_INSERT: 2. write into $3d18 (old: $d5, new: $00).
    5. MEM_INSERT: 2. write into $3d19 (old: $af, new: $00).
    6. MEM_INSERT: 2. write into $3d1a (old: $32, new: $00).
    7. MEM_INSERT: 2. write into $3d1b (old: $10, new: $00).
    8. MEM_INSERT: 2. write into $3d1c (old: $d5, new: $00).
    9. MEM_INSERT: 2. write into $3d1d (old: $18, new: $00).
    10. MEM_INSERT: 2. write into $3d1f (old: $2a, new: $00).
    11. MEM_INSERT: 2. write into $3d20 (old: $74, new: $00).
    12. MEM_INSERT: 2. write into $3d21 (old: $d1, new: $00).
    13. MEM_INSERT: 2. write into $3d22 (old: $09, new: $00).
    14. MEM_INSERT: 2. write into $3d23 (old: $22, new: $00).
    15. MEM_INSERT: 2. write into $3d24 (old: $11, new: $00).
    16. MEM_INSERT: 2. write into $3d25 (old: $d5, new: $00).
    17. MEM_INSERT: 2. write into $3d26 (old: $af, new: $00).
    18. MEM_INSERT: 2. write into $3d27 (old: $32, new: $00).
    19. MEM_INSERT: 2. write into $3d28 (old: $10, new: $00).
    20. MEM_INSERT: 2. write into $3d29 (old: $d5, new: $00).
    21. MEM_INSERT: 2. write into $3d2a (old: $2a, new: $f0).
    22. MEM_INSERT: 2. write into $3d2b (old: $10, new: $ff).
    23. MEM_INSERT: 2. write into $3d2c (old: $d5, new: $10).
    24. MEM_INSERT: 2. write into $3d2d (old: $ed, new: $00).
    25. MEM_INSERT: 2. write into $3d2e (old: $5b, new: $f0).
    26. MEM_INSERT: 2. write into $3d2f (old: $16, new: $ff).
    27. MEM_INSERT: 2. write into $3d30 (old: $d5, new: $10).
    28. MEM_INSERT: 2. write into $3d31 (old: $0e, new: $00).
    29. MEM_INSERT: 2. write into $3d32 (old: $00, new: $e0).
    30. MEM_INSERT: 2. write into $3d33 (old: $cb, new: $ff).
    31. MEM_INSERT: 2. write into $3d34 (old: $7a, new: $20).
    32. MEM_INSERT: 2. write into $3d35 (old: $28, new: $00).
    33. MEM_INSERT: 1. write into $3d36 (old: $00, new: $e0).
    34. MEM_INSERT: 2. write into $3d37 (old: $0d, new: $ff).
    35. MEM_INSERT: 2. write into $3d38 (old: $af, new: $20).
    36. MEM_INSERT: 2. write into $3d39 (old: $ed, new: $00).
    37. MEM_INSERT: 2. write into $3d3a (old: $52, new: $f0).
    38. MEM_INSERT: 2. write into $3d3b (old: $22, new: $ff).
    39. MEM_INSERT: 2. write into $3d3c (old: $10, new: $fc).
    40. MEM_INSERT: 2. write into $3d3d (old: $d5, new: $ff).
    41. MEM_INSERT: 2. write into $3d3e (old: $dd, new: $c0).
    42. MEM_INSERT: 2. write into $3d3f (old: $7e, new: $ff).
    43. MEM_INSERT: 2. write into $3d40 (old: $12, new: $10).
    44. MEM_INSERT: 2. write into $3d41 (old: $99, new: $00).
    45. MEM_INSERT: 2. write into $3d42 (old: $dd, new: $c0).
    46. MEM_INSERT: 2. write into $3d43 (old: $77, new: $ff).
    47. MEM_INSERT: 2. write into $3d44 (old: $12, new: $10).
    48. MEM_INSERT: 2. write into $3d45 (old: $21, new: $00).
    49. MEM_INSERT: 2. write into $3d48 (old: $22, new: $00).
    50. MEM_INSERT: 2. write into $3d49 (old: $6f, new: $00).
    51. MEM_INSERT: 2. write into $3d4a (old: $d3, new: $00).
    52. MEM_INSERT: 2. write into $3d4b (old: $22, new: $00).
    53. MEM_INSERT: 2. write into $3d4c (old: $16, new: $00).
    54. MEM_INSERT: 2. write into $3d4d (old: $d5, new: $00).
    55. MEM_INSERT: 2. write into $3d4e (old: $c9, new: $f0).
    56. MEM_INSERT: 2. write into $7ff0 (old: $bf, new: $54).
    57. MEM_INSERT: 2. write into $7ff1 (old: $06, new: $4d).
    58. MEM_INSERT: 2. write into $7ff2 (old: $80, new: $52).
    59. MEM_INSERT: 2. write into $7ff3 (old: $0e, new: $20).
    60. MEM_INSERT: 2. write into $7ff4 (old: $be, new: $53).
    61. MEM_INSERT: 2. write into $7ff5 (old: $ed, new: $45).
    62. MEM_INSERT: 2. write into $7ff6 (old: $b3, new: $47).
    63. MEM_INSERT: 2. write into $7ff7 (old: $c9, new: $41).
    64. Free space at $13f80-$13fff.
    65. Free space at $17e00-$17fff.
    66. Free space at $1bfd0-$1bfff.
    67. Free space at $1fab4-$1ffff.
    68. Free space at $23c72-$23fff.
    69. Free space at $27e51-$27fff.
    70. Free space at $2b812-$2bfff.
    71. Free space at $2fa48-$2ffff.
    72. Free space at $33d9d-$33fff.
    73. Free space at $37d0c-$37fff.
    74. Free space at $3b723-$3bfff.
    75. Free space at $3fc5e-$3ffff.
    76. Free space at $43eda-$43fff.
    77. Free space at $47e00-$47fff.
    78. Free space at $4be06-$4bfff.
    79. Free space at $4fc2a-$4ffff.
    80. Free space at $53c60-$53fff.
    81. Free space at $57f9a-$57fff.
    82. Free space at $5b900-$5bfff.
    83. Free space at $5fb06-$5ffff.
    84. Free space at $633cc-$63fff.
    85. Free space at $67f06-$67fff.
    86. Free space at $6bd8a-$6bfff.
    87. Free space at $6f719-$6ffff.
    88. Free space at $73fac-$73fff.
    89. Free space at $77732-$77fff.
    90. Free space at $7bbe7-$7bfff.
    91. Free space at $7ffe3-$7ffff.
    92. 27534 unused bytes of total 524288.
    93. Total 524288 additional bytes (from headers and footers).
    94. Total size 1048576 bytes.
    95. Linking...
    96. ==========================
    97.   Build Success.
    98. ==========================
    99. Presione una tecla para continuar . . .
    So I guess there isn't space in the first two blocks (both dedicated to the main code of the game) and the assembler overwrites some bytes, causing it to crash.

    I'll see if I can compress/simplify the code in some way.
     
  14. Glitch

    Glitch

    Tech Member
    164
    3
    18
    Yeah. That's basically what's happening. You've added some code which is overflowing into $3D16. The assembler is overwriting your code with the acceleration constants ("accel_values_right.bin"). Incidentally, the code that enforces the camera limit is also around that area. That's probably the cause of your level bounds issues.

    There's precious little free space in the first two ROM banks. If you can get away with it, you'd be better off putting your new code in one of the later banks (which have plenty of free space) and swapping it in as you need it.
     
  15. Ambil

    Ambil

    I want that heinous hedgehog hammered! Member
    893
    0
    0
    Spain
    Thanks, but how to do this? Just call the label, or I need to somehow call the bank?

    Also, now I got a weird error in the command line. It never happened before and there's no way for me to ged rid of it.

    Code (Text):
    1. MEM_INSERT: Origin ($4000) overflows from bank (1).
    Perhaps it has something to do with the .org at $3D16? I don't know, but this error keeps going on with or without that .org label.

    (Edit: Minor error)
     
  16. Ravenfreak

    Ravenfreak

    Is actually a guy. Tech Member
    2,980
    115
    43
    O'Fallon Mo
    Hacking Sonic Drift, Writer at Sonic Cage Dome
    ^ I've seen that error plenty of times while modifying code. The compiler is telling you that the stack pointer is literally overflowing with data, due to recursive function calls or mis-matched pushes to the stack. I got this problem when I had too many ret operands in my code. And don't you have to page in the bank that contains the free space you want to use? (Like paging in bank 08 if you wanted to use the free space at $23C72 and then setting it to an empty offset?)
     
  17. Glitch

    Glitch

    Tech Member
    164
    3
    18
    That error means that you're including data in a bank which is overflowing the bank boundary. Each bank is 16KB (16384 bytes) in size. The data is simply overflowing the 16,384th byte. It doesn't have anything to do with the stack pointer. WLA-DX doesn't perform any stack analysis. It's the programmer's job to maintain the stack. If you happen to execute too many RETs or forget to pop something off you'll encounter some strange bugs. Such is the nature of low-level development; it's not a flaw in the assembler.

    As for swapping in a bank: you need to write the bank number into one of the paging registers. This changes the part of ROM that is visible in each 16KB slot of the Z80's address space. WLA-DX can automatically calculate the bank number for you. If you prefix a label with a colon WLA-DX will replace it with the bank number:

    Code (Text):
    1.  
    2. LD  A, :SomeLabel
    3.  
     
  18. Ambil

    Ambil

    I want that heinous hedgehog hammered! Member
    893
    0
    0
    Spain
    Thanks. Now I can successfully link to code in other banks. I tried it and it works.

    Everything works perfectly except for one thing. In the final boss, in less than a minute of play, the game crashes. The sprites grow larger and then the game gets stucks or reboots.

    [​IMG]