├── .gitignore ├── README.md ├── boot.asm ├── demo.gif ├── pablo_jimenez_mateo_cv.pdf └── patch.py /.gitignore: -------------------------------------------------------------------------------- 1 | uncompressed.pdf 2 | CV_english.pdf 3 | boot.bin 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | My curriculum is also a bootloader 2 | ==================================== 3 | 4 | ![ScreenShot](demo.gif) 5 | 6 | What? 7 | ------------ 8 | 9 | The PDF attached in this repository is both a working PDF of my CV and a custom bootloader created by me to impress technical recruiters. [Check the discussion in HN](https://news.ycombinator.com/item?id=19344146). 10 | 11 | How? 12 | ------------ 13 | 14 | I wrote a tiny bootloader of 1018 bytes. Then I just copy the first bytes of my bootloader at the beginning of my CV and that is it, a working PDF and bootloader. 15 | 16 | Can I try it? 17 | ------------ 18 | 19 | Sure! You can do so by two means, in real hardware (I spend a lot of time making it compatible) or in an emulated environment. 20 | 21 | #### Real hardware 22 | 23 | **NOTE:** All the data on the USB will be lost, please make sure you have a backup before continuing. 24 | 25 | ##### Windows 26 | 27 | You can use [Win32 Disk Imager](https://sourceforge.net/projects/win32diskimager/) and use the PDF as the input image. 28 | 29 | To recover your USB just right click on it and format normally. 30 | 31 | ##### Linux 32 | **NOTE:** Please be very careful with this command. If you select the wrong drive **you will lose data**. 33 | 34 | Get a USB, check the device name (you can use df -h) and do: 35 | ```bash 36 | sudo dd if=pablo_jimenez_mateo_cv.pdf of=/dev/sdX bs=512 count=2880 37 | sync 38 | ``` 39 | 40 | where X is your device. 41 | 42 | To recover your USB just use [gparted](https://gparted.org/). 43 | 44 | #### Emulated environment 45 | 46 | ##### Qemu 47 | ```bash 48 | qemu-system-i386 -drive format=raw,file=pablo_jimenez_mateo_cv.pdf 49 | ``` 50 | 51 | ##### Bochs: 52 | 53 | Create a bochsrc.txt file with this contents: 54 | ```text 55 | megs: 32 56 | romimage: file=/usr/share/bochs/BIOS-bochs-latest, address=0xfffe0000 57 | vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest 58 | floppya: 1_44=pablo_jimenez_mateo_cv.pdf, status=inserted 59 | boot: a 60 | log: bochsout.txt 61 | mouse: enabled=0 62 | display_library: x, options="gui_debug" 63 | ``` 64 | 65 | And then execute bochs as follows: 66 | 67 | ```bash 68 | bochs -f bochsrc.txt 69 | ``` 70 | 71 | Compiling from source 72 | ------------ 73 | 74 | ```bash 75 | nasm boot.asm -o boot.bin 76 | ``` 77 | 78 | Get your vanilla PDF, CV_english.pdf in this example, and do: 79 | ```bash 80 | qpdf --stream-data=uncompress --object-streams=disable CV_english.pdf uncompressed.pdf 81 | python3 patch.py 82 | ``` 83 | 84 | FAQ 85 | ------------ 86 | 87 | * But why? 88 | 89 | As a learning experience. I realized that I had no practical knowledge on how the booting process works, and also my assembly was a little bit rusty. This is a project I really enjoyed, although it was a little bit overwhelming at some points. 90 | 91 | * Can you explain how? 92 | 93 | I have tried my best to comment the code thoroughly, including my optimization decisions as well as the encoding of the sprites. I have also linked to a couple of Stackoverflow answers for deeper understanding. 94 | 95 | * But now your PDF is bigger! 96 | 97 | Yes, but 1018 bytes bigger or less than 0.01% bigger. For comparison, the gif in this readme is 2778093 bytes or 2728,97 times bigger! The Firefox icon is 8532 bytes, 8.38 times bigger! 98 | 99 | 100 | 101 | This program is licensed under Creative commons Attribution 3.0 Unported, more info : 102 | http://creativecommons.org/licenses/by/3.0/deed.en_US 103 | -------------------------------------------------------------------------------- /boot.asm: -------------------------------------------------------------------------------- 1 | bits 16 2 | 3 | boot: 4 | 5 | ; This is a BPB, so that the BIOS does not overwrite our code 6 | ; https://stackoverflow.com/questions/47277702/custom-bootloader-booted-via-usb-drive-produces-incorrect-output-on-some-compute 7 | jmp start 8 | TIMES 3-($-$$) DB 0x90 ; Support 2 or 3 byte encoded JMPs before BPB. 9 | 10 | ; Dos 4.0 EBPB 1.44MB floppy 11 | OEMname: db "mkfs.fat" ; mkfs.fat is what OEMname mkdosfs uses 12 | bytesPerSector: dw 512 13 | sectPerCluster: db 1 14 | reservedSectors: dw 1 15 | numFAT: db 2 16 | numRootDirEntries: dw 224 17 | numSectors: dw 2880 18 | mediaType: db 0xf0 19 | numFATsectors: dw 9 20 | sectorsPerTrack: dw 18 21 | numHeads: dw 2 22 | numHiddenSectors: dd 0 23 | numSectorsHuge: dd 0 24 | driveNum: db 0 25 | reserved: db 0 26 | signature: db 0x29 27 | volumeID: dd 0x2d7e5a1a 28 | volumeLabel: db "NO NAME " 29 | fileSysType: db "FAT12 " 30 | 31 | ; This is used to offset all memory addresses by 8 bytes, or the size of the PDF magic numbers 32 | dw 0xffff, 0xffff, 0xffff, 0xffff 33 | 34 | start: 35 | 36 | ; In real hardware the BIOS puts the address of the booting drive on the dl register 37 | ; so I am writing that addres into memory at [bootdrv] 38 | mov [bootdrv], dl 39 | 40 | ; Setting the stack 41 | mov ax, 07C0h 42 | add ax, 288 43 | mov ss, ax ; ss = stack space 44 | mov sp, 4096 ; sp = stack pointer 45 | 46 | mov ax, 07C0h 47 | mov ds, ax ; ds = data segment 48 | 49 | mov ah, 00d ; Set video mode to graphical 50 | mov al, 13d ; 13h - graphical mode, 40x25. 256 colors.;320x200 pixels. 1 page. 51 | 52 | int 10h ; Call 53 | 54 | 55 | ; The function print_text accepts a message and position and writes it to screen in 56 | ; mode 13h 57 | push 9 ; column 58 | push 5 ; row 59 | push 20 ; msg length 60 | push msg1 ; msg to write 61 | call print_text 62 | 63 | push 11 ; column 64 | push 6 ; row 65 | push 17 ; msg length 66 | push msg2 ; msg to write 67 | call print_text 68 | 69 | ; draw_border is in charge of calling draw_rock in a loop to draw all the cave border 70 | ; no parameters 71 | call draw_border 72 | 73 | ; You only have 512 bytes of space on the first stage, and my napking maths told me that 74 | ; the sprites were too much. So on the first stage I write the text and draw the border 75 | ; (or else I would not have enough space on second stage), and then jump to second stagr 76 | ; https://stackoverflow.com/questions/2065370/how-to-load-second-stage-boot-loader-from-first-stage 77 | ; Restore the direction of the booting drive 78 | mov dl, [bootdrv] 79 | 80 | ; I need this label in case the boot fails, in real hardware the BIOS puts the drive address on dl, 81 | ; but if you are using qemu dl must be 0x80 for it to boot. So I try to boot normally first and if it 82 | ; fails I retry for qemu 83 | jump_to_stage2: 84 | 85 | mov ah, 0x02 86 | mov al, 1 ; Number of sectors to read 87 | mov ch, 0 ; Cylinder number 88 | mov dh, 0 ; Head number 89 | mov cl, 2 ; Starting sector number. 2 because 1 was already loaded. 90 | mov bx, stage2 ; Where the stage 2 code is 91 | 92 | int 0x13 93 | 94 | mov dl, 0x80 95 | jc jump_to_stage2 ; If error loading, set dl to 0x80 and try again, this should make it work in qemu 96 | 97 | jmp stage2 98 | 99 | ; Stage 1 functions 100 | 101 | ; This function calls draw_rock on a loop in order to draw the whole border 102 | draw_border: 103 | ; You will notice that I use xor a lot 104 | ; this is to set a register to 0 105 | ; is on byte less than mov and I need those bytes 106 | 107 | xor cx, cx ; Draw horizontally 108 | xor dx, dx ; Border index 109 | 110 | ; The inits set the initial parameters for each border 111 | ; di = Initial x position 112 | ; si = Initial y position 113 | ; ax = When to stop 114 | ; cx = Draw horizontally (0) or vertically (1) 115 | .top_border_init: 116 | xor si, si 117 | xor di, di 118 | mov ax, 320 119 | jmp .draw 120 | 121 | .bottom_left_border_init: 122 | 123 | mov si, 176 124 | xor di, di 125 | mov ax, 128 126 | jmp .draw 127 | 128 | .bottom_right_border_init: 129 | 130 | mov di, 192 131 | mov ax, 304 132 | jmp .draw 133 | 134 | .right_border_init: 135 | 136 | xor si, si 137 | xor di, di 138 | mov ax, 192 139 | mov cx, 1 ; draw vertically 140 | jmp .draw 141 | 142 | .left_border_init: 143 | 144 | xor si, si 145 | mov di, 304 146 | 147 | .draw: 148 | 149 | push si ; Initial y position to draw the rock 150 | push di ; Initial x position to draw the rock 151 | call draw_rock_tile 152 | 153 | cmp cx, 0 ; If we are drawing horizontally 154 | jne .vertical_index_update 155 | 156 | ; Update the horizontal index (di) 157 | add di, 16 158 | cmp di, ax 159 | je .check_finish 160 | 161 | jmp .draw 162 | 163 | ; Update the vertical index (si) 164 | .vertical_index_update: 165 | 166 | add si, 16 167 | cmp si, ax 168 | je .check_finish 169 | 170 | jmp .draw 171 | 172 | ; If we go here, we have finished a border, increment the border index 173 | ; and continue 174 | .check_finish: 175 | 176 | inc dx ; we have finished a border 177 | 178 | cmp dx, 1 179 | je .bottom_left_border_init 180 | 181 | cmp dx, 2 182 | je .bottom_right_border_init 183 | 184 | cmp dx, 3 185 | je .right_border_init 186 | 187 | cmp dx, 4 188 | je .left_border_init 189 | 190 | jmp .done 191 | 192 | .done: 193 | ret 194 | 195 | ; This function draws a rock, the coordinates are the top-left corner 196 | ; 197 | ; The rock sprite is encoded as following 198 | ; 199 | ; - The tile is 16x16px 200 | ; - Each 1 represents a brown pixel 201 | ; - Each 0 represents a black pixel (or no draw since the background is black) 202 | ; 203 | ; So we iterate through every bit on [rock] to do that. Each byte is a row. 204 | ; 205 | ; Parameters: 206 | ; 207 | ; - [bp + 4] x coordinate 208 | ; - [bp + 6] y coordinate 209 | ; 210 | draw_rock_tile: 211 | 212 | push bp ; Save old base pointer 213 | mov bp, sp ; Use the current stack pointer as new base pointer 214 | pusha 215 | 216 | mov cx, [bp + 4] ; x coordinate 217 | mov dx, [bp + 6] ; y coordinate 218 | 219 | ; Initializing to 0, saves one byte from using mov 220 | xor si, si ; Index of the bit we are checking (width) 221 | xor di, di ; How many bytes have we read 222 | 223 | .row: ; Main loop, this will iterate over every bit of [rock] 224 | 225 | cmp si, 16 ; Check if we have to move to the next byte/row 226 | jne .same_row ; We are still on the same row 227 | 228 | ; This executes if we move to the next row 229 | xor si, si ; Set the index of the bit to 0 230 | cmp di, 32 ; If we have read all the bytes (finished with the tile) 231 | je .done 232 | 233 | add di, 2 ; Next row/byte 234 | inc dx 235 | 236 | mov cx, [bp + 4] ; Restore the x coordinate 237 | 238 | .same_row: 239 | 240 | mov ax, [rock + di] ; Get the Byte 241 | bt ax, si ; Store the bit in position si on the carry flag (CF) 242 | jnc .pass ; jnc = jump if no carry, a.k.a. if it is a 0 243 | 244 | ; It is a 1, draw 245 | mov ah, 0Ch 246 | xor bh, bh ; Page number 0 247 | mov al, 06h ; Color brown 248 | int 10h 249 | 250 | .pass: ; Increment the counters 251 | inc si 252 | inc cx 253 | jmp .row 254 | 255 | .done: ; Restore the stack and return 256 | 257 | popa 258 | mov sp, bp 259 | pop bp 260 | ret 4 261 | 262 | ; Given a row, a column, a text and a length draws it to screen 263 | ; 264 | ; - [bp + 4] message direction 265 | ; - [bp + 6] length of string 266 | ; - [bp + 8] row to put the string 267 | ; - [bp + 10] column to put the string 268 | ; 269 | print_text: 270 | 271 | push bp ; Save old base pointer 272 | mov bp, sp ; Use the current stack pointer as new base pointer 273 | pusha 274 | 275 | mov ax, 7c0h ; Beginning of the code 276 | mov es, ax 277 | mov cx, [bp + 6] ; Length of string 278 | mov dh, [bp + 8] ; Row to put string 279 | mov dl, [bp + 10] ; Column to put string 280 | mov bp, [bp + 4] 281 | 282 | mov ah, 13h ; Function 13 - write string 283 | mov al, 01h ; Attrib in bl, move cursor 284 | mov bl, 0Fh ; Color white 285 | 286 | int 10h 287 | 288 | ; Restore the stack and return 289 | popa 290 | mov sp, bp 291 | pop bp 292 | 293 | ret 8 294 | 295 | ; Store the drive addres given by the BIOS 296 | bootdrv: db 0 297 | 298 | ; Data 299 | msg1: db "IT'S DANGEROUS TO GO" 300 | msg2: db "ALONE! TAKE ME." 301 | rock: dw 0xC3B7, 0xDFCF, 0xFFCF, 0x7FCF, 0x7FE6, 0xFFEF, 0xBFEF, 0xBFEF, 0x7FE7, 0xFFEF, 0x7DE7, 0x3C9B, 0x7DFD, 0xBC7D, 0xFCFF, 0x2CFC ; 32 bytes 302 | 303 | ; The first sector MUST be 512 bytes and the last 2 bytes have to be 0xAA55 for it 304 | ; to be bootable 305 | times 510 - ($ - $$) db 0 ; Padding with 0 at the end 306 | dw 0xAA55 ; PC boot signature 307 | 308 | 309 | stage2: 310 | 311 | ; So apparently I was unable to pass the memory address of the current sprite as an argument 312 | ; so I copy it to a new memory location and execute from there 313 | 314 | ; Copy the current sprite 315 | mov cx, 32 ; How many bytes 316 | lea di, [current_sprite] ; To where 317 | lea si, [wiseman_left] ; From where 318 | rep movsb 319 | 320 | push 32 ; How many bytes the sprite has 321 | push 06h ; First color, brown 322 | push 04h ; Second color, red 323 | push 90 ; y coordinate 324 | push 152 ; x coordinate 325 | call draw_sprite 326 | 327 | ; Copy the current sprite 328 | mov cx, 32 329 | lea di, [current_sprite] 330 | lea si, [wiseman_right] 331 | rep movsb 332 | 333 | ; Now, the trick that saved most bytes (and the project) is to reuse the values stored in the stack 334 | ; between sprites. So I reuse coordinates and sprite size. When the draw_sprite returns it DOES NOT 335 | ; pop all the arguments, only the x coordinate 336 | 337 | push 160 ; x coordinate 338 | call draw_sprite 339 | 340 | ; Copy the current sprite 341 | mov cx, 32 342 | lea di, [current_sprite] 343 | lea si, [fire_left] 344 | rep movsb 345 | 346 | ; We need to change the colors, therefore we need to pop them 347 | pop si 348 | pop si 349 | pop si 350 | 351 | 352 | 353 | ; We draw the two left parts of the fire to reuse the stack 354 | push 0Eh ; First color, yellow 355 | push 0Ch ; Second color, light red 356 | push 90 ; y coordinate 357 | push 80 ; x coordinate 358 | call draw_sprite 359 | 360 | 361 | push 224 ; x coordinate 362 | call draw_sprite 363 | 364 | ; Copy the current sprite 365 | mov cx, 32 366 | lea di, [current_sprite] 367 | lea si, [fire_right] 368 | rep movsb 369 | 370 | ; Same y, same colors 371 | push 88 ; x coordinate 372 | call draw_sprite 373 | 374 | push 232 ; x coordinate 375 | call draw_sprite 376 | 377 | ; Copy the current sprite 378 | mov cx, 44 ; This one is 44 bytes instead of 32 379 | lea di, [current_sprite] 380 | lea si, [gef_left] 381 | rep movsb 382 | 383 | ; Delete the arguments on the stack 384 | pop si 385 | pop si 386 | pop si 387 | pop si 388 | 389 | push 44 ; How many bytes the sprite has 390 | push 07h ; First color, gray 391 | push 06h ; Second color, brown 392 | push 120 ; y coordinate 393 | push 152 ; x coordinate 394 | call draw_sprite 395 | 396 | ; Copy the current sprite 397 | mov cx, 44 398 | lea di, [current_sprite] 399 | lea si, [gef_right] 400 | rep movsb 401 | 402 | push 160 ; x coordinate 403 | call draw_sprite 404 | 405 | cli 406 | hlt 407 | 408 | ; This function is in charge of drawing a sprite, the sprite MUST be 16 pixels wide 409 | ; but can be as high as necessary. Note that the coordinates are for the top-left corner 410 | ; 411 | ; The sprites are encoded as following: 412 | ; 413 | ; - Each sprite has a maximum of 4 colors 414 | ; - Each pixel is encoded in 2 bits 415 | ; - 00 is always black 416 | ; - 11 is always white 417 | ; - 01 is the first color 418 | ; - 10 is the second color 419 | ; 420 | ; Parameters: 421 | ; 422 | ; - [bp + 4] x coordinate 423 | ; - [bp + 6] y coordinate 424 | ; - [bp + 8] Second color 425 | ; - [bp + 10] First color 426 | ; - [bp + 12] How many bytes/rows the sprite has 427 | ; - [current_sprite] The sprite 428 | ; 429 | draw_sprite: 430 | 431 | push bp ; Save old base pointer 432 | mov bp, sp ; Use the current stack pointer as new base pointer 433 | pusha 434 | 435 | mov cx, [bp + 4] ; x coordinate 436 | mov dx, [bp + 6] ; y coordinate 437 | 438 | ; Initializing to 0, saves one byte from using mov 439 | xor si, si ; Index of the bit we are checking (width) 440 | xor di, di ; How many bytes we have checked 441 | 442 | .row: ; Main loop, we get 2 bits at a time to check the color 443 | 444 | cmp si, 16 ; Check if we have to move to the next byte/row 445 | jne .same_row ; Same byte 446 | 447 | ; This executes if we move to the next row 448 | xor si, si ; Start from 0 449 | add di, 2 ; Next row/byte 450 | cmp di, [bp + 12] ; If we have checked all bytes 451 | je .done 452 | ; Increment byte and x coordinate 453 | inc dx 454 | mov cx, [bp + 4] ; x coordinate 455 | 456 | .same_row: 457 | 458 | xor bh, bh ; We will store the color index here 459 | 460 | mov ax, [current_sprite + di] ; Get the current byte 461 | 462 | bt ax, si ; First bit 463 | jnc .next_bit ; If it is 1 increment bh by one 464 | inc bh 465 | 466 | .next_bit: 467 | 468 | inc si 469 | bt ax, si ; Second bit 470 | jnc .end_bit ; If it is 1 increment bh by two 471 | add bh, 2 472 | 473 | .end_bit: 474 | cmp bh, 0 ; If the color is 0 (black) we just don't draw anything 475 | je .pass 476 | 477 | mov ah, 0Ch ; Draw instruction 478 | 479 | cmp bh, 1 ; Draw first color 480 | je .first_color 481 | 482 | cmp bh, 2 ; Draw second color 483 | je .second_color 484 | 485 | jmp .white ; Draw white 486 | 487 | .first_color: 488 | 489 | mov al, [bp + 10] ; Set the first color 490 | jmp .draw 491 | 492 | .second_color: 493 | 494 | mov al, [bp + 8] ; Set the second color 495 | jmp .draw 496 | 497 | .white: 498 | 499 | mov al, 0Fh ; Set white 500 | jmp .draw 501 | 502 | .draw: 503 | xor bh, bh ; First page, funny note if you remove this instruction qemu will 504 | ; still execute but it won't work in real hardware 505 | int 10h 506 | 507 | .pass: 508 | ; Increment indexes and move on 509 | inc si 510 | inc cx 511 | jmp .row 512 | 513 | .done: 514 | 515 | popa 516 | mov sp, bp 517 | pop bp 518 | ret 2 ; Only pop the y 519 | 520 | 521 | current_sprite: dw 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,0x0000, 0x0000, 0x0000, 0x0000 ; 32 bytes 522 | fire_left: dw 0x2020, 0x8020, 0x8800, 0xA810, 0xA880, 0xA288, 0xA6A8, 0xAAA2, 0x9AA2, 0x66AA, 0x55AA, 0x7568, 0xFD68, 0xF5A0, 0x56A0, 0xAA00 ; 32 bytes 523 | fire_right: dw 0x0808, 0x0802, 0x0022, 0x042A, 0x022A, 0x228A, 0x2A9A, 0x8AAA, 0x8AA6, 0xAA99, 0xAA55, 0x295D, 0x297F, 0x0A5F, 0x0A95, 0x00AA ; 32 bytes 524 | wiseman_left: dw 0x5400, 0x7700, 0x4500, 0x4500, 0x5E00, 0xFF80, 0x0FA0, 0xFBE8, 0xFAE9, 0xFAA9, 0xE8A9, 0xA8A8, 0xA8A8, 0xAA20, 0xAA00, 0x9680 ; 32 bytes 525 | wiseman_right: dw 0x0015, 0x00dd, 0x0051, 0x0051, 0x00b5, 0x02ff, 0x0af0, 0x2bef, 0x6baf, 0x6aaf, 0x6a2b, 0x2a2a, 0x2a2a, 0x08aa, 0x00aa, 0x0296 ; 32 bytes 526 | gef_left: dw 0xAA00, 0xAA80, 0xAAA0, 0x88A8, 0x1818, 0x5158, 0x5558, 0x56A8, 0x5558, 0x5018, 0x5ED8, 0x5ED0, 0x9550, 0x65A0, 0x2A80, 0xBC00, 0x3F00, 0x33C0, 0x30F0, 0x3040, 0x0000, 0x2000 ; 44 bytes 527 | gef_right: dw 0x00aa, 0x02aa, 0x0aaa, 0x2a22, 0x2424, 0x2545, 0x2555, 0x2a95, 0x2555, 0x2405, 0x27b5, 0x07b5, 0x0556, 0x0a59, 0x02a8, 0x003e, 0x00fc, 0x03cc, 0x0f0c, 0x010c, 0x0000, 0x0008 ; 44 bytes 528 | 529 | ; The PDF header needs to be at most, here in 03FA (it must start at 1018 bytes) a lots of bytes were sacrified for this to work -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pablojimenezmateo/curriculum-bootloader/009834edcc4fe47dff093d47c76bb75dd2cd1965/demo.gif -------------------------------------------------------------------------------- /pablo_jimenez_mateo_cv.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pablojimenezmateo/curriculum-bootloader/009834edcc4fe47dff093d47c76bb75dd2cd1965/pablo_jimenez_mateo_cv.pdf -------------------------------------------------------------------------------- /patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | This utility merges my CV with the bootloader, 4 | this way makes it work in Adobe Reader as well 5 | as in all the rest I have tested. 6 | ''' 7 | import os 8 | import sys 9 | 10 | if not os.path.isfile('boot.bin'): 11 | 12 | print("boot.bin does not exist, please follow the instructions to create it.") 13 | sys.exit(-1) 14 | 15 | if not os.path.isfile('uncompressed.pdf'): 16 | 17 | print("uncompressed.pdf does not exist, please follow the instructions to create it.") 18 | sys.exit(-1) 19 | 20 | with open("boot.bin", "rb") as binaryfile : 21 | bootloader = bytearray(binaryfile.read()) 22 | 23 | # Delete the manual padding I added for the PDF magic numbers 24 | # please refer to boot.asm for more information 25 | bootloader_magic = bytearray(b'\xEB\x44\x25\x50\x44\x46\x2D\x31\x2E\x35') #Bootloader + PDF magic numbers 26 | bootloader_start = bootloader[2:62] 27 | bootloader_end = bootloader[70:] 28 | bootloader = bootloader_magic + bootloader_start + bootloader_end 29 | 30 | binaryfile.close() 31 | 32 | # Open PDF 33 | with open("uncompressed.pdf", "rb") as binaryfile: 34 | cv = bytearray(binaryfile.read()) 35 | 36 | cv = cv[8:] #Remove the magic numbers 37 | cv = bootloader + cv #Append bootloader 38 | 39 | binaryfile.close() 40 | 41 | with open("pablo_jimenez_mateo_cv.pdf", "wb") as binaryfile: 42 | binaryfile.write(cv) 43 | 44 | binaryfile.close() 45 | --------------------------------------------------------------------------------