03/2003 by Zadig.
Tools needed:
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:
I- Analysis of the current code
1- Understanding what netpositive does
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:
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:
...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:
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 9ba3eHere 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 4fd9cWhat 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!
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 positionThis 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 9baa5Finally a quick look to this let us think that it checks that the window does not go outside the screen. This is not important.
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.
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:
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
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.