how to expand the janitors chattiness

Request patches for Keens 4-6.
Post Reply
User avatar
Nisaba
Posts: 320
Joined: Fri Jan 01, 2016 11:15 pm
Location: patch.pat
Contact:

how to expand the janitors chattiness

Post by Nisaba »

so I'm learning how to manipulate patches, how to handle executable segments, how to find new text locations and how to use pointers.
(by the way this is so much fun.)

but fortunately I came across some (skill?) limitation. Cause the only things I'm able to modify is already given stuff. what I'm not yet quite capable of is adding new stuff like the following:


I'd love to add a fifth text to the Janitor conversation. The Oracle Janitor text windows one to four already provide pointers for text elements but the fifth doesn't (at least not to my knowledge).
Is there a way to add text to the fifth window?
And how to add something like this in general?

any help is appreciated!
levellass
Posts: 3001
Joined: Wed Oct 11, 2006 12:03 pm
Location: Ngaruawahia New Zealand

Post by levellass »

This is a patch that sounds simple but is devilishly complicated. Let's take a look at it.

First we want to see what the game is doing with the fifth text window and how it varies from the fourth text window, which is closest by and has a text string. To do this we look up some existing patches or search for the fourth window's text string.

On the Keen Wiki the Oracle Janitor page has a section dealing with the windows. The 'Window setup' section shows things like the window height, and each set of patches seems to follow the same pattern. For the fourth window this is:

Code: Select all

#Janitor window 4
%patch $F268 $0008W  #Height
%patch $F26C $001AW  #Width
%patch $F2C3 $003CW  #Time displayed
%patch $F278 $006EW  #Bitmap displayed + 6 (Show BMP 104)
%patch $F29B $1F65RW #Text read from 1
%patch $F29F $0000W  #2

#Text
%patch $1F650 "Sorry.  You aren't" $0A
              "mad, are you?" $00
The fifth window deviates from this a bit, it has height and width, but it has two bitmaps displayed, not one, and no text string. Next we want to look at the code involved. The easiest way is to open a dump or use one of the tutoritools to extract the code.

Using the latter method to display it here we'll want to start at one byte *before* the window height patch (This *should* get all the window code, one byte before the first variable usually does.) We then get the following two chunks of window code:

Code: Select all

#Fourth window code
%patch $F267 $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL     $83 $C4 $04
             $B8 $006EW  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A $1D060C79RL
                 $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5 $30 $83 $06
             $33 $A5 $30 $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D $86 $38 $FF
             $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF $50 $9A
     $19310EBDRL     $83 $C4 $02 $9A $1D060A9BRL     $B8 $003CW  $50 $9A
             $1E0A0AD3RL     $83 $C4 $02 $9A $146008A8RL     $9A $14600EDFRL

Code: Select all

#Fifth window code
%patch $F2D8 $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL     $83 $C4 $04
             $B8 $006FW  $50 $FF $36 $DE $A7 $A1 $A533W  $03 $06 $31 $A5 $2D
             $30 $00 $50 $9A $1D060C79RL     $83 $C4 $06 $B8 $0071W  $50 $A1
             $A7DEW  $05 $18 $00 $50 $A1 $A533W  $03 $06 $31 $A5 $2D $28 $00
             $50 $9A $1D060C79RL     $83 $C4 $06 $9A $1D060A9BRL     $B8 $001EW
                 $50 $9A $1E0A0AD3RL     $83 $C4 $02 $9A $146008A8RL     $9A
             $14600EDFRL
Now I had to tidy things up a bit; the extractor is 'dumb' and when I go for the windows' codes it won't magically stop, but keep going. I deleted everything in the fourth window dump after I noticed the start of the fifth window's code and similarly a bit of extra code is run after the fifth window. (Specifically I checked the last piece of code the 4th window runs, $9A $14600EDFRL, this should be the end of the 5th window's code too.)

Now we can look at possible strategies. Initially I hoped I could just copy the 4th window's code into the 5th's space, this would make the 5th window a clone of the 4th that we could easily alter. (The bitmap issue would be simple to solve also, just take the special bitmap used in window 5 and make it larger, there's no real need to have a window with TWO bitmaps in it.)

However we see here immediately that the 5th window is *shorter* code-wise than the 4th, a simple copy is not possible. This means we'll have to alter the 5th window's code to try and fit in a bitmap.

Specifically we can try and replace the second bitmap code with the text string code from the 4th window. We can identify both strings of code because they'll start by loading the bitmap\text number and end with a $9A something. Doing this we find we are trying to replace:

Code: Select all

#Bitmap code
             $B8 $0071W  $50 $A1 $A7DEW  $05 $18 $00 $50 $A1 $A533W  $03 $06
             $31 $A5 $2D $28 $00 $50 $9A $1D060C79RL     $83 $C4 $06

#Text code
             $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D $86 $38 $FF $50 $9A $000037B3RL
                         $83 $C4 $08
The sizes check out; our replacement is smaller. So the next step is to pad the smaller code with $90s so they're the same length and we can just slot the new code in. (We could do something more complex. We need 8 $90s for this and then we can try our first patch:

Code: Select all

#First replacement attempt
%patch $F303 $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D $86 $38 $FF $50 $9A $000037B3RL
                         $83 $C4 $08 $90 $90 $90 $90 $90 $90 $90 $90
The result? The code doesn't crash the game... but all we seem to have done is remove the second bitmap... there's no text! What could have gone wrong? This is where the deviousness comes in. You see in these windows the text and bitmaps 'work together'; our replacement has drawn some text... offscreen at a random location.

Our next attempt involves replacing the first AND second bitmap codes with the bitmap-text combination from window 4. Following the same steps as before we get:

#Second replacement attempt

Code: Select all

%patch $F2D8 $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL     $83 $C4 $04
             $B8 $0071W  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A $1D060C79RL
                 $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5 $30 $83 $06
             $33 $A5 $30 $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D $86 $38 $FF
             $50 $9A $000037B3RL     $83 $C4 $08

The result? The same thing, no text. Evidently simple replacement isn't going to work. At this point we may think we've run out of options. The root problem is the 4th window is 13 bytes larger than the 5th. But we can be sneaky.

There's a bit of code repeated at the end of each window in the conversation, $B8 $001EW $50 $9A $1E0A0AD3RL $83 $C4 $02 $9A $146008A8RL $9A $14600EDFRL. This does stuff like wait for a keypress and clear the window. It's 22 bytes long. If we could somehow pack this away and call it at the end of each window then we'd free up 22 byes per window, minus the stuff needed to set things up.

Working through this we find we need to do this for three windows before we get enough space. I'm going to show the patch as it develops, which takes a lot of room.

The first step is to get the code for windows 3 and 4 and then copy 4 to make a new window 5. This leaves us 13 bytes 'in debt' as we well know. This time we're also keeping the 'ending code' after window 5, it's cleaner that way.

Code: Select all

#Copied code, 13 bytes too large
%patch $F1F4 $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL     $83 $C4 $04
             $B8 $006FW  $50 $FF $36 $DE $A7 $A1 $A533W  $03 $06 $31 $A5 $2D
             $30 $00 $50 $9A $1D060C79RL     $83 $C4 $06 $83 $2E $31 $A5 $30
             $83 $06 $35 $A5 $0C $B8 $1F63W  $50 $B8 $0000W  $50 $16 $8D $86
             $38 $FF $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF $50
             $9A $19310EBDRL     $83 $C4 $02 $9A $1D060A9BRL     $B8 $003CW 
             $50 $9A $1E0A0AD3RL     $83 $C4 $02 $9A $146008A8RL     $9A $14600EDFRL

                         $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL    
             $83 $C4 $04 $B8 $006EW  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A
             $1D060C79RL     $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5
             $30 $83 $06 $33 $A5 $30 $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D
             $86 $38 $FF $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF
             $50 $9A $19310EBDRL     $83 $C4 $02 $9A $1D060A9BRL     $B8 $003CW
                 $50 $9A $1E0A0AD3RL     $83 $C4 $02 $9A $146008A8RL     $9A
             $14600EDFRL     

                         $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL    
             $83 $C4 $04 $B8 $006EW  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A
             $1D060C79RL     $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5
             $30 $83 $06 $33 $A5 $30 $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D
             $86 $38 $FF $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF
             $50 $9A $19310EBDRL     $83 $C4 $02 $9A $1D060A9BRL     $B8 $003CW
                 $50 $9A $1E0A0AD3RL     $83 $C4 $02 $9A $146008A8RL     $9A
             $14600EDFRL     

                             $9A $06BD2092RL     $9A $12A614EDRL
                     $FF $36 $68 $7A $9A $06BD20C7RL     $83 $C4 $02 $8B $E5
             $5D $CB

Next up I'm going to delete the repeated code at the end of each window, replacing it with a dummy call $9A $00000000L that will, later, call the new code. This makes the code ((22 * 3) - 13) - (3 * 5) = 38 bytes smaller, but we have nothing to call.

Also since I'm not really altering window 3's code, just shortening it I'll delete it from the patch. Looking at the dump I see that the first change I'm making (Deleting the repeated code) starts at $F24C

Code: Select all

#Code with repeated sections removed
%patch $F24C $9A $00000000L

                         $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL    
             $83 $C4 $04 $B8 $006EW  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A
             $1D060C79RL     $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5
             $30 $83 $06 $33 $A5 $30 $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D
             $86 $38 $FF $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF
             $50 $9A $19310EBDRL     $83 $C4 $02  $9A $00000000L

                         $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL    
             $83 $C4 $04 $B8 $006EW  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A
             $1D060C79RL     $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5
             $30 $83 $06 $33 $A5 $30 $B8 $1F65W  $50 $B8 $0000W  $50 $16 $8D
             $86 $38 $FF $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF
             $50 $9A $19310EBDRL     $83 $C4 $02 $9A $00000000L

                             $9A $06BD2092RL     $9A $12A614EDRL
                     $FF $36 $68 $7A $9A $06BD20C7RL     $83 $C4 $02 $8B $E5
             $5D $CB
But NOW I must add back the deleted code in its own neat little chunk. I will put this after the shrunk code, the total code now being 38 - 22 - 2 = 14 bytes smaller, we'll actually end up with extra space!

The added code will be put at $F321, I can either calculate this from the ending of the old code or run my 'incomplete' patch and make a dump to show me. (The laziest and easiest method.)

Given this location I now know what call I need to use to get it. (I can either make one up, like $9A $0F320001RL or look at the executable segments to find the 'proper' call to use $9A $0E8F0A31RL)

I make the appropriate changes and run. The game crashes with a 'String exceeds width' error. No! How is that possible?! If you haven't paid *real* close attention to he windows patches on the wiki you'll never find the answer. The window texts use 'RW' patches, but the code extractor produces just plain 'W', we need to tweak that for everything to work. Doing THAT gives us the following, functioning patch:

Code: Select all

#Text in last Janitor window
#Shorten 3rd window
%patch $F24C $9A $0E8F0A31RL

#4th window
                         $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL    
             $83 $C4 $04 $B8 $006EW  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A
             $1D060C79RL     $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5
             $30 $83 $06 $33 $A5 $30 $B8 $1F65RW $50 $B8 $0000W  $50 $16 $8D
             $86 $38 $FF $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF
             $50 $9A $19310EBDRL     $83 $C4 $02 $9A $0E8F0A31RL

#New 5th window
                         $B8 $0008W  $50 $B8 $001AW  $50 $9A $19311070RL    
             $83 $C4 $04 $B8 $0071W  $50 $FF $36 $DE $A7 $FF $36 $33 $A5 $9A
             $1D060C79RL     $83 $C4 $06 $83 $06 $35 $A5 $06 $83 $2E $31 $A5
             $30 $83 $06 $33 $A5 $30 $B8 $1F65RW $50 $B8 $0000W  $50 $16 $8D
             $86 $38 $FF $50 $9A $000037B3RL     $83 $C4 $08 $8D $86 $38 $FF
             $50 $9A $19310EBDRL     $83 $C4 $02 $9A $0E8F0A31RL

                             $9A $06BD2092RL     $9A $12A614EDRL
                     $FF $36 $68 $7A $9A $06BD20C7RL     $83 $C4 $02 $8B $E5
             $5D $CB

#New code to make windows shorter
%patch $F321 $55 $8B $EC $9A $1D060A9BRL     $B8 $003CW  $50 $9A $1E0A0AD3RL 
             $83 $C4 $02 $9A $146008A8RL     $9A $14600EDFRL     $5D $CB
Note that there are limits to this, you cannot for example load something onto the stack ($B8 etc) and then call code, if you do THAT the game loads a 'return pointer' onto the stack as well which messes things up. This is why I limited myself to just the end code of the windows.

The setup here uses Keen's angry eyes bitmap and places it where the Janitor's head is. If you want it to be where Keen's head is you'll need to use a copy of window 3's code instead of window 4's, after all this mess a simple enough thing to do.

This patch also totally wrecks all 'default' Keen Wiki patches for windows 4 and 5. It also sets the 'wait time' for windows 3, 4 and 5 to the same value, $003CW, so you lose some flexibility.

At maximum effectiveness (Used on windows 1 and 2 too) this patch frees up enough space for an additional textless window.

You will notice that the 'new' text is just a copy of the 4th window's. Because of the odd way text patches are handled in these windows it can be tricky to find new text space. If you've patched the level names with one of my programs you'll probably have a decent amount of space you can use for an additional message in the 'usual' location. Otherwise you will have to resort to more devious means to add text such as making a sub-segment in the data segment.


And that is how a tricky patch can be done. WHEW!
User avatar
Nisaba
Posts: 320
Joined: Fri Jan 01, 2016 11:15 pm
Location: patch.pat
Contact:

Post by Nisaba »

oh wow. this is huge!
it might take a while until I grasp the comprehension of all this. talking of which: how long did it take you solve this puzzling thing? I'm truly astonished and very thankful for having you around and doing some magical tricks like this. lets see if I'm capable of deciphering this secret egg salad recipe.

Eighty percent of success is showing up, I mean: “Confidence is what you have before you understand the problem.â€￾
levellass
Posts: 3001
Joined: Wed Oct 11, 2006 12:03 pm
Location: Ngaruawahia New Zealand

Post by levellass »

This took about an hour to do, but mostly because I was typing things out and working through the failures. I also considered a few other failure options that might have been illuminating, for example cutting out the 'load\unload data' bytes to free up space. (For example since all the windows are the same size there's no real need to set window size each time, you could just load one window size at the conversation start then reuse it for each window before unloading it at the end.)

In fact looking at this, if I were ever to use the Janitor in something I think I can extend this to allow far more windows (It'd be all about creating an entire 'default window' code that is just called again and again, just changing the bitmap and text each time.)

I've got another bit of my patch tutorial on the write too that I should post.
Post Reply