Hi,
as some of you might know by now, a friend and I are fanatic Mega Bomberman players. Getting really good at this game had its drawbacks, as some infidelities in the game had surfaced...
You may ask, why would you fix a bug in a game this old? Let alone one that has a thousand ports to modern consoles and computers? Aside from blazingly simple and lag-free netplayability using Kega Fusion... well, most Bomberman inevitably take the skill out of the equasion as they plaster the map with game-breaking gimmicks. The Mega Drive rendition lacks most of that, but that makes it all the more tactically brilliant. The set of items you are able to collect in a match is limited, simple and precise. The game allows you to tightly control kicked bombs. The controls are perfect and responsive, something that most, if not all, other bombermen lack.
Stage 7 with the warpdoors is special. These introduce tons of new strategy. You can go for a rush, you can play it safe, lure the other player into doing things, precisely send bombs to other parts of the map. It's simply brilliant. However, at the higher skill levels during play, a massive oversight on Westone's part becomes obvious.
If you don't know, the skull item is an item that selects randomly between 10 different "punishments" when collected. Either you get one of the nine preset "downgrades" (walking speed, explosion timer, cant stop laying bombs, etc.) or, and this is the fun bit, you swap positions and louies with another player.
If you happen to be inside a warp door when someone triggers this swap, you will be invincible until either the game ends or you go in a warpdoor again. (another annoying glitch is that the other player will be stuck for a few seconds and then reappear on top of the first warpdoor instead of the real destination warpdoor).
Here's a video I made of the bug as we first discovered it.
So, as this started happening quite often, we decided to playing this game because it is factually broken now... Unelss I could fix the bug, we would never play Bomberman again!
So, armed with Exodus and IDA, I got to work. I isolated the routines responsible for the skull (and subsequently the swap). The routine copies bytes (one by one!! *sigh*) between the players (position and louie attributes, etc.)
Based on my earlier reverse-engineering of the game's memory contents (http://pastebin.com/YFqqUaf2), I found out that two values in a player's memory area corresponds to the state regarding the warpdoors (specifically the identifier for the warpdoor you're currently in and the amount of frames left in this state). I also found out that these bytes are NOT copied, neither are the original values deleted from the original player. As a consequence, the game thinks the player is still in the warpdoor, and thus he is internally marked invincible (as you cannot get killed while you're in a warpdoor, obviously) -- see the "invinc" bit on the player attribute byte...
This is the routine that copies the values. The values for the warpdoors would be $12(a*) and $3A(a*), they're missing.Code:ROM:00006BB2 move.l 2(a4),-(sp) ROM:00006BB6 move.l 2(a5),2(a4) ROM:00006BBC move.l (sp)+,2(a5) ROM:00006BC0 move.l 6(a4),-(sp) ROM:00006BC4 move.l 6(a5),6(a4) ROM:00006BCA move.l (sp)+,6(a5) ROM:00006BCE move.b $26(a4),-(sp) ROM:00006BD2 move.b $26(a5),$26(a4) ROM:00006BD8 move.b (sp)+,$26(a5) ROM:00006BDC move.b $27(a4),-(sp) ROM:00006BE0 move.b $27(a5),$27(a4) ROM:00006BE6 move.b (sp)+,$27(a5) ROM:00006BEA move.b $28(a4),-(sp) ROM:00006BEE move.b $28(a5),$28(a4) ROM:00006BF4 move.b (sp)+,$28(a5) ROM:00006BF8 move.b $29(a4),-(sp) ROM:00006BFC move.b $29(a5),$29(a4) ROM:00006C02 move.b (sp)+,$29(a5) ROM:00006C06 move.b $2A(a4),-(sp) ROM:00006C0A move.b $2A(a5),$2A(a4) ROM:00006C10 move.b (sp)+,$2A(a5) ROM:00006C14 move.b $2B(a4),-(sp) ROM:00006C18 move.b $2B(a5),$2B(a4) ROM:00006C1E move.b (sp)+,$2B(a5) ROM:00006C22 move.b $15(a4),-(sp) ROM:00006C26 move.b $15(a5),$15(a4) ROM:00006C2C move.b (sp)+,$15(a5) ROM:00006C30 move.b $14(a4),d0 ROM:00006C34 andi.b #$7F,d0 ; '' ROM:00006C38 ori.b #$40,d0 ; '@' ROM:00006C3C move.b $14(a5),d1 ROM:00006C40 move.b d0,$14(a5) ROM:00006C44 andi.b #$7F,d1 ; '' ROM:00006C48 ori.b #$40,d1 ; '@' ROM:00006C4C move.b d1,$14(a4) ROM:00006C50 clr.b $24(a4) ROM:00006C54 clr.b $24(a5) ROM:00006C58 clr.b $25(a4) ROM:00006C5C clr.b $25(a5)
My bugfixed code looks like this:
Code:move.l 2(a4),-(sp) ; Copy information between the players... move.l 2(a5),2(a4) move.l (sp)+,2(a5) move.l 6(a4),-(sp) move.l 6(a5),6(a4) move.l (sp)+,6(a5) move.l $26(a4),-(sp) move.l $26(a5),$26(a4) ; These have to do with louies. ; I so want to make this use longs instead of bytes. ;; Post-note: I did =P move.l (sp)+,$26(a5) move.w $2A(a4),-(sp) move.w $2A(a5),$2A(a4) move.w (sp)+,$2A(a5) ;;; The bugfix starts here ; Copy Warping Doors Timer move.b $12(a4),-(sp) move.b $12(a5),$12(a4) move.b (sp)+,$12(a5) ; Copy Current Warping Door ID move.b $3A(a4),-(sp) move.b $3A(a5),$3A(a4) move.b (sp)+,$3A(a5) ;;;;; END BUGFIX!!! move.b $15(a4),-(sp) move.b $15(a5),$15(a4) move.b (sp)+,$15(a5) move.b $14(a4),d0 andi.b #$7F,d0 ori.b #$40,d0 move.b $14(a5),d1 ; This byte stores the direction and walking ; properties of the player. move.b d0,$14(a5) andi.b #$7F,d1 ori.b #$40,d1 move.b d1,$14(a4) clr.b $24(a4) clr.b $24(a5) clr.b $25(a4) clr.b $25(a5) rts
The costs of jumping there and copying the additional values are mitigated by optimizing the code to use longwords instead of copying each byte separately :P
The code is attached to the end of the ROM ($100000) and the original code is replaced with this:
This is a jsr to $100000 and a jmp to the end of the code (the lazy approach).Code:4E B9 00 10 00 00 4E F9 00 00 6C 60
The ROM I use actually has another perk which I call "tournament edition", it actually makes the game boot into battle-mode, thus saving time spent skipping the intro and going there manually :P It's done using - sort of - a savestate. I paused Exodus right before the menu appeared, and then exported everything important: RAM contents, register contents, VRAM contents, CRAM contents, Z80 memory contents. I modified the initial PC vector at offset 4 in the ROM to point to the new startup code I made, which restores all of this.
So, I present to you Mega Bomberman: The bugfixed tournament edition :PCode:VDPControl EQU $C00004 VDPData EQU $C00000 SetVRAMAddr: macro addr, dest move.l #$40000000|((\addr)&$3FFF)<<16|(\addr)>>14, (\dest) endm Entry: move.w #$2700, sr move.l #'SEGA', ($A14000) lea (VDPRegs), a0 move.w #24-1, d0 @regs: move.w (a0)+, (VDPControl) dbf d0, @regs lea (Memory), a0 lea ($FF0000), a1 move.w #$10000/4-1, d0 @memcpy: move.l (a0)+, (a1)+ dbf d0, @memcpy ; Z80 Reset & Busreq move.b #1, ($a11200) ; stop reset move.b #1, ($a11100) ; request bus lea (Z80Mem), a0 lea ($A00000), a1 move.w #8192-1, d0 @z80cpy: move.b (a0)+, (a1)+ dbf d0, @z80cpy SetVRAMAddr 0, VDPControl lea (VRAM), a0 move.w #$10000/4-1, d0 @vcpy: move.l (a0)+, (VDPData) dbf d0, @vcpy move.b #0, ($a11200) ; Start Z80 reset move.b #0, ($a11100) ; unrequest Z80 bus move.b #1, ($a11200) ; Stop Z80 reset MOVE.B #$40, ($A10009) ; Ctrl 1 MOVE.B #$40, ($A1000B) ; Ctrl 2 lea (regs), a0 movem.l (a0)+, d0-d7/a1-a7 move.l #$1A712, a0 ; bra.s * jmp $00007C5A Memory: incbin ./mem.bin Z80Mem: incbin ./z80.bin VRAM: incbin ./vram.bin VDPRegs: dc.w $8004, $8124, $8238, $8338, $8407, $857F, $8600, $8700, $8800, $8900, $8A00, $8B00 dc.w $8C00, $8D3F, $8E00, $8F02, $9011, $9100, $9200, $9300, $9400, $9500, $96C0, $977F Regs: dc.l $440, 5, 2, $808000, $6a6, $FF, 3, $128 dc.l $EFA92, $FFFFAD12, $DB7CC, $10564, $E2004EB8, $FF72B2, $FFFE9E
http://oerg866.mdscene.net/f/mbtournament.bin
PS: Unfortunately this ROM does not work in Regen, use Fusion or Exodus instead. Real Hardware support not tested.
PPS: Due to replacing the init code with my own, Multitap/4-Way-Play devices are not initialized, so only two players can play (if someone supplies me with support code for it, i'll happily fix that)
Cheers.





Reply With Quote
