NVIDIA Hack for Haiku and
Widescreen LCDs


Introduction

The problem is that your video card does not have a VESA resolution that matches your LCD's native resolution. For example, your highest VESA mode is 1280x1024, and your LCD has 1920x1080. The result is that the whole Haiku interface will be stretched and scaled to fill the screen. This is very undesirable because everything will be blurry and distorted. The solution is to disable this default up-sampling.

Requirements

1. Any recent nVidia card (7300GT, 8400GS, and 9800GT)

2. Any digital LCD connected via DVI (SyncMaster XL2270)

Method

The on-chip scaler can be disabled with a mere 12 bytes of machine code. This code must be executed in real mode, therefore it must be called early on in the boot process. A good place for it is the MBR (stage0) or the PBR (stage1). It may also be placed in your video card's BIOS or even your PC's BIOS, if you are feeling adventurous and want a permanent solution.

For simplicity, let's inject the disabler code into Haiku's PBR. This is the first sector of the partition (/dev/disk0s4 in this example) where Haiku is installed and it just happens to be full to the brim, as shown below.

Hexdump of the original PBR


00000000 0e 1f 0e 07 0e 17 bc 00 7c fa fc 88 16 00 80 b4 |........|.......|
00000010 41 bb aa 55 cd 13 72 10 81 fb 55 aa 75 0a f6 c1 |A..U..r...U.u...|
00000020 01 74 05 c6 06 98 7d 01 66 31 c0 40 66 8d 2e 00 |.t....}.f1.@f...|
00000030 7e e8 28 00 66 81 3e 20 7e 31 53 46 42 74 05 be |~.(.f.> ~1SFBt..|
00000040 a4 7d eb 03 e9 9c 02 e8 06 00 b4 00 cd 16 cd 19 |.}..............|
00000050 ac 08 c0 74 06 b4 0e cd 10 eb f5 c3 bf 01 00 66 |...t...........f|
00000060 60 66 03 06 fa 7d 8a 16 00 80 80 3e 98 7d 00 74 |`f...}.....>.}.t|
00000070 28 c7 06 04 80 10 00 89 3e 06 80 66 89 2e 08 80 |(.......>..f....|
00000080 66 a3 0c 80 66 31 c0 66 a3 10 80 be 04 80 b4 42 |f...f1.f.......B|
00000090 cd 13 73 50 be 99 7d eb ae 66 50 b4 08 cd 13 72 |..sP..}..fP....r|
000000a0 f3 66 58 86 d6 30 f6 52 66 31 d2 66 83 e1 3f 66 |.fX..0.Rf1.f..?f|
000000b0 f7 f1 fe c2 59 52 31 d2 30 ed 41 f7 f1 08 d6 89 |....YR1.0.A.....|
000000c0 c1 86 cd c0 c9 02 5a 08 d1 66 89 e8 66 c1 e8 10 |......Z..f..f...|
000000d0 06 50 07 89 eb 89 f8 8a 16 00 80 b4 02 cd 13 07 |.P..............|
000000e0 39 f8 75 b0 66 61 c3 31 c0 66 60 87 26 96 7d 66 |9.u.fa.1.f`.&.}f|
000000f0 61 66 21 c9 7f 38 fe ca 7d 1d 4b 7c 4d 66 31 f6 |af!..8..}.K|Mf1.|
00000100 8d 36 34 82 66 60 66 97 66 89 f5 e8 4e ff 66 61 |.64.f`f.f...N.fa|
00000110 66 47 66 31 d2 b2 40 e8 92 02 66 21 c0 74 db 66 |fGf1..@...f!.t.f|
00000120 91 66 0f b7 44 06 e8 9a 02 66 91 83 c6 08 66 83 |.f..D....f....f.|
00000130 e9 02 66 57 bf 02 00 e8 25 ff 66 05 02 00 00 00 |..fW....%.f.....|
00000140 8b 3e 96 7d c6 45 1c 01 66 5f 66 60 87 26 96 7d |.>.}.E..f_f`.&.}|
00000150 66 61 c3 66 60 87 26 96 7d 66 61 31 db 8d 36 ec |fa.f`.&.}fa1..6.|
00000160 80 66 83 3c 00 75 07 66 83 7c 04 00 74 17 8d 36 |.f.<.u.f.|..t..6|
00000170 e4 80 e8 37 02 66 21 c0 74 0b 66 97 66 0f b7 44 |...7.f!.t.f.f..D|
00000180 06 e8 3f 02 93 66 31 c9 b2 0c 8d 36 7c 80 66 31 |..?..f1....6|.f1|
00000190 ed bd 34 84 eb b4 e0 5b 00 72 65 61 64 20 65 72 |..4....[.read er|
000001a0 72 6f 72 00 62 61 64 20 73 75 70 65 72 62 6c 6f |ror.bad superblo|
000001b0 63 6b 00 6e 6f 74 20 61 20 64 69 72 65 63 74 6f |ck.not a directo|
000001c0 72 79 00 62 61 64 20 69 6e 6f 64 65 00 68 61 69 |ry.bad inode.hai|
000001d0 6b 75 5f 6c 6f 61 64 65 72 20 6e 6f 74 20 66 6f |ku_loader not fo|
000001e0 75 6e 64 00 06 73 79 73 74 65 6d 0c 68 61 69 6b |und..system.haik|
000001f0 75 5f 6c 6f 61 64 65 72 00 00 00 48 86 02 55 aa |u_loader...H..U.|

Modifying/Hacking the PBR

We need to find a codecave that is 16 bytes long: 12 bytes for the disabler code, 2 for an enclosing pusha/popa sequence, and 2 more for a jmp opcode. We have no choice but to overwrite some unused text messages, which are located between offsets 0x199 and 0x1E3. This works out just fine because those error messages are only seen if there's a problem. And if there is a problem booting, you will see gibberish instead of the actual error message.

Step 1: Inject the 16-byte code at offset 0x1A0 with the following two commands.
XSTR='\x60\xb8\x14\x4f\xb3\x02\xb7\x01\xb9\x01\x00\xcd\x10\x61\xeb\x9a'
printf $XSTR | sudo dd of=/dev/disk0s4 bs=1 seek=416


We also need to redirect the flow of execution so that this code is called. The way to do this without destroying any critical code is to find a jump instruction and modify it to point to the offset of our code. At offset 0x194 you will find such a jump (EB B4) that returns back to 0x14A, which is found by (0xB4-0x100)+0x2+0x194. Calculate the 2nd byte for the new forward jump as follows: 0x1A0-0x194-0x2=0x0A.

Step 2: Change the B4 to 0A with this command:
printf '\x0A' | sudo dd of=/dev/disk0s4 bs=1 seek=405


Note that the end of the code in step 1 already has a jump opcode (EB 9A) that returns execution back to offset 0x14A. The 9A is the back jump's 2nd byte and is calculated as follows: 0x14A-(1A0+0xE)-0x2+0x100, where 0xE is the size of the disabler code (12 bytes) plus the enclosing pusha/popa opcodes (2 bytes), so 12+2=14 or 0xE.

When these 17 bytes are changed, the code is executed without interfering with Haiku's boot process. Here's a screenshot of what your PBR should look like after the changes. And if you are not comfortable with the command line, you can also edit your PBR with any hex editor and type in the 17 bytes manually.

Hexdump of the Modified PBR

The 17 changed bytes are highlighted in yellow.

00000000 0e 1f 0e 07 0e 17 bc 00 7c fa fc 88 16 00 80 b4 |........|.......|
00000010 41 bb aa 55 cd 13 72 10 81 fb 55 aa 75 0a f6 c1 |A..U..r...U.u...|
00000020 01 74 05 c6 06 98 7d 01 66 31 c0 40 66 8d 2e 00 |.t....}.f1.@f...|
00000030 7e e8 28 00 66 81 3e 20 7e 31 53 46 42 74 05 be |~.(.f.> ~1SFBt..|
00000040 a4 7d eb 03 e9 9c 02 e8 06 00 b4 00 cd 16 cd 19 |.}..............|
00000050 ac 08 c0 74 06 b4 0e cd 10 eb f5 c3 bf 01 00 66 |...t...........f|
00000060 60 66 03 06 fa 7d 8a 16 00 80 80 3e 98 7d 00 74 |`f...}.....>.}.t|
00000070 28 c7 06 04 80 10 00 89 3e 06 80 66 89 2e 08 80 |(.......>..f....|
00000080 66 a3 0c 80 66 31 c0 66 a3 10 80 be 04 80 b4 42 |f...f1.f.......B|
00000090 cd 13 73 50 be 99 7d eb ae 66 50 b4 08 cd 13 72 |..sP..}..fP....r|
000000a0 f3 66 58 86 d6 30 f6 52 66 31 d2 66 83 e1 3f 66 |.fX..0.Rf1.f..?f|
000000b0 f7 f1 fe c2 59 52 31 d2 30 ed 41 f7 f1 08 d6 89 |....YR1.0.A.....|
000000c0 c1 86 cd c0 c9 02 5a 08 d1 66 89 e8 66 c1 e8 10 |......Z..f..f...|
000000d0 06 50 07 89 eb 89 f8 8a 16 00 80 b4 02 cd 13 07 |.P..............|
000000e0 39 f8 75 b0 66 61 c3 31 c0 66 60 87 26 96 7d 66 |9.u.fa.1.f`.&.}f|
000000f0 61 66 21 c9 7f 38 fe ca 7d 1d 4b 7c 4d 66 31 f6 |af!..8..}.K|Mf1.|
00000100 8d 36 34 82 66 60 66 97 66 89 f5 e8 4e ff 66 61 |.64.f`f.f...N.fa|
00000110 66 47 66 31 d2 b2 40 e8 92 02 66 21 c0 74 db 66 |fGf1..@...f!.t.f|
00000120 91 66 0f b7 44 06 e8 9a 02 66 91 83 c6 08 66 83 |.f..D....f....f.|
00000130 e9 02 66 57 bf 02 00 e8 25 ff 66 05 02 00 00 00 |..fW....%.f.....|
00000140 8b 3e 96 7d c6 45 1c 01 66 5f 66 60 87 26 96 7d |.>.}.E..f_f`.&.}|
00000150 66 61 c3 66 60 87 26 96 7d 66 61 31 db 8d 36 ec |fa.f`.&.}fa1..6.|
00000160 80 66 83 3c 00 75 07 66 83 7c 04 00 74 17 8d 36 |.f.<.u.f.|..t..6|
00000170 e4 80 e8 37 02 66 21 c0 74 0b 66 97 66 0f b7 44 |...7.f!.t.f.f..D|
00000180 06 e8 3f 02 93 66 31 c9 b2 0c 8d 36 7c 80 66 31 |..?..f1....6|.f1|
00000190 ed bd 34 84 eb 0a e0 5b 00 72 65 61 64 20 65 72 |..4....[.read er|
000001a0 60 b8 14 4f b3 02 b7 01 b9 01 00 cd 10 61 eb 9a |`..O.........a..|
000001b0 63 6b 00 6e 6f 74 20 61 20 64 69 72 65 63 74 6f |ck.not a directo|
000001c0 72 79 00 62 61 64 20 69 6e 6f 64 65 00 68 61 69 |ry.bad inode.hai|
000001d0 6b 75 5f 6c 6f 61 64 65 72 20 6e 6f 74 20 66 6f |ku_loader not fo|
000001e0 75 6e 64 00 06 73 79 73 74 65 6d 0c 68 61 69 6b |und..system.haik|
000001f0 75 5f 6c 6f 61 64 65 72 00 00 00 48 86 02 55 aa |u_loader...H.%U.|

Other Boot Sectors

If you use GRUB as your main bootloader, you are in luck because at offset 0x0 there is a jump (EB 63) followed by one nop (90) and enough nulls for the code plus much more. The jump goes to offset 0x65 (0x63+0x2+0x0). You won't need the enclosing pusha/popa, but you will need to add a trailing jump. Calculate the new forward jump's 2nd byte as follows: 0x65-0xC-0x2 = 0x57. Now inject the code (using the 5th partition as an example) at offset 0x0 with these commands:
XSTR='\xb8\x14\x4f\xb3\x02\xb7\x01\xb9\x01\x00\xcd\x10\xeb\x57'
printf $XSTR | sudo dd of=/dev/disk0s5

And if you use Windows as your main bootloader, you can still inject the code in your NTFS partition, using the a similar method.

Conclusion

Now when you reboot, you'll notice that the BIOS will go through it's various stages, and the text will be large and stretched -- just as it always was. But when the new PBR code is loaded and executed, the Haiku loader icons will not be stretched and they will be small and tack sharp. Once Haiku is loaded it may look like a small window in the center of the display, but it's not. What you are seeing is a one to one pixel ratio.

If you have a recent nVidia video card AND a digital LCD monitor connected via DVI, you can modify your boot sector and enjoy a much improved Haiku experience.


This page was last revised on July 14, 2011
Copyright © 2011 Anthony D'Agostino
All rights reserved.