03/2003 by Zadig.

Tools needed:

  • A disassembler: reveng of course :p.
  • A debugger: db.
  • An assembler: nasm.

Introduction:

Hello dear reader,
In this tut I will explain how to modify the way netpositive places new windows on the screen. To understand better, here is the description of the user how asked me for this reverse: "I wonder if you could help me fix something on NetPositive? I would like to get rid of its automatic new window tiling feature, so that each new window opens at the same size as the first window."
That's pretty clear, so here is what we have to do to achieve this:

  • Find how not to tile a new window (of course ;-)
  • Find where is stored the original window size.
  • Open a new window with the size we want.
  • Optional: add a preference settings checkbox to enable/disable it.


I- Analysis of the current code

1- Understanding what netpositive does

2- Finding the function to patch

3- Undertanding PositionWindowRect

II- Reverse

1- How to proceed

2- Writing the patch

III- Conclusion

I- Analysis of the current code:

1- Understanding what netpositive does

The first thing to do is understand how netpositive displays windows. So let's play with it: Launch it, open new windows, del them, move them, relaunch netpositive... Well, it is not really obvious. Here is what I understood, but the behavement is strange sometimes:

  • The window size is the one of the original parent window.
  • All windows are cascaded from the position of the first window that has been created.
The main point here is that it is very anoying when you use it! Anyway we have enought element to go further: We know that there is a way to know the parent window size, and probably the position of any window that was created.

2- Finding the function to patch

Now let's disassemble netpositive and search for interesting resources. Seaching for "window" is probably a good way to start. Here is what we get from the classes list:

  • HTMLView::CloneInNewWindow(void)
  • HTMLWindow constructor and HTMLWindow::MessageReceived(BMessage *)
  • NetPositive::NewWindow(void)
  • NetPositive::NewWindowFromError(char *, char *, unsigned long)
  • NetPositive::NewWindowFromResource(UResourceImp *, bool, unsigned long, bool, bool, bool, bool, BRect *, bool, int, BMessage *)
  • NetPositive::NewWindowFromURL(BString const &, unsigned long, char const *, bool, bool, bool, BRect *, bool, int, char const *, BMessage *)
The CloneInNewWindow method seems to be exactly what we need because it is what netpositive does when we open a new window. The "HTMLWindow::MessageReceived" method may be interesting to trap the "CTRL+N" keyboard event. The other methods are self explanatory I think. So let have a look to CloneInNewWindow:

      ...start code...
0007ef7c:   mov    0x8(%ebp),%eax      ; param 0 (Object handle)
0007ef7f:   mov    0x1c4(%eax),%edx    ; edx = HTMLView.use_url
0007ef85:   test   %edx,%edx           ; use url?
0007ef87:   je     7ef94               ; if not then use resource
0007ef89:   mov    0xfffffffc(%edx),%edx
0007ef8c:   and    $0x7fffffff,%edx
0007ef92:   jmp    7ef96 

Referenced by (conditionnal) jump(s) at Address(es):
	0007EF87  
0007ef94:   xor    %edx,%edx           ; edx = 0

Referenced by (conditionnal) jump(s) at Address(es):
	0007EF92  
0007ef96:   test   %edx,%edx           ; if edx == 0
0007ef98:   jle    7efd7               ; use resource
0007ef9a:   testb  $0x2,0x2d4(%eax)    ; if HTMLView.xxx == 1
0007efa1:   jne    7efd7               ; use resource
      ...some code...
0007efca:   add    $0x1c4,%eax
0007efcf:   push   %eax

Reference to function "NetPositive::NewWindowFromURL(BString const &, unsigned long, 
         char const *, bool, bool, bool, BRect *, bool, int, char const *, BMessage *)"
0007efd0:   call   9cb18 
0007efd5:   jmp    7f00d 

Referenced by (conditionnal) jump(s) at Address(es):
	0007EF98  0007EFA1  
0007efd7:   push   $0x0
0007efd9:   push   $0x2
      ...some code...
Reference to function "NetPositive::NewWindowFromResource(UResourceImp *, bool, 
         unsigned long, bool, bool, bool, bool, BRect *, bool, int, BMessage *)"
0007f008:   call   9c874 

Referenced by (conditionnal) jump(s) at Address(es):
	0007EFD5  
0007f00d:            ...end of function...

Great! Depending on one or two class attributes, either NewWindowFromURL or NewWindowFromResource is called. Let's put a breakpoint on these functions to know which one is called and when. What I saw is that only NewWindowFromResource is called, so let's see what it does.
I won't print it all here but reading it you may understand what it does:

  • Search for the resource param content type
  • If content type is not found, get default window position/size
  • Call "PositionWindowRect"
  • Print window from "PositionWindowRect" result

Note:
When writing this tutorial there was a bug in reveng param's references: The NewWindowFromResource params are all shifted because the first param is not the object handle. Be carefull!

So once again I used db to know where we go throught the code: In fact we never find the resource content type, so we always go here:

0009c9d7:   lea    0xfffffff0(%ebp),%esi
0009c9da:   push   %esi
0009c9db:   mov    %ebx,%eax
0009c9dd:   add    $0xfffdcb83,%eax

Reference to string "DefaultBrowserWindowRect"
0009c9e2:   push   %eax
0009c9e3:   mov    0x134c(%ebx),%eax
0009c9e9:   push   %eax

Reference to function "BMessage::FindRect(char const *, BRect *) const"
0009c9ea:   call   50cfc 
0009c9ef:   push   %esi

Reference to function "PositionWindowRect(BRect *)"
0009c9f0:   call   9b960 

Here we get the default params, and call PositionWindowRect to know where to print the new window. This means that what we have to patch is PositionWindowRect.

3- Understanding PositionWindowRect

Now let's try to understand what is done in this function. We don't need to understand it all but it will help to build the patch.

      ...start code...
0009b984:   push   %edi

Reference to function "BScreen::BScreen(screen_id)"
0009b985:   call   50a0c 
0009b98a:   push   %edi 
0009b98b:   lea    0xffffffec(%ebp),%eax
0009b98e:   push   %eax

Reference to function "BScreen::Frame(void)" ; Get BRect of the screen (=> screen size);
0009b98f:   call   4e57c 
First we get the size of the screen. This size is returned in the floating point registers.
0009b994:   flds   (%esi)  ; param 1 left
0009b996:   fstps  0xffffffdc(%ebp) ; 0xffffffdc(%ebp) = param 1 value;
0009b999:   flds   0x4(%esi)  ; param 1 top
0009b99c:   fstps  0xffffffe0(%ebp)
0009b99f:   flds   0x8(%esi)  ; param 1 right
0009b9a2:   fstps  0xffffffe4(%ebp)
0009b9a5:   flds   0xc(%esi)  ; param 1 bottom
0009b9a8:   fstps  0xffffffe8(%ebp)
0009b9ab:   jmp    9ba3e 
Here we load the default coordinates from the BRect param to the fp registers, and store them at 0xffffffdc(%ebp). The next piece of code is the core of a loop that will step throught all open netpositive windows:
Referenced by (conditionnal) jump(s) at Address(es):
	0009BA65  
0009b9b0:   push   %edx             ; Window handle
0009b9b1:   lea    0xffffffcc(%ebp),%eax
0009b9b4:   push   %eax

Reference to function "BWindow::Frame(void) const"
0009b9b5:   call   5179c      ; Get window position
0009b9ba:   flds   0xffffffd0(%ebp) 
0009b9bd:   flds   0x4(%esi)
0009b9c0:   add    $0x4,%esp
0009b9c3:   fucompp                 ; compare fp registers
0009b9c5:   fnstsw %ax              ; store status word
0009b9c7:   and    $0x44,%ah        
0009b9ca:   xor    $0x40,%ah        ; check a status bit
0009b9cd:   jne    9ba48            ; get next window
0009b9cf:   flds   0xffffffcc(%ebp) 
0009b9d2:   flds   (%esi)           
0009b9d4:   fucompp 
0009b9d6:   fnstsw %ax
0009b9d8:   and    $0x44,%ah
0009b9db:   xor    $0x40,%ah
0009b9de:   jne    9ba48      ; get next window
0009b9e0:   flds   0xfffdcc0c(%ebx)
0009b9e6:   sub    $0x4,%esp
0009b9e9:   fsts   (%esp,1)   ; push y offset (25)
0009b9ec:   sub    $0x4,%esp
0009b9ef:   fstps  (%esp,1)   ; push x offset (25)
0009b9f2:   push   %esi

Reference to function "BRect::OffsetBy(float, float)"
0009b9f3:   call   4fd9c 
0009b9f8:   flds   0xfffffff8(%ebp) ; screen bottom
0009b9fb:   fdivs  0xfffdcc10(%ebx) ; screen bottom /= 3
0009ba01:   fadd   %st(0),%st       ; screen bottom *= 2
0009ba03:   add    $0xc,%esp
0009ba06:   fcomps 0x4(%esi)
0009ba09:   fnstsw %ax
0009ba0b:   and    $0x45,%ah
0009ba0e:   cmp    $0x1,%ah
0009ba11:   jne    9ba41            ; get first window
0009ba13:   flds   0xffffffdc(%ebp)
0009ba16:   fstps  (%esi)
0009ba18:   flds   0xffffffe0(%ebp)
0009ba1b:   fstps  0x4(%esi)
0009ba1e:   flds   0xffffffe4(%ebp)
0009ba21:   fstps  0x8(%esi)
0009ba24:   flds   0xffffffe8(%ebp)
0009ba27:   fstps  0xc(%esi)
0009ba2a:   push   $0x0
0009ba2c:   flds   0xfffdcc14(%ebx)
0009ba32:   sub    $0x4,%esp
0009ba35:   fstps  (%esp,1)
0009ba38:   push   %esi

Reference to function "BRect::OffsetBy(float, float)"
0009ba39:   call   4fd9c 
What is done first is getting the coordinates of the window by calling BWindow::Frame. The result of this function can be found in the floating-point registers. Then some comparisons are done to know if the current window may be the one that we use as a reference. I didn't understand exactly what was compared with what, and using db didn't help much. I still have to learn asm!
Anyway if the window does not match, we continue in the loop with the next window. On the other way, the window is moved down and rigth (from 9b9e0 to 9ba38). There is an exception to this: In one case (9ba11) we restart the loop from the first window. Let's continue in this code:
Referenced by (conditionnal) jump(s) at Address(es):
	0009B9AB  
0009ba3e:   add    $0xc,%esp

Referenced by (conditionnal) jump(s) at Address(es):
	0009BA11  
0009ba41:   movl   $0x0,0xffffffc8(%ebp)
This is where we fall when we begin the loop or when we restart with the first window. "0xffffffc8(%ebp)" contains the index of the current window.
Referenced by (conditionnal) jump(s) at Address(es):
	0009B9CD  0009B9DE  
0009ba48:   mov    0xffffffc8(%ebp),%eax  ; 0xffffffc8(%ebp) = current window index
0009ba4b:   push   %eax
0009ba4c:   inc    %eax                   ; current_window_index++
0009ba4d:   mov    %eax,0xffffffc8(%ebp)
0009ba50:   mov    0x12b0(%ebx),%eax      ; application handle
0009ba56:   mov    (%eax),%eax
0009ba58:   push   %eax

Reference to function "BApplication::WindowAt(long) const"
0009ba59:   call   4f49c 
0009ba5e:   mov    %eax,%edx     ; edx = window handle;
0009ba60:   add    $0x8,%esp
0009ba63:   test   %edx,%edx     ; handle valid?
0009ba65:   jne    9b9b0         ; get window position
This piece of code gets the current window handle and increases the current window index. Note that 0x12b0(%ebx) points to a global variable that should have been retrieved by reveng (grrr, one more bug to fix...). There is nothing more to say here.
0009ba6b:   flds   0xfffffff4(%ebp) ; screen rigth (db)
0009ba6e:   fcoms  0x8(%esi)
0009ba71:   fnstsw %ax
0009ba73:   and    $0x45,%ah
0009ba76:   cmp    $0x1,%ah
0009ba79:   jne    9ba86 ; jump 
0009ba7b:   fsubs  0xfffdcc18(%ebx)
0009ba81:   fstps  0x8(%esi)
0009ba84:   jmp    9ba88 

Referenced by (conditionnal) jump(s) at Address(es):
	0009BA79  
0009ba86:   fstp   %st(0)

Referenced by (conditionnal) jump(s) at Address(es):
	0009BA84  
0009ba88:   flds   0xfffffff8(%ebp) ; screen bottom (db)
0009ba8b:   fcoms  0xc(%esi)
0009ba8e:   fnstsw %ax
0009ba90:   and    $0x45,%ah
0009ba93:   cmp    $0x1,%ah
0009ba96:   jne    9baa3 ; jump 
0009ba98:   fsubs  0xfffdcc18(%ebx)
0009ba9e:   fstps  0xc(%esi)
0009baa1:   jmp    9baa5 
Finally a quick look to this let us think that it checks that the window does not go outside the screen. This is not important.
The interestiong point in this whole code is that the handle of the window that netpositive use as a reference is found by stepping throught all windows. What we will do shall be much more simple...

II- Reverse

1- How to proceed

Now we come to the interesting part. What do we have to do here? We must make PositionWindowRect return the BRect of the current window so that netpositive will create the new window at the same position that ours. To do this we must get the current window handle, get its position, and finally return it.
So let's search in the bebook the way to get the handle of the active window. Unfortunately, such a function does not exists. In fact we will have to use the same search algo than netpositive :-). We will step throught all windows, and ask Beos for each one if it is active. This is done with BWindow::IsActive. So here is what we will do:

while(window exists)
{
   if( window if active)
   {
      get window position;
      return(window position)         
   }
   get next window;
}
return(default window position);
That's a quite simple algorithm. Unfortunatelly BWindow::IsActive is not called in the original function, but don't panic: Searching for it in the disassembly gives us its address.
Finally there is one thing to take care of: The original function is 343 bytes big. So our patch code must be smaller than this.

2- Writing the patch

Following is the code of the patched function. There are a lot of comments, so it should be easy to understand. I won't detail the use of C++ API in asm code, read my previous tutorials for this. The code is written with intel syntax style because I used nasm to generate it (remember that src/dest are swapped compared to att syntax style).
Three last things:

  • "BWindow::Frame" has a hidden param: The pointer to the BRect that will be returned by this function.
  • "BWindow::Frame" frees the 4 bytes of the hidden parameter, so we must take care not to free them before leaving the function.
  • Since the final size of this code is 119 bytes, we won't have space troubles.
start:
   push ebp
   mov ebp, esp
   pusha

   ; step 1: get current window handle
   ; To do this we step throught all windows and check if the current one
   ; is active.
   mov      ecx, 0xFFFFFFFF      ; first window

NextWindow:
   inc      ecx                  ; ecx = window number
   mov      edx, [ebx + 12b0h]   ; edx = application handle
   mov      edx, [edx]
   push     ecx                  ; save window number
   
   push     ecx
   push     edx
   call     BApplication::WindowAt(long)
   add      esp, 8         ; restore stack...
   
   pop      ecx            ; and window number
   test     eax, eax       ; handle is NULL ?
   jz       WindowNotFound ; then finish here

   push     eax            ; save window handle
   push     ecx            ; save window number
   
   push     eax            ; window handle
   call     BWindow::IsActive(void)
   add      esp, 4         ; restore stack

   pop      ecx            ; get window number
   pop      edx            ; get window handle
   test     eax, eax       ; App is not active ?
   jz       NextWindow

   ; step 2: Get current window BRect
   
   sub      esp, 10h  ; allocate space for BRect
   push     edx ; Window handle
   lea      eax, [esp + 4]
   push     eax
   call     BWindow::Frame(void)  ; WARNING: This frees 4 bytes in stack...

   ; step 3: Return current window BRect
   
   mov      esi, [ebp + 8h]        ; esi = param 1 (BRect *)
   fld      dword [esp + 4]
   fstp     dword [esi]
   fld      dword [esp + 8]
   fstp     dword [esi + 4]
   fld      dword [esp + 12]
   fstp     dword [esi + 8]
   fld      dword [esp + 16]
   fstp     dword [esi + 12]

   add      esp, 14h ; 0x18 - 0x4 (the 4 bytes of BWindow::Frame)

WindowNotFound:
   popa
   pop   ebp
   ret

III- Conclusion

That's all for today. I didn't add the checkbox in the settings to enable or disable due to lack of time. By the way this would be a very interesting job: Using the Gui API, Adding a new segment in the binary (there is almost no padding space in netpositive)...
Concerning this tutorial, there was no new thing compared to my other tutorials, but I another reverse example is always a good thing, no ?

See you later, Zadig.