Announcement: Be excellent to each other.


Caravel Forum : Caravel Boards : Development : Fly mode for Webfoot DROD (Some dirty reverse engineering inside)
New Topic New Poll Post Reply
Poster Message
vorteX
Level: Goblin
Rank Points: 20
Registered: 08-02-2023
IP: Logged
icon Fly mode for Webfoot DROD (+8)  
Hey folks!
Recently I decided to play good old DROD on my Steamdeck to refresh some of my childhood memories.
I came a long way up to level 8 only to realize that I missed a room in the beginning and there is no way back.

I just spent few hours to finish a room with three tar mothers. No undos, no checkpoints, remember? I'm sure you understand me.
Even though I had save at the beginning of the level, I didn't want to replay it again.

So... why not to hack the game and fly over the barrier?
There must be a code that checks if Beethro can go to the tile and I can "fix" it.
Also I heard that there are some other bugs that may prevent the game from finishing.

TLDR: Patch byte 0x74 to 0xEB at offset 0x6FD3 to make Beethro fly over walls and other obstacles.

This is 16-bit game for Windows 3.1, sounds like a challenge :)

So for debugging I used good old Turbo Debugger launched in Windows 3.1 installed inside DOSBox and Ghidra as a disassembler/decompiler.
I also could run the game with wine and debug with gdb but it's too much trouble because 16-bit game is running inside NTVDM. It's better to use native tools.

First, let's find window proc. It should be referenced somewhere before RegisterClass():

LAB_1010_23c6                                   XREF[1]:     1010:23a6 (j)   
       1010:23c6                 MOV        SI ,word ptr [BP  + param_3 ]
       1010:23c9                 MOV        word ptr [0x2c4 ],SI
       1010:23cd                 CMP        word ptr [BP  + param_2 ],0x0
       1010:23d1                 JNZ        LAB_1010_242c
       1010:23d3                 MOV        word ptr [BP  + local_2e ],0x3
       1010:23d8                 MOV        word ptr [BP  + local_2c ],0x24c0
       1010:23dd                 MOV        word ptr [BP  + local_2a ],0x1010
       1010:23e2                 XOR        AX ,AX
       1010:23e4                 MOV        word ptr [BP  + local_28 ],AX
       1010:23e7                 MOV        word ptr [BP  + local_26 ],AX
       1010:23ea                 MOV        word ptr [BP  + local_24 ],SI
       1010:23ed                 PUSH       SI
       1010:23ee                 PUSH       DS
       1010:23ef                 PUSH       0x2c8
       1010:23f2                 CALLF      USER::LOADICON                                   undefined LOADICON()
       1010:23f7                 MOV        word ptr [BP  + local_22 ],AX
       1010:23fa                 PUSH       SI
       1010:23fb                 PUSH       DS
       1010:23fc                 PUSH       0xa0c
       1010:23ff                 CALLF      USER::LOADCURSOR                                 undefined LOADCURSOR()
       1010:2404                 MOV        word ptr [BP  + local_20 ],AX
       1010:2407                 PUSH       0x1
       1010:2409                 CALLF      GDI::GETSTOCKOBJECT                              undefined GETSTOCKOBJECT()
       1010:240e                 MOV        word ptr [BP  + local_1e ],AX
       1010:2411                 SUB        AX ,AX
       1010:2413                 MOV        word ptr [BP  + local_1a ],AX
       1010:2416                 MOV        word ptr [BP  + local_1c ],AX
       1010:2419                 MOV        AX ,0x2c8
       1010:241c                 MOV        word ptr [BP  + local_18 ],AX
       1010:241f                 MOV        word ptr [BP  + local_16 ],DS
       1010:2422                 LEA        AX ,[BP  + local_2e ]
       1010:2425                 PUSH       SS
       1010:2426                 PUSH       AX
       1010:2427                 CALLF      USER::REGISTERCLASS                              undefined REGISTERCLASS()


It's 1010:24C0.
After some analyzis we can find WM_KEYDOWN handler, it's at 1010:2B60:

if (*(int *)&DAT_1020_02c6 != 0) {
    return;
}
iVar7 = FUN_1008_4d1a();
if (iVar7 == 2) {
    FUN_1010_6c86(param_3);
    return;
}
if (iVar7 == 3) {
    if (param_3 == (byte *)0x5a) {
    FUN_1008_09f4();
    }
    if (param_3 == (byte *)0x70) {
LAB_1010_2ba4:
    FUN_1010_729c();
    return;
    }
    iVar7 = FUN_1010_7404(param_3);
    if (iVar7 == 0) {
    return;
    }
    if (iVar7 != 0xd) {
    if (iVar7 == 0xe) {
        return;
    }
    if (iVar7 != 0xf) {
        iVar18 = FUN_1010_1c60();
        if (iVar18 != 0) {
        return;
        }
        FUN_1010_5b54();
        FUN_1008_297e(iVar7);
        return;
    }
    iVar7 = FUN_1010_1c60();
    if (iVar7 != 0) {
        return;
    }
    FUN_1008_15f6();
    return;
    }
}


We don't care about DAT_1020_02c6, I don't know what is it, just some check.
Looks like FUN_1008_4d1a returns current game state, 2 is for main menu and 3 is for the game.
Next, the game checks if pressed 'Z' (shows current location) or 'F1' (shows help).

FUN_1010_7404() accepts key and return game action. Here is the list of possible actions:
0 = Empty
1 = Go North
2 = Go North-East
3 = Go West
4 = Empty
5 = Go East
6 = Go South-West
7 = Go South
8 = Go South-East
9 = Swing Clockwise
10 = Swing Counterclockwise
11 = Go North-West
12 = Wait
13 = Cancel, activated on Escape
14 = Unkown, activated on F5
15 = Restart room

After some unimportant checks and call we finally go the main function - FUN_1008_297e.
There is a huge switch in the beginning of the function for every possible action.
Every move action modifies two variables, apparently they define how Beethro moves in X and Y directions, for example:
case 8:
    dx = 1;
    local_10 = dx;
    local_e = dx;
    dy = dx;
    break;

For South-East, we go in direction [1; 1]. Coordinates start from top-left corner :)

Next, the game gets tile of the new position. Note that it's calculated differently when Beethro goes to the direction of his sword:
if ((sword_pos % 3 - dx == 1) && (sword_pos / 3 - dy == 1)) {
    target_tile = DAT_1020_0268;
    if (DAT_1020_0268 == 0xff) {
        target_tile = FUN_1008_84d6(DAT_1020_0262,DAT_1020_0264);
    }
}
else {
    target_tile = FUN_1008_854a(DAT_1020_0254 + dx,dy + DAT_1020_0256);
    target_tile = target_tile & 0xff;
}


By the way, sword_pos contains position of Beethro's sword, it's value corresponds to actions form the table above.
Let's say, we a going South-East: sword_pos=8, dx=1, dy=1, 8 % 3 - 1 = 1, 8 / 3 - 1 = 1 - the check works :)
I didn't understand why this check is required, but anyway...

Checking some tiles, don't care:
if ((((((DAT_1020_025a == 0xdf) || (DAT_1020_025a == 0xd8)) || (DAT_1020_025a == 0xd9)) ||
    ((DAT_1020_025a == 0xda || (DAT_1020_025a == 0xdb)))) ||
    ((DAT_1020_025a == 0xdc || ((DAT_1020_025a == 0xdd || (DAT_1020_025a == 0xde)))))) &&
    (iVar7 = FUN_1008_5ed8(DAT_1020_025a,dy * 3 + dx + 4), iVar7 != 0)) {
    dy = 0;
    dx = 0;
    bVar1 = true;
}


The most interesting part is right here:
if (!bVar1) {
    if ((((target_tile == 0xdf) || (target_tile == 0xd8)) || (target_tile == 0xd9)) ||
        (((target_tile == 0xda || (target_tile == 0xdb)) ||
        ((target_tile == 0xdc || ((target_tile == 0xdd || (target_tile == 0xde)))))))) {
        iVar7 = FUN_1008_5ed8(target_tile,dy * 3 + dx + 4);
        if (iVar7 == 0) {
            local_36 = FUN_1008_84d6(dx + cur_x,dy + cur_y);
            iVar7 = FUN_1008_6ea2(local_36);
            if (iVar7 == 0) {
                cVar2 = (byte)local_36;
                goto joined_r0x10083a0b;
            }
        }
        else {
    LAB_1008_3ab5:
            dy = 0;
            dx = 0;
        }
    }
    else if (((dx != 0) || (dy != 0)) && (iVar7 = FUN_1008_6ea2(target_tile), iVar7 == 0)) {
        cVar2 = (byte)target_tile;
    joined_r0x10083a0b:
        if ((((cVar2 != 0x37) && (cVar2 != 0x30)) &&
            (((cVar2 != 0x31 && ((cVar2 != 0x32 && (cVar2 != 0x33)))) && (cVar2 != 0x34)))) &&
            (((((((cVar2 != 0x35 && (cVar2 != 0x36)) && (cVar2 != 0x17)) &&
                ((cVar2 != 0x10 && (cVar2 != 0x11)))) && (cVar2 != 0x12)) &&
            ((cVar2 != 0x13 && (cVar2 != 0x14)))) &&
            (((((cVar2 != 0x15 && (((cVar2 != 0x16 && (cVar2 != 0x29)) && (cVar2 != 0xc1)))) &&
                (((cVar2 != 0x39 && (cVar2 != 0xc4)) && (cVar2 != 0x78)))) &&
            ((cVar2 != 0x79 && (cVar2 != 0x88)))) &&
            ((cVar2 != 0x98 && ((cVar2 != 0xa8 && (cVar2 != 0xc5)))))))))) goto LAB_1008_3ab5;
    }
}


Just check this out!
dy = 0;
dx = 0;


This is where the game checks if Beethro can actually go to the new position, exactly what we are looking for!
We just need to change the last check
1008:3ab3                 JZ         LAB_1008_3abd

to this
1008:3ab3                 JMP         LAB_1008_3abd


And now Beethro can go fly over the walls :)
Surprisingly, it doesn't break anything in the game.

It's not very clear patch, I didn't alter the check before, so it might not work for all cases.
But it was enough for me to go back and finish the room.

What can be done next:
0) Fly mode toggled by key press. Currently I have to switch executables.
1) Instant win room
2) Make level finished
3) Invisiblity
4) God mode
5) Something else?

Small bonus, I'm sure you know where this is:


Thanks for reading!
See you later, I have 17 more levels to beat.

08-08-2023 at 12:17 PM
View Profile Send Private Message to User Show all user's posts Quote Reply
Kalin
Level: Master Delver
Avatar
Rank Points: 194
Registered: 01-25-2016
IP: Logged
icon Re: Fly mode for Webfoot DROD (+3)  
vorteX wrote:
Small bonus, I'm sure you know where this is:
Great! Now you can play JtRH with no undo or checkpoints!
08-08-2023 at 06:29 PM
View Profile Send Private Message to User Show all user's posts Quote Reply
New Topic New Poll Post Reply
Caravel Forum : Caravel Boards : Development : Fly mode for Webfoot DROD (Some dirty reverse engineering inside)
Surf To:


Forum Rules:
Can I post a new topic? No
Can I reply? No
Can I read? Yes
HTML Enabled? No
UBBC Enabled? Yes
Words Filter Enable? No

Contact Us | CaravelGames.com

Powered by: tForum tForumHacks Edition b0.98.8
Originally created by Toan Huynh (Copyright © 2000)
Enhanced by the tForumHacks team and the Caravel team.