Warp from command line?

Tools, assembly, and file formats.
Post Reply
User avatar
Malvineous
Posts: 113
Joined: Sat Mar 13, 2004 12:54 am
Location: Brisbane, Australia
Contact:

Warp from command line?

Post by Malvineous »

Hi all,

This is slightly off-topic as it's related to non-Keen games (AFAIK Keen already supports it.)

As some of you know I'm working on a level editor for a number of different games, and I'd like to add a shortcut key to load the level currently being edited in the game so you can see how it's playing. IIRC Keen can do this by writing certain values to a TEDLEVEL.CKx file and/or starting the game with the TEDLEVEL parameter, which skips all the intro and menu screens and starts right at the beginning of a given level, ready to play.

Does anyone know if this is possible with any other games? Or, more to the point, how much effort would be involved in patching a game's .exe file to immediately start a new game on a specific level? I'm thinking that writing a CKPatch-style patch containing the target level number and launching the game would be a nice way to go about it, but of course this would require a certain degree of reverse engineering a bunch of different games which would be very time consuming (I assume - it would be if I was doing it!)

For the record, the games I am initially focusing on for this sort of thing are Cosmo, Monster Bash and Dangerous Dave 1.

Any thoughts?
NY00123
Posts: 85
Joined: Thu Sep 24, 2009 8:03 am

Post by NY00123 »

Hey,

This post has made me want to have a little attempt at solving this for Dangerous Dave. Lets tell the few results of this.

I do assume that, before uncompression with UNLZEXE, the DAVE.EXE's filesize is 76586 bytes and its MD5SUM is 10ac35dd6bc6314cd5caf08a4ffb4275. It's probably the only official release, but still checking...

Well, if one just wants to change the number of the map to start in, looks like it can be done by changing the word value on address 0x51A3 (same address format as of CKPatch: After EXE uncompression and excluding the header).

Unfortunately, I haven't found a similar thing for the so-called "warp zones" for now. Some other code appears to be used for these, and it could be non-trivial to simulate that on startup.
OK, I've attempted to "cheat" by the way of changing the start position and the like (while loading the map as an "ordinary" one, not a warp zone). Unfortunately, it seems to mess up the monsters' initial positions (which seem to depend on the viewport's location).
At least, though, I've come up with a few details which might help (or at least the newly revealed ones); And possibly not just for what you've asked for here.

---------------------

Alright, lets list a few things I've found out (at least as guesses).
I'd just note that different units of length might be used, depending on the specific entry (e.g. tiles vs pixels).

Variables:
dseg:56EA - Player's initial column on screen. MIGHT change during gameplay, although I haven't fully validated that.
dseg:56EC - Player's initial row on screen. Similarly, might change as well.
dseg:56F4 - Current level map.
dseg:573C - 1 if we're in a warp zone, and 0 otherwise.
dseg:57A0 - On level load, stores viewport's position (leftmost column of map seen). Maybe changing during gameplay too.
dseg:6152 - While in a warp zone, this stores the original map to get back to.

Tables:
- Original map tables:
dseg:0122 - Player's initial column on screen.
dseg:0136 - Player's initial row.
- Warp zone tables, indexed by the *original* map (not the warp zone map):
dseg:016A - Warp zone map numbers; Counted beginning from 1, instead of 0.
dseg:0192 - Initial viewport position.
dseg:01A6 - Player's initial column.

A few values set in the beginning (upon starting a new game):
0x51A3 - Initial map number. By default, 0.
0x51A9 - Initial viewport position (might be ignored?). By default, 0.
0x51B5 - Boolean value telling if we're in a warp zone or not. By default, 0.

A few more values:
0x148A - Initial viewport position for ordinary maps, set after death. By default, 0.
0x1510 - Player's initial row for all warp zones (shared), set after death. By default, 0x10.
0x34CD - Player's initial row for all warp zones, set on startup. By default, 0x10.
0x4F37 - Initial viewport position for ordinary maps, set on startup. By default, 0.


---------------------

Lets hope this helps out for DDave modding!
User avatar
Malvineous
Posts: 113
Joined: Sat Mar 13, 2004 12:54 am
Location: Brisbane, Australia
Contact:

Post by Malvineous »

Wow, very impressive! What tools are you using to find out this sort of detail so quickly?

With the warp zones you could possibly fake them by starting on the main level and then changing the X and Y coordinates to put the player at the correct starting point. Sounds like the viewport might need to change too.

In your 'original map tables' you seem to have discovered how to change the player's starting point - is that correct? Because this is currently fixed, so it would be great if it could be changed. Likewise the location of enemies is also fixed (see DDave level format) so being able to change that would be great!

By the way, are there any existing CKPatch style tools that work with any game, that could be used for this sort of patching? I was going to write my own but if they are already out there, there's no point reinventing the wheel.
NY00123
Posts: 85
Joined: Thu Sep 24, 2009 8:03 am

Post by NY00123 »

Hey again, and thanks for your compliment!

Well, for the tools, I haven't really used much more than UNLZEXE and some freeware release of IDA Pro. Looking for a sequence of numbers with some meaning can help (i.e. warp zone map numbers), although I've accidentally found something a bit different (warp zone map numbers shifted by 1).

For your other questions regarding DDave, here are a few things that I should note:
- The initial viewport for ordinary maps is shared among all of them, and is stored in two locations (one on map load, and another after player death).
- The initial player row (of start position) is shared among all of the warp zone maps. Stored twice as with the viewport for ordinary maps.
- On load of an ordinary map, the monsters' locations don't seem to be changed, and it's actually assumed that the viewport is initially 0. If it's not 0, the monsters will be in wrong locations in game.
- On load of a warp zone, the monsters' locations are shifted to the left according to the initial viewport position, so their locations are corrected.
- On player's death, the monsters' are shifted to the right, fitting the case that the viewport position changes to 0.
- Furthermore, on player's death in a warp zone, the monsters *also* shift to the left, in order to accommodate the warpzone's unique initial viewport position. So there are two shifts applied one after the other.

Now, there are two issues which may remain:
- I haven't found a way to determine, for a specific map, if Dave falls on startup or not.
- I have also not found where are the monsters' locations stored.

Lets assume that these are solved, though. Alternatively, simply support levels with no monsters, while also not caring about the player's falling behavior on startup.
Then, it may be possible to simulate a warp zone by applying the following:
- Change 0x51A3 (initial map number) to the warp zone map index.
- Change 0x51B5 (warp zone boolean) to 1. It wouldn't take an affect right on map load, but it would on player's death. It also avoids the player from getting out of the map into a randomly defined warp zone.
- Set the original map to return to (this is needed if we set 0x51B5 to 1). By default, it isn't done on startup. But, you can solve this by writing the following bytes to address 0x51A5: "C7 06 52 61 XX XX". Of course, "XX XX" refers to the original map number. Note that this overwrites a command setting viewport on game startup, but it doesn't seem to be useful anyway. Or so I guess...
- Change the relevant player column entry in dseg:0122 for current map to be the same as in dseg:01A6 (indexed by the original map).
- Change the player row entry in dseg:0136 for current map to be the same as in 0x34CD (initial row for all warp zones on startup).
- Change the startup viewport in 0x4F37 to be the same as in the relevant entry in dseg:0192 (again indexed by original map).
- **Currently stays unsolved** shift the monsters accordingly; Just for the testing.
- **Also stays unsolved** determine if the player falls on level loading or not in some way.

Lets hope I haven't missed anything...

To finish, a short look into CKPatch shows that you can probably modify it a bit to your needs. My guess is that you need to create a new program based on the file CKXPATCH.INC, only that it'd rather be a PAS file, with a main program calling the Main procedure. Furthermore, it wouldn't refer to things like Version and ExecutableFileSpec, and would rather figure out the EXE name from the command line.

*** UPDATE (August 22th, 2011) ***
Ugh, a nightmare has almost come true for some (at least, lets hope just almost...)
Basically, had a little mistake with a number above. Regarding the command "C7 06 52 61 XX XX", it should be written to address 0x51A5, *not* 0x51A9. After all, we want to overwrite a whole assembly command, not just some value being assigned...
Last edited by NY00123 on Mon Aug 22, 2011 8:15 pm, edited 1 time in total.
User avatar
Malvineous
Posts: 113
Joined: Sat Mar 13, 2004 12:54 am
Location: Brisbane, Australia
Contact:

Post by Malvineous »

Wow, thanks again for all the useful info! It will take me some time to digest it all, but in the mean time I am not sure what you mean by whether the player falls on level loading - the player will fall if there is nothing to stand on (I just verified this by modding level 4 to place a block under the starting location and the player doesn't fall.)

I might have to give some thought to the testing procedure though, since as you point out the levels share files. If I have an option to test the file currently being edited it could be two different levels, and one of them could even be a warp level, to further complicate things. Hmm...

With CKPatch I was thinking of porting it to C to make it a bit easier to maintain, rather than trying to make too many changes to it, but given its complexity this won't be a quick thing to do. Plus a 16-bit DOS compiler isn't exactly common any more either!

Anyway, thanks again for all the DDave info! Now to figure out how to get it all into Camoto... :-)
NY00123
Posts: 85
Joined: Thu Sep 24, 2009 8:03 am

Post by NY00123 »

Well, at least for me, upon loading map 1 (second level) in a faked "warp zone", the player didn't fall; Just like what happens as you enter map 1 for the very first time in an unmodified game.

There are a few values shared indeed (like the player's initial row in the warp zones), but at least these are simple values and not whole buffers of data. At best, I guess you could let the user editing levels choose such common values; That is, if not just keep them intact.

Oh, yeah, some version of Turbo Pascal is required for CKPATCH. I *believe* there aren't many code changes to apply; And as an alternative, you can also just make a DDave-specific PAS based on CK4PATCH.PAS, BM1PATCH.PAS or some other one. We'll see what's going to happen a bit later.
User avatar
Fleexy
Site Admin
Posts: 490
Joined: Fri Dec 12, 2008 1:33 am
Location: Bloogton Tower

Post by Fleexy »

I have a generic 16-bit patcher, but it's pretty difficult to use :/ I'll link if you want.
User avatar
Frenkel
Posts: 38
Joined: Sun Feb 15, 2004 10:18 am
Location: Netherlands
Contact:

Post by Frenkel »

In Prince of Persia 1 you can warp to different levels by using the parameter megahit (v1.0) or improved (v1.3 and v1.4) followed by the levelnumber. For example Prince.exe improved 3 lets you start level 3 in PoP v1.3 and v1.4. Number 13 goes to the second part of level 12 and number 14 goes to the final level.
lemm
Posts: 554
Joined: Sun Jul 05, 2009 12:32 pm

Post by lemm »

An alternative would be to give the editor a "put the latest level edited into slot 1" mode, and the option to edit a dummy world map that has level 1 right next to the start.


I think that making custom patches for each game would be a headache, and it wouldn't save THAT much time anyway since many games have a level warp function already.


If you wanna get fancy, you could automate stuff by having your program send keystrokes to Dosbox.
NY00123
Posts: 85
Joined: Thu Sep 24, 2009 8:03 am

Post by NY00123 »

Before I get into the actual reply, I'd just like to emphasize here: I've had a mistake in one of the numbers in a previous post of mine in this topic. Knowing the lots of trouble this could cause for someone, I'm very sorry for that. The post has been edited accordingly (now 0x51A5 is written and not 0x51A9).
At least, though, I may now confirm that it's indeed corrected. Reason being some test, mentioned later in this post (with the 6th level).
lemm wrote:An alternative would be to give the editor a "put the latest level edited into slot 1" mode, and the option to edit a dummy world map that has level 1 right next to the start.


I think that making custom patches for each game would be a headache, and it wouldn't save THAT much time anyway since many games have a level warp function already.
In enough circumstances, I do agree with the following: The simpler, the better. And so, as long as there isn't a lot of hardcoded stuff for specific levels, your suggestion of swapping an edited level with level 1 (and using a template world map) has the potential to be very useful.
I can see it becoming problematic wherever there's some level-specific hardcoded stuff (e.g. Dangerous Dave monsters, Bio Menace in-game textual messages).

------

Back to Dangerous Dave, well, looks like I've solved the two remaining issues left for simulating warp zones right in the beginning of the game. I could have a short working test of the 6th level warp zone (ordinarily accessible from the 8th). OK lets write the two points...

*** Information about monsters:
To locate that for some level, begin by denoting L the current level number (counting from 0 as usual). Then, with all numbers in hex format, dseg:(L*50+496) appears to store monsters-related data for level L.
Now, each level can have a maximum of four monsters, as revealed from the data.
For a specific level, it begins with four word-sized booleans, each of them telling if we want an additional monster (01 00) or not (00 00). The following four words in dseg:(L*50+49E) determine the monsters' respective initial columns, and the four words following these in dseg:(L*50+4A6) determine the rows. Afterwards, there's some more data, which may be monster-specific. It could affect shooting rate and more, although not all has been revealed as of now.

*** Do we want Dave to fall right in the beginning of some level or not?
Here are a few things revealed as of now:
- dseg:6156 - A variable which appears to store a kind of a player status, using bits for different flags. For now, it can be used to determine if the player falls on map (re)start or not.
- dseg:118 - Relevant to ordinary maps (not warp zones), it's an array of values determining the falling behavior. A value of 0x28 is set if we want Dave to fall, and 0x24 otherwise.
- 0x1516 - Behavior set in a warp zone after the player dies. By default, 0x28.
- 0x29AC - Another place where a value is assigned to the variable dseg:6156, for a currently unknown reason. The default value is 0.
- 0x398F - Yet another such place, but with a value of 0x40.
- For setting up the behavior on all warp zones right on startup (but not after death), things are a bit more tricky.
The short answer: Keep 0x350B as 08 00 if you want Dave to fall in *all* warp zones, and change it to 04 00 otherwise (Dave not falling, again in *all* warp zones).
The long answer: In a surrounding piece of code, the value is copied to some where; Then, a bitwise 'and' operator is applied on it with 0xE0. And then, an 'or' operator is applied on the current result with 8. Finally, the last result is stored back into the variable.
What can be changed in 0x350B is the constant value that the 'or' operator applies a result with.

------

So, to conclude, there should now be a way to fully simulate a specific warp zone from within the level editor. The main catch is the need to shift the monsters. Basically, get the warp zone viewport position, multiply it by 0x10 (this is important!), and then subtract the result from each of the monsters' respective initial columns. If for a specific monster one obtains a negative value, simply add to it the value of 0x10000 (or cast to an unsigned 16-bit word).
Post Reply