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)
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!