Help figuring out some logic in Flicky - new challenge #3!
Moderator: BigEvilCorporation
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:
http://www.youtube.com/watch?v=9_jcUGPURt8&t=29s
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.
Thanks,
matt
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:
http://www.youtube.com/watch?v=9_jcUGPURt8&t=29s
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.
Thanks,
matt
Last edited by matt on Thu Jun 27, 2013 7:27 am, edited 2 times in total.
-
- Very interested
- Posts: 292
- 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.
Bad, long way.
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! :]
Next, please!
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! :]
Next, please!
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:
Look for 000164EC and get:
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
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:
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:
Similar result we should get with Gens Tracer. I've got next log after loading state with diamonded cat.
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:
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.
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)
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
MOVE.W #$0028,(A1)
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
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: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.
@GManiac
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.
http://flicky1984.tumblr.com
Thank you once again. You rock!
matt
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.
http://flicky1984.tumblr.com
Thank you once again. You rock!
matt
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.
Let me know your thoughts.
matt
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.
matt
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!
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!