View Full Version: Keen1 reverse engineering

CloneKeenPlus > Suggestions > Keen1 reverse engineering


Title: Keen1 reverse engineering


tulip - October 22, 2009 10:29 AM (GMT)
Lemm just posted over at the PCKF, and I don't know if you guys even visit there often, so I thought I'd post this over here. I'm not sure if this is any help (I know gerstrong already had a look at the original keen1.c.txt, but maybe Lemm's understanding of it will help a bit, at least I hope so:

QUOTE ("Lemm")
http://files.commanderkeen.org/users/omp/keen1.c.txt

I'm keeping this file and adding comments to it as I go along.

The original is http://www.quantumg.net/keen1.c.txt, but I've added a fair number of annotations in my copy, which might be helpful.


handle_ctrl at $5A39, move_left_right at $2B9A, and draw_level at $4B69 would be good places to start looking. Unfortunately I haven't really looked at the timing of everything. I do know that for each loop, the game records what buttons you have pressed down for that loop as well as the loop prior to the current one.

CODE

#define input.direction   0x7FCA
#define input.jump      0x7FCC
#define input.pogo      0x7FCE
#define input_old.direction      0x7FD0  // these three appear to be the input from the previous frame
#define input_old.jump      0x7FD2
#define input_old.pogo      0x7FD4


I do know that keen has a velocity_x located at 0x6EFA in data segment and what I would guess is if the left/right keys are pushed down, one of the "think" functions (try think_13 for starters) that keen uses would notice this and add whatever to his x velocity.

CODE

  // seg000:391F (This is within think_13() )
     if (reg_bx <= 6) {
        // seg000:3924
        switch(reg_bx) {
           case 0:
           case 1:
           case 2:
              // seg000:392B
              PUSHI(2);
              EMU_CALL(a_move_left_right, 3932, think_13);
              reg_sp += 2;


So here, "2" is passed as a parameter to move_left_right....

CODE

void move_left_right()
{
  PUSH(bp);
  MOV(bp, sp);
  PUSH(si);
  PUSH(di);

  reg_di = MEMW(reg_bp + 4);
  reg_si = 1;

  // seg000:2BD5
  while (reg_si <= MEMW(sprite_sync)) {
     // seg000:2BA7
     W_MEMW(sprite_copy.vel_x, MEMW(sprite_copy.vel_x) + reg_di);
     if ((short)MEMW(sprite_copy.vel_x) > 120) {
        // seg000:2BB2
        W_MEMW(sprite_copy.vel_x, 120);
     } else {
        // seg000:2BBA
        if ((short)MEMW(sprite_copy.vel_x) < -120) {
           // seg000:2BC1
           W_MEMW(sprite_copy.vel_x, -120);
        }
     }

     // seg000:2BC7
     if (reg_si != MEMW(sprite_sync)) {
        // seg000:2BCD
        reg_ax = MEMW(sprite_copy.vel_x);
        W_MEMW(sprite_copy.delta_x, MEMW(sprite_copy.delta_x) + reg_ax);
     }

     // seg000:2BD4
     reg_si++;
  }

  // seg000:2BDB
  POP(di);
  POP(si);
  POP(bp);
  RET;
}


Delta_x, which is the offset per frame in 256'ths of a pixel keeps getting vel_x added to it, until si which starts at 1, equals sprite sync...

And that's all I can tell you. Don't know how this sprite sync works though...

gerstrong - October 22, 2009 11:45 AM (GMT)
Thanks a lot for the information. I will take another look at. Maybe this way we can improve even more the physics.

lemm - December 8, 2009 01:30 PM (GMT)
Hello!

I heard there was some reverse engineering going on so I thought I would stop by here just to see what you guys are up to. I didn't know you all had practically rewritten the entire game :P.

gerstrong - December 9, 2009 05:34 AM (GMT)
We are doing developing as best fitting to Commander Keen as possible,

lemm - December 28, 2009 02:53 AM (GMT)
Perhaps you guys could help me with a problem. I want to write sprite behaviours in C instead of assembly (for mods), because it is faster... but I don't know much about C.

I have maps of the keen1 executable that give the address (16 bit segment:offset) locations of pretty much all the variables and procedures found in keen1.exe.

What I want to do write a function in C, but be able to include the variable names, so that when the code is compiled, the references to memory are at the actual address in Keen1.exe. Also, I would want to be able to define the address of the C function.

Finally, this would have to be compiled for 16 bit real mode, although it could make use of 32-bit intstructions.

gerstrong - December 29, 2009 08:21 AM (GMT)
QUOTE
Perhaps you guys could help me with a problem. I want to write sprite behaviours in C instead of assembly (for mods), because it is faster... but I don't know much about C.

I have maps of the keen1 executable that give the address (16 bit segment:offset) locations of pretty much all the variables and procedures found in keen1.exe.


Hmm. I don't get your point. Are you really talking about sprite behaviours? We have information in the executable about tile behaviours. well, I trust, that you really mean sprite behaviours.

Do you really want to use C? I know C and C++ pretty well, also thanks to the development of CG (I learned a lot there) of it, and I can help you getting it run anyhow. I would use C++ for much shorter and elegant code, but I'm not sure, where you want to compile it.

For tile behaviours, that still is C part of CG, we use a struct with the behaviours of one tile and then make an array of it. I think that's the most simple and best way to it so. In C++ you could use std::maps, but I'm not sure, if it's better code.

QUOTE

What I want to do write a function in C, but be able to include the variable names, so that when the code is compiled, the references to memory are at the actual address in Keen1.exe. Also, I would want to be able to define the address of the C function.


How many adresses do you have? I still compare it to the tile behaviours of Keen?.exe. We just have one address (per Episode) and count down, getting all the elements of the tile behaviours we need. I can show you that code, maybe it helps you in the design of your program.

QUOTE

Finally, this would have to be compiled for 16 bit real mode, although it could make use of 32-bit intstructions.


Well, if that's so important for you, I would not use int then. Use the ones of SDL, if you use SDL (Uint8, Uint16, Uint32) or just #define "unsigned short" as WORD and "unsigned char" as BYTE, and create cases if different compiler behave too different. A problem about most 16-bit apps is that in the source code, they use int as 16-bit, and on 32-bit systems it's 32-bit. So when you compile the code on a newer compiler, it might work, but you might get very unhappy results.

I recently fixed UNLZEXE and I discovered that problem there. My version was meant for 16-bit. It runs well under DOSBox but not under my Linux64 enviroment. So I adapted the code and also improved the performance. I'm a lazy guy and also used a bit of C++, so I don't have to mess around with too many allocations and frees, but It's up to you. :-)


lemm - January 6, 2010 01:00 PM (GMT)
You guys probably have the physics wrapped up for keen1, but I just thought I would post this. This is a very important routine in determining how velocity translates into sprite movement.


I posted a thread over on keen modding about the sprite structs, and how velocity is translated into a pixel displacement.

Take x-axis for example. Sprites have an x velocity (sprite.vel_x) and a delta (in 256ths of a pixel, sprite.delta_x). At the start of every level loop, the variable sprite_sync is set to 0 and incremented once for every timer interrupt (do while sprite_sync < 6). When sprite sync reaches 6, then the timer loop is broken and the main level loop can start.

So during the level loop, we do the sprite think functions, which calculate the new vel_x for the sprite for this loop iteration.


Finally, for every sprite, the position update function is called (shown below). DELTA_X is updated by multiplying VEL_X by SPRITE_SYNC, and adding that displacement to POSITION_X (position_x is a doubleword measured in 256ths of a pixel from the upper left corner of the level map; this correction is done in the procedure check_ground, you can see the call below)


Then the level loop repeats, and sprite sync = 0 again.


Hope that helps.


CODE

seg000:2C6D compute_sprite_delta proc near         ; CODE XREF: think_21_yorp_walk+65p
seg000:2C6D                                        ; think_yorp_look+54p ...
seg000:2C6D                 push    bp
seg000:2C6E                 mov     bp, sp
seg000:2C70                 mov     ax, temp_sprite.vel_x
seg000:2C73                 imul    sprite_sync
seg000:2C77                 mov     dx, temp_sprite.delta_x
seg000:2C7B                 add     dx, ax
seg000:2C7D                 mov     temp_sprite.delta_x, dx
seg000:2C81                 mov     ax, temp_sprite.vel_y
seg000:2C84                 imul    sprite_sync
seg000:2C88                 mov     dx, temp_sprite.delta_y
seg000:2C8C                 add     dx, ax
seg000:2C8E                 mov     temp_sprite.delta_y, dx
seg000:2C92                 call    check_ground
seg000:2C95                 pop     bp
seg000:2C96                 retn
seg000:2C96 compute_sprite_delta endp
seg000:2C96

lemm - March 21, 2010 10:28 PM (GMT)
Here is compilable code of all the in level functions and global variables.

http://lemm.pastebin.com/njxCwmB9
http://lemm.pastebin.com/VutVbtqX
http://lemm.pastebin.com/BtvZmzcS

gerstrong - March 26, 2010 07:36 AM (GMT)
Hi lemm,

thanks a lot for your help. I have been a bit busy with my Uni-stuff, but I will be looking close on what you have done...

If I get it right the delta is something like the boost, or speed increaser. 256th is very close. Before we changed things we had 32 parts per pixel, and we weren't able to reproduce the physics. Now we have 512 which is really much, as I have seen. I like it that way.

I will take a closer look at the code you pasted. Great work! Maybe we can learn some stuff and still improve it even more. I will try to implement more galaxy stuff, what I always wanted, but maybe Pizza and the others want to improve stuff in the vorticons, anyway... We will take a deeper look! :-)

Thanks again!



Hosted for free by InvisionFree