Help figuring out some logic in Flicky - new challenge #3!

Moderator: BigEvilCorporation

matt
Newbie
Posts: 9
Joined: Tue Nov 01, 2011 3:29 pm
Location: UK
Contact:

Help figuring out some logic in Flicky - new challenge #3!

Hi,

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.

Thanks,
matt
Last edited by matt on Thu Jun 27, 2013 7:27 am, edited 2 times in total.

Charles MacDonald
Very interested
Posts: 283
Joined: Sat Apr 21, 2007 1:14 am

Re: Help figuring out some logic in the game Flicky

EDIT: Yeah r57shell has a much better method!
Last edited by Charles MacDonald on Sun Jun 02, 2013 3:08 am, edited 1 time in total.

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:

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.

matt
Newbie
Posts: 9
Joined: Tue Nov 01, 2011 3:29 pm
Location: UK
Contact:
Hi,

Thanks for the detailed reply Charles, and r57shell.

I have to say I prefer the r57shell method so I will try for that.
The diamond value is not always 1000, but I get the picture.

I am a patient man, this will take me a long time, but I will not give up.

Thanks!
matt

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:
Forgot to mention. Sometimes, to catch something, you will need fully repeatable events. So, use replay, to make code execution flows static.
It is really helpful when you need to catch bug of your game .

GManiac
Very interested
Posts: 92
Joined: Thu Jan 29, 2009 2:05 am
Location: Russia
Hi matt. Sorry for late reply on your PM, I didn't notice it and knew about it only checking my abandoned e-mail

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....
http://shedevr.org.ru/ghost/Flicky.gen.analyzed.rar
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)
``````
Look for 000164EC and get:

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)``````
So, we need value 0028 in d0 to get to 000164EC.

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

``MOVE.W    #\$0028,(A1)``
Wait a minute, we fill d0 from (A0), not (A1)! Though this instruction is that we are searching for, it's pretty unobvious a0 is equal to a1 at that moment. And there is no direct call of 0001129E

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       ``````
Value 0028 and condition above are what we were searching for. So what is RAM:\$D88A? Open RAM window in Exodus, for example, resume the game and realize that \$D888 are in-level seconds and \$D88A are in-level frames (60 per second) in decimal notation. So the condition for diamond appearance is: cat should die at 0-9th frame of any in-level time second.
Phewww. ^_^

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: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
``````
You may see there "new" instructions from 0153E2 to 015400. And we can realize the first "new" instruction goes right after condition we are searching for.

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
``````
You should change last instruction to JMP RAM:\$00010000. We will call it "hacked ROM".

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.

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:
GManiac wrote:Unfortunatelly, there are no any really powerful debugging tools for MD, so we have to use what we have.
How about my version of Gens rerecording 11b? O_o

GManiac
Very interested
Posts: 92
Joined: Thu Jan 29, 2009 2:05 am
Location: Russia
r57shell wrote:
GManiac wrote:Unfortunatelly, there are no any really powerful debugging tools for MD, so we have to use what we have.
How about my version of Gens rerecording 11b? O_o
No way

r57shell
Very interested
Posts: 478
Joined: Sun Dec 23, 2012 1:30 pm
Location: Russia
Contact:
Even z80 debug with breakpoints.
Even breakpoints from lua. You can check condition yourself from lua, and pause z80 or m68k.
More another features. I think it is at least better than Gens KMod.

matt
Newbie
Posts: 9
Joined: Tue Nov 01, 2011 3:29 pm
Location: UK
Contact:
@GManiac

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.
http://flicky1984.tumblr.com

Thank you once again. You rock!
matt

GManiac
Very interested
Posts: 92
Joined: Thu Jan 29, 2009 2:05 am
Location: Russia
matt wrote:Whilst it is not the kind of answer I was hoping for to be able to exploit the logic for high scores
Mmmm, what do you mean?
I will post a summary of your findings on my Flicky blog, if this is OK with you?
Yeah, it's OK.

matt
Newbie
Posts: 9
Joined: Tue Nov 01, 2011 3:29 pm
Location: UK
Contact:
Well, I mean that I was hoping for an answer with logic that is easier to trigger. I want to be able to collect more diamonds during play. (without modifying the game, as I play mainly on arcade hardware)

But, thank you once again!

matt
Newbie
Posts: 9
Joined: Tue Nov 01, 2011 3:29 pm
Location: UK
Contact:
Hi,

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.

matt

TascoDLX
Very interested
Posts: 256
Joined: Tue Feb 06, 2007 8:18 pm
Hey matt,

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.

Cheers!

matt
Newbie
Posts: 9
Joined: Tue Nov 01, 2011 3:29 pm
Location: UK
Contact:
Awesome! Thanks so much.

That other thread is one of mine, but for some reason I did not get a notification there had been replies. So, double thanks!