Patch:Jump conditions

From KeenWiki
Jump to: navigation, search

Jump conditions are conditions required in a patch for a 'choice' to be made, such as whether or not to display a graphic or to exit a level or add points. They are very important in the Keen executable, but rarely patched. The general format is a value, followed by a 'condition' followed by the number of bytes to jump. Any or all of these may be in a patch. They are the equivalent of the BASIC 'IF... THEN' command. As an example, the following patch changes the BWB level in Keen 4. (The level that can be played infinitely many times, unlike other levels which get marked as done.):


Patch: Keen 4
#BwB level:
%patch $60FD $11 $77 $5B
#Level $xx and above

The various kinds of jump are as follows. They will be explained gradually.

$xx $72 $yy      JB:  Jump if below
$xx $73 $yy      JAE: Jump if above or equal
$xx $74 $yy      JE:  Jump if equal
$xx $76 $yy      JBE: Jump if below or equal
$xx $77 $yy      JA:  Jump if above
$xx $7C $yy      JL:  Jump if less
$xx $7D $yy      JGE: Jump if greater or equal
$xx $7E $yy      JLE: Jump if less or equal
$xx $7F $yy      JG:  Jump if greater
$xx $75 $yy      JNE: Jump if not equal
    $EB $yy      JMP: Jump short
    $E9 $yy $yy  JMP: Jump near
    $E8 $yy $yy       CALL: Call near
    $9A $yyyyyyyyRL   CALL FAR: Call far; address is absolute

Our example patch checks the number of the level Keen has just exited. IF the number is above ($77) 17 ($11) THEN it jumps 91 ($5B) bytes ahead in the code. These 91 bytes mark a level as done, so in this case, any level above 17 Keen enters will not be marked done when he exits, and can be played again as many times as he likes.

The 'distance' jumped is measured from the byte AFTER the amount jumped. So our example patch jumps to $615B. ($6100 + $5B) The jump is thus 'relative', though 'call far' is 'absolute' (See below.)


Above\below vs more\less

Notice there are two sets of jumps that seem to do the same thing. This is because Keen uses two types of values (integers); signed and unsigned. Signed integers can be negative ($FF = -1, $F0 = -16...) whereas unsigned integers can't. ($FF = 255 $F0 = 240). This doesn't usually have too much of an effect on patches, as most use small positive values, which are the same in both systems, but it can affect more complex patches. (Specifically a 1-byte value of $80 or a 2-byte value of $8000 or more becomes negative if it is signed, causing problems if you're not careful.)

The two sets of jumps thus treat certain values differently, depending on which is used. ($FF is smaller than 0 if signed (-1), but bigger than 0 if unsigned (255)) The greater/less jumps are for signed integers (-32768 to 32767 in the case of a 16-bit integer), while the above/below jumps are for unsigned integers (0 to 65535)


Conditions

Most jumps have conditions, that is, they check something before jumping. Some do not. Specifically the 'jump short', 'jump near' and both the 'calls' don't check anything. When they are encountered something ALWAYS happens. This can be seen in some patches. As an example, if we wanted ALL levels to be infinitely replayable we would simply change our example patch to:

Patch: Keen 4
#All levels are BWB-type levels
%patch $60FD $11 $EB $5B
#Level $xx and above


Combining jumps

You will notice that some of the 'jumps' have a TWO-byte jump distance, whereas most have just one. You may also wonder why a program needs mandatory jumps, if some code is always going to be skipped, why have it in there in the first place?

There are a number of reasons, based on the slightly odd way the jumps work. You will notice that ALL of the 'conditional jumps' have only one-byte jump distances. This means they longest they can jump is 128 bytes in either direction ($7F = 127 bytes forward, $80 = 128 bytes backwards) But what if you want to skip more code than that? In general a conditional jump AND a mandatory jump are used. Consider the hypothetical patch below:

Patch: Hypothetical patch
#Hypothetical patch
%patch $ABCD $04 $74 $03 $E9 $0280W

Here if some value is equal to four, a jump itself is jumped. Another way to look at this is 'If a value is NOT equal to four, jump $0208' This lets a programmer make 'big conditional jumps', with the annoying side-effect of the jump being 'reversed' A lot of these tend to turn up, meaning that an unwary patcher may be dealing with a patch that does the opposite of what he thinks it does. It thus pays to pay special attention to what an example patch does before changing it.


Removing jumps

Another trick that can be used is to remove an existing jump entirely; this is usually done by replacing the jump and jump distance by $90. In our example patch, if we wanted to make all levels completable we would use this patch:

Patch: Keen 4
#NO levels are BWB-type levels:
%patch $60FD $11 {$90 $90
#Level $xx and above


Here the jump has been eliminated, no matter what level Keen exits, no code will be skipped. This too will often be seen in patches and can be done with any jump. For 'call near' and 'jump near' we would need three bytes of $90 to properly remove the jump and for 'call far', FIVE bytes. (It is also worth noting that a one-byte value of say $06 is a two-byte value of $06 $00 or $0006W when patching.)


Calls

The last two 'jumps' in the list are not jumps at all, but 'calls'; they are always executed when encountered and are the equivalent of GOSUB and SUB in BASIC. (That is the program will go to where it is directed, do something, then return.) The 'call near' is similar to 'jump near', but is much more common and is often used to jump backwards in the code. (The jump distance is signed,.) The 'call far' is more complex.

Call far goes to an absolute address in the code. Its' address is considered to have two parts, a 'segment' and an 'offset', each two bytes long. The actual location in the code is [segment * 256] + [offset] This is done because executables require addresses of more than 2 bytes and, for simplicity, they are divided into segments of varying lengths.

Because of the way CKPatch works, call far patches can be done in a number of ways. The first thing to note is that because the code is patched into memory, all the addresses in it are addresses in memory, and thus change. CKPatch automatically alters unpatched call far values to work with this, by adding another value, z, to the segment locations. However it doesn't do this to normal patches, meaning that for some time it was impossible to patch call far, as z is different each time CKPatch is run. However it is now possible for the Galaxy series to use 'relative word' and 'relative long' patches, RW and RL. The patch $12 $34 $56 $78 can thus be presented as $3412W $7856RW or $78563412RL, indicating to CKPatch that the patch is 'relative' and should be adjusted when patching into memory.


Links

[1] - More information on x86 assembly program control flow instructions.