I am looking for some advice and/or help :)
I want to reverse engineer a specific area of Flicky for Megadrive, but I have no real idea where to start. I come from a Computer Science background and am familiar with the process of debugging. But my Megadrive specific knowledge is a bit lacking. I need pointing in the right direction.
So the question I want to answer using debugging is:
What logic governs whether or not a cat will drop a diamond when it disappears?
You can see what I mean in this video:
If you watch some of the rest of the video, you'll see that when cats are knocked out they do not drop diamonds every time.
But where to start looking?
First I thought about watching for the diamond sprite, but that's displayed after the logic that governs the diamond drop.
So, thinking a bit more I remembered that after the cat disappears there's a puff of smoke. After that the diamond appears (or not, according to the logic).
So I need to put a break/watch on when this graphic is accessed. But first I need to find it in memory.
Then I'll need to trace through the code that executes from this point on, for bunch frames.
Any thoughts, advice or additional help appreciated.
My approach (works only if your debugger provides call stack, or with another features providing tracing backwards):
1) Find good clue. I tell you such clue: when you get diamond -> +1000 to score.
2) Find score value in RAM. Check, that when you picking up diamond, this value grows up. This value can be in any format, so may be you will need to use "equal to previous value" and "not equal to previous value" condition when you search it.
3) Now, set breakpoint to write into offset of this value. Cut off all false write events, that not corresponded to your event (diamond picking up). So, before next step you will need to know exact place in code, where adding score happens when you get diamond.
4) Now, find what condition checked to make decision: picked up or not.
5) What needs to make this condition satisfied? To answer this question, you will need to see what RAM entries code checks.
6) If you know RAM entries used for checking, then set breakpoints there, and see which code performs write.
7) Repeat from 3)
So, game workflow: condition -> action, which at some point will change state of another condition -> action again....
We investigate: hook action -> see condition before (it's simple) -> hook action that makes condition satisfied -> see condition before.
Knowledge of hardware is not required for this method . You will need only knowledge of system assembler. You will need very few commands: compare, and conditional jumps.
Advice wanted? Take it! :]
Unfortunatelly, there are no any really powerful debugging tools for MD, so we have to use what we have.
If you want NLZ of Flicky, here it is, I made it 5 years ago... eh, good old times....
Just disassemble file. All data marked as 0xFF.
Let me describe my vision of this situation of reverse engineering and my actions in it. Some new versions of Gens and other emulators provide some new functions, but I'm too lazy to learn something so my actions may seem primitive and "old-school"
Sorry for ugly English
The main idea is: imagine two cases:
1. Cat leaves a diamond
2. Cat doesn't leave it
In second case game uses additional logic from branch "show a diamond" and data for it. Using code/data logger we can track code and data used specially for a diamond. We need logger which doesn't log every instruction but saves MAP and logs only unique instructions. And it also must save map between logging sessions. Exodus does so, and, AFAIR, old Gens Tracer does too. So what we do?
First, we need to make save state for case 2: before cat dies and leaves diamond.
Enable logging from start (hard reset the game), start the game, walk here and there, get chickens, perform as many actions as we can to fill logger map and... kill cat without diamond. Stop the logging and save this point. On Gens Tracer you should do nothing, on Exodus you should Analyze and export log to file. Then start the logging again and load savestate where cat leaves diamond. Stop logging and save this new point. Comparing two results we will see new code/data.
Exodus was my first try, I didn't use saves and made all actions as one large try. After 2nd logger "checkpoint" it showed only 7 bytes more. Comparing results gave me address $165AC and near it. Ok, look for it in NLZed disassembly:
Code: Select all
_000164EA: AB64 Invalid _000164EC: 08D0 0007 BSET #$7,(A0) ... _00016502: 217C 000165AC 0008 MOVE.L #$000165AC,$0008(A0)
Code: Select all
... _0001129E: 3F00 MOVE.W D0,-(A7) _000112A0: 3010 MOVE.W (A0),D0 _000112A2: 6708 BEQ ROM:$000112AC(pc) _000112A4: 0240 7FFC ANDI.W #$7FFC,D0 _000112A8: 4EBB 0006 JSR $000112B0(pc,D0.W) _000112AC: 301F MOVE.W (A7)+,D0 _000112AE: 4E75 RTS _000112B0: 6000 FFEA BRA ROM:$0001129C(pc) ... _000112D8: 6000 5212 BRA ROM:$000164EC(pc)
Now I could cheat and tell you "look for "0028,(A0"" or "look even for "0028,("", but it won't be truth, I didn't do so, and yes, you cannot find first substring with A0, but you can find second substring with left brace only in disassembly. But found instruction will look like
Code: Select all
Of course, I didn't know the right instruction, so I did next steps. Unfortunatelly, 0001129E is called very often with various values for A0 and simple breakpoint to 1129E won't help. Breakpoint to 000164EC won't help too - when it's done, we will be ALREADY at 000164EC, but we need preceding code. So we need to localize frame where this breakpoint is done, stop 100-1000 instructions BEFORE it and trace in debugger to track the right conditions we are searching for. In new versions of Gens good choice is to use Slow Mode or Frame Advance.
For example, we have savestate with 10 frames before diamond appearance. We create breakpoint to 000164EC (I prefer patch-code 000164EC:60FE in embedded Cheat Engine, it will hang 68k only). Then we make 10 frame steps. Go to debugger and see we are already at 000164EC. Ok. We load savestate, make 9 frame steps, go to debugger, see some random instruction, SAVE to another slot, press P button in Gens debugger and ...voila! We've got to 000164EC. Next actions are straightforward. We trace instructions, look at RAM and localize conditions which lead us to 000164EC.
I found this subroutine:
Code: Select all
_000153A4: 08E8 0007 003C BSET #$7,$003C(A0) ... _000153D8: 1038 D88A MOVE.B RAM:$D88A,D0 _000153DC: 0200 00F0 ANDI.B #$F0,D0 _000153E0: 6622 BNE ROM:$00015404(pc) _000153E2: 43F8 C740 LEA RAM:$C740,A1 ... _00015400: 32BC 0028 MOVE.W #$0028,(A1) _00015404: 6100 BD12 BSR ROM:$00011118(pc) _00015408: 4E75 RTS
Interestingly, there is another similar subroutine, but it wasn't used:
Code: Select all
... _0001620E: 32BC 0028 MOVE.W #$0028,(A1) ...
Similar result we should get with Gens Tracer. I've got next log after loading state with diamonded cat.
Code: Select all
TRACE STOPPED 01:53E2 43 F8 LEA ($C740),A1 01:53E6 4A 28 TST.B $0016(A0) 01:53EA 67 04 BEQ #$04 [01:53F0] 01:53F0 3E 28 MOVE.w $0030(A0),D7 01:53F4 3C 28 MOVE.w $0024(A0),D6 01:53F8 33 47 MOVE.w D7,$0030(A1) 01:53FC 33 46 MOVE.w D6,$0024(A1) 01:5400 32 BC MOVE.w #$0028,(A1) 01:12D8 60 00 BRA #$5212 [01:64EC] 01:64EC 08 D0 BSET #7,(A0) 01:64F0 66 28 BNE #$28 [01:651A] 01:64F2 42 28 CLR.B $0005(A0) 01:64F6 42 A8 CLR.L $0034(A0) 01:64FA 42 A8 CLR.L $002C(A0) 01:64FE 5E 68 ADDQ.W #7,$0024(A0) 01:6502 21 7C MOVE.l #$000165AC,$0008(A0) 01:650A 42 68 CLR.W $0006(A0) 01:650E 31 7C MOVE.w #$012C,$0038(A0) 01:6514 08 A8 BCLR #7,$0002(A0) 01:651A 61 00 BSR #$FFFFAB40 [01:105C] 01:651E 61 00 BSR #$FFFFAC06 [01:1126] 01:6522 43 F8 LEA ($C440),A1 01:6526 61 00 BSR #$FFFFB298 [01:17C0] 01:652A 4A 00 TST.B D0 01:652C 67 4E BEQ #$4E [01:657C] 01:657C 53 68 SUBQ.W #1,$0038(A0) 01:6580 66 04 BNE #$04 [01:6586] 01:6586 4E 75 RTS TRACE STOPPED
One tricky thing.
Although this game is very small and old, internally it's pretty tricky. It copies bunch of code and data to RAM and jumps there. Here is code:
Code: Select all
_000003C8: 41F9 00010000 LEA ROM:$00010000,A0 _000003CE: 43F9 00FF0000 LEA RAM:$00FF0000,A1 _000003D4: 303C 2FFF MOVE.W #$2FFF,D0 _000003D8: 22D8 MOVE.L (A0)+,(A1)+ _000003DA: 51C8 FFFC DBRA D0,ROM:$000003D8(pc) _000003DE: 4EF9 00FF0000 JMP RAM:$00FF0000
Also there are many jumps to other parts of RAM.
I don't remember exactly, it seems I used original, not hacked version of ROM with Exodus, that's why it didn't show "new" code in second log (code was in RAM and I didn't log that area), only several bytes of data. And then I hacked the ROM, and later, writing this message, I tried GensTracer - on already hacked ROM.
Yeah, I've just verified, tested hacked ROM on Exodus and it gave me the same result as Gens Tracer. Quick and simple But it needed prework - hacking the ROM.
Please forgive my response to your private message.
When I saw that you had left a reply, I was not expecting such detailed information and THE ANSWER!
You have my utmost, heartfelt thanks. Amazing work!
Whilst it is not the kind of answer I was hoping for to be able to exploit the logic for high scores, it is as much of an answer as I could have ever hoped.
Now I will try to reproduce your steps using MAME debugger to check that the arcade version works in the same way.
I will post a summary of your findings on my Flicky blog, if this is OK with you? I will give you full credit.
Thank you once again. You rock!
I wondered if anybody was interested in another Flicky logic challenge?
Each level has one window that looks different to the others. This is the special bonus window.
If you wait too long in a level, a monster will appear in this window and spit fireballs at you.
However, every so often, on competition of a level the special bonus window opens to reveal a character (eg. a semi-naked pixel girl) and you get a nice jump in score.
So, I was wondering what the logic is that governs the window opening?
I have read that it is something to do with finishing previous levels in less than 20 seconds (maximum score bonus per level) and not losing any lives. But the exact equation is unknown.
Let me know your thoughts.
Here's the deal. The window bonus is given based on performance over every 8-round span, starting with round 3. If you earned it, it comes in the last of those 8 rounds.
To get the bonus, you need to (for each stage):
- deliver all the chirps at once
- finish within the time limit (see below)
Bonus round performances are not a factor.
Time limits and bonus points for each 8-round span are:
Rounds 3 thru 10: 30 seconds max, 10000 points
Rounds 11 thru 18: 35 seconds max, 50000 points
Rounds 19 thru 26: 40 seconds max, 100000 points
Rounds 27 thru 34: 45 seconds max, 500000 points
Rounds 35 thru 42: 50 seconds max, 1000000 points
Rounds 43 thru 50: 25 seconds max, 2000000 points
... then it loops back around as the stages repeat.
BTW if you're interested in the arcade version, check out Phil Bennett's posts in that thread over at MAMEWorld.