- detect kernel start
[x86emu:bloat.git] / bloat.c
1 #define _GNU_SOURCE
2 #define _FILE_OFFSET_BITS 64
3
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <unistd.h>
7 #include <string.h>
8 #include <stdarg.h>
9 #include <getopt.h>
10 #include <inttypes.h>
11 #include <ctype.h>
12 #include <fcntl.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <sys/ioctl.h>
16 #include <linux/fs.h>   /* BLKGETSIZE64 */
17 #include <x86emu.h>
18
19
20 #define FIRST_DISK      0x80
21 #define FIRST_CDROM     0xe0
22 #define MAX_DISKS       0x100
23
24 #include "uni.h"
25
26 typedef struct {
27   unsigned char scan;
28   unsigned char ascii;
29   char *name;
30 } bios_key_t;
31
32 #include "bios_keys.h"
33
34 typedef struct {
35   unsigned type;
36   unsigned boot:1;
37   unsigned valid:1;
38   struct {
39     unsigned c, h, s, lin;
40   } start;
41   struct {
42     unsigned c, h, s, lin;
43   } end;
44   unsigned base;
45 } ptable_t;
46
47
48 typedef struct {
49   x86emu_t *emu;
50
51   unsigned kbd_cnt;
52   unsigned key;
53
54   unsigned memsize;     // in MB
55
56   unsigned a20:1;
57   unsigned maybe_linux_kernel:1;
58   unsigned is_linux_kernel:1;
59
60   struct {
61     char version[0x100];
62     char cmdline[0x1000];
63     unsigned loader_id;
64     unsigned boot_proto;
65     unsigned code_start;
66     unsigned initrd_start;
67     unsigned initrd_length;
68     unsigned cmdline_start;
69   } kernel;
70
71   struct {
72     unsigned iv_base;
73     int (* iv_funcs[0x100])(x86emu_t *emu);
74   } bios;
75 } vm_t;
76
77
78 void lprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
79 void flush_log(x86emu_t *emu, char *buf, unsigned size);
80
81 void help(void);
82 uint64_t vm_read_qword(x86emu_t *emu, unsigned addr);
83 unsigned vm_read_segofs16(x86emu_t *emu, unsigned addr);
84 void vm_write_qword(x86emu_t *emu, unsigned addr, uint64_t val);
85 void handle_int(x86emu_t *emu, unsigned nr);
86 int check_ip(x86emu_t *emu);
87 vm_t *vm_new(void);
88 void vm_free(vm_t *vm);
89 void vm_run(vm_t *vm);
90 unsigned cs2s(unsigned cs);
91 unsigned cs2c(unsigned cs);
92 int do_int(x86emu_t *emu, u8 num, unsigned type);
93 int do_int_10(x86emu_t *emu);
94 int do_int_11(x86emu_t *emu);
95 int do_int_12(x86emu_t *emu);
96 int do_int_13(x86emu_t *emu);
97 int do_int_15(x86emu_t *emu);
98 int do_int_16(x86emu_t *emu);
99 int do_int_19(x86emu_t *emu);
100 int do_int_1a(x86emu_t *emu);
101 void prepare_bios(vm_t *vm);
102 void prepare_boot(x86emu_t *emu);
103 int disk_read(x86emu_t *emu, unsigned addr, unsigned disk, uint64_t sector, unsigned cnt, int log);
104 void parse_ptable(x86emu_t *emu, unsigned addr, ptable_t *ptable, unsigned base, unsigned ext_base, int entries);
105 int guess_geo(ptable_t *ptable, int entries, unsigned *s, unsigned *h);
106 void print_ptable_entry(int nr, ptable_t *ptable);
107 int is_ext_ptable(ptable_t *ptable);
108 ptable_t *find_ext_ptable(ptable_t *ptable, int entries);
109 void dump_ptable(x86emu_t *emu, unsigned disk);
110 char *get_screen(x86emu_t *emu);
111 void dump_screen(x86emu_t *emu);
112 unsigned next_bios_key(char **keys);
113
114
115 struct option options[] = {
116   { "help",       0, NULL, 'h'  },
117   { "verbose",    0, NULL, 'v'  },
118   { "disk",       1, NULL, 1001 },
119   { "floppy",     1, NULL, 1002 },
120   { "cdrom",      1, NULL, 1003 },
121   { "boot",       1, NULL, 1004 },
122   { "show",       1, NULL, 1005 },
123   { "feature",    1, NULL, 1007 },
124   { "no-feature", 1, NULL, 1008 },
125   { "max",        1, NULL, 1009 },
126   { "log-size",   1, NULL, 1010 },
127   { "keys",       1, NULL, 1011 },
128   { }
129 };
130
131 struct {
132   unsigned verbose;
133   unsigned inst_max;
134   unsigned log_size; 
135
136   struct {
137     char *dev;
138     int fd;
139     unsigned heads;
140     unsigned sectors;
141     unsigned cylinders;
142     uint64_t size;
143   } disk[MAX_DISKS];
144
145   unsigned floppies;
146   unsigned disks;
147   unsigned cdroms;
148   unsigned boot;
149
150   unsigned trace_flags;
151   unsigned dump_flags;
152
153   struct {
154     unsigned rawptable:1;
155   } show;
156
157   struct {
158     unsigned edd:1;
159   } feature;
160
161   FILE *log_file;
162   char *keyboard;
163 } opt;
164
165
166 int main(int argc, char **argv)
167 {
168   char *s, *t, *dev_spec, *err_msg = NULL;
169   int i, j, err;
170   unsigned u, u2, ofs, *uu, tbits, dbits;
171   struct stat sbuf;
172   uint64_t ul;
173   ptable_t ptable[4];
174   x86emu_t *emu_0 = x86emu_new(X86EMU_PERM_R | X86EMU_PERM_W, 0);
175   vm_t *vm;
176
177   opt.log_file = stdout;
178
179   opt.inst_max = 10000000;
180   opt.feature.edd = 1;
181
182   opterr = 0;
183
184   while((i = getopt_long(argc, argv, "hv", options, NULL)) != -1) {
185     err = 0;
186     dev_spec = NULL;
187
188     switch(i) {
189       case 'v':
190         opt.verbose++;
191         break;
192
193       case 1001:
194       case 1002:
195       case 1003:
196         if(i == 1001) {
197           if(opt.disks >= FIRST_CDROM - FIRST_DISK) break;
198           uu = &opt.disks;
199           ofs = FIRST_DISK + opt.disks;
200         }
201         else if(i == 1002) {
202           if(opt.floppies >= FIRST_DISK) break;
203           uu = &opt.floppies;
204           ofs = opt.floppies;
205         }
206         else /* i == 1003 */ {
207           if(opt.cdroms >= MAX_DISKS - FIRST_CDROM) break;
208           uu = &opt.cdroms;
209           ofs = FIRST_CDROM + opt.cdroms;
210         }
211
212         dev_spec = strdup(optarg);
213         if((s = strchr(dev_spec, ','))) {
214           *s++ = 0;
215         }
216         if(!*dev_spec) {
217           err = 1;
218           break;
219         }
220         opt.disk[ofs].dev = strdup(dev_spec);
221         if(s) {
222           u = strtoul(s, &t, 0);
223           if((*t == 0 || *t == ',') && u <= 255) {
224             opt.disk[ofs].heads = u;
225           }
226           else {
227             err = 2;
228             break;
229           }
230           if(*t++ == ',') {
231             u = strtoul(t, &t, 0);
232             if(*t == 0 && u <= 63) {
233               opt.disk[ofs].sectors = u;
234             }
235             else {
236               err = 3;
237               break;
238             }
239           }
240         }
241         (*uu)++;
242         break;
243
244       case 1004:
245         if(!strcmp(optarg, "floppy")) {
246           opt.boot = 0;
247         }
248         else if(!strcmp(optarg, "disk")) {
249           opt.boot = FIRST_DISK;
250         }
251         else if(!strcmp(optarg, "cdrom")) {
252           opt.boot = FIRST_CDROM;
253         }
254         else {
255           u = strtoul(optarg, &s, 0);
256           if(s != optarg && !*s && u < MAX_DISKS) {
257             opt.boot = u;
258           }
259           else {
260             err = 4;
261           }
262         }
263         break;
264
265       case 1005:
266         for(s = optarg; (t = strsep(&s, ",")); ) {
267           u = 1;
268           tbits = dbits = 0;
269           while(*t == '+' || *t == '-') u = *t++ == '+' ? 1 : 0;
270                if(!strcmp(t, "trace")) tbits = X86EMU_TRACE_DEFAULT;
271           else if(!strcmp(t, "code"))  tbits = X86EMU_TRACE_CODE;
272           else if(!strcmp(t, "regs"))  tbits = X86EMU_TRACE_REGS;
273           else if(!strcmp(t, "data"))  tbits = X86EMU_TRACE_DATA;
274           else if(!strcmp(t, "acc"))   tbits = X86EMU_TRACE_ACC;
275           else if(!strcmp(t, "io"))    tbits = X86EMU_TRACE_IO;
276           else if(!strcmp(t, "ints"))  tbits = X86EMU_TRACE_INTS;
277           else if(!strcmp(t, "time"))  tbits = X86EMU_TRACE_TIME;
278           else if(!strcmp(t, "debug")) tbits = X86EMU_TRACE_DEBUG;
279           else if(!strcmp(t, "dump"))         dbits = X86EMU_DUMP_DEFAULT;
280           else if(!strcmp(t, "dump.regs"))    dbits = X86EMU_DUMP_REGS;
281           else if(!strcmp(t, "dump.mem"))     dbits = X86EMU_DUMP_MEM;
282           else if(!strcmp(t, "dump.mem.acc")) dbits = X86EMU_DUMP_ACC_MEM;
283           else if(!strcmp(t, "dump.mem.inv")) dbits = X86EMU_DUMP_INV_MEM;
284           else if(!strcmp(t, "dump.attr"))    dbits = X86EMU_DUMP_ATTR;
285           else if(!strcmp(t, "dump.io"))      dbits = X86EMU_DUMP_IO;
286           else if(!strcmp(t, "dump.ints"))    dbits = X86EMU_DUMP_INTS;
287           else if(!strcmp(t, "dump.time"))    dbits = X86EMU_DUMP_TIME;
288           else if(!strcmp(t, "rawptable"))    opt.show.rawptable = u;
289           else err = 5;
290           if(err) {
291             err_msg = t;
292           }
293           else {
294             if(tbits) {
295               if(u) {
296                 opt.trace_flags |= tbits;
297               }
298               else {
299                 opt.trace_flags &= ~tbits;
300               }
301             }
302             if(dbits) {
303               if(u) {
304                 opt.dump_flags |= dbits;
305               }
306               else {
307                 opt.dump_flags &= ~dbits;
308               }
309             }
310           }
311         }
312         break;
313
314       case 1007:
315       case 1008:
316         s = optarg;
317         u = i == 1007 ? 1 : 0;
318         while((t = strsep(&s, ","))) {
319           if(!strcmp(t, "edd")) opt.feature.edd = u;
320           else err = 6;
321         }
322         break;
323
324       case 1009:
325         opt.inst_max = strtoul(optarg, NULL, 0);
326         break;
327
328       case 1010:
329         opt.log_size = strtoul(optarg, NULL, 0);
330         break;
331
332       case 1011:
333         opt.keyboard = optarg;
334         break;
335
336       default:
337         help();
338         return i == 'h' ? 0 : 1;
339     }
340
341     free(dev_spec);
342
343     if(err && (i == 1001 || i == 1002 || i == 1003)) {
344       fprintf(stderr, "invalid device spec: %s\n", optarg);
345       return 1;
346     }
347
348     if(err && i == 1004) {
349       fprintf(stderr, "invalid boot device: %s\n", optarg);
350       return 1;
351     }
352
353     if(err && (i == 1005 || i == 1006)) {
354       fprintf(stderr, "invalid show spec: %s\n", err_msg);
355       return 1;
356     }
357
358     if(err && (i == 1007 || i == 1008)) {
359       fprintf(stderr, "invalid feature: %s\n", optarg);
360       return 1;
361     }
362   }
363
364   if(!opt.disks && !opt.floppies && !opt.cdroms) {
365     fprintf(stderr, "we need some drives\n");
366     return 1;
367   }
368
369   if(!opt.disk[opt.boot].dev) {
370     if(opt.disk[FIRST_CDROM].dev) {
371       opt.boot = FIRST_CDROM;
372     }
373     else if(opt.disk[FIRST_DISK].dev) {
374       opt.boot = FIRST_DISK;
375     }
376     else if(opt.disk[0].dev) {
377       opt.boot = 0;
378     }
379   }
380
381   lprintf("; drive map:\n");
382
383   for(i = 0; i < MAX_DISKS; i++) {
384     opt.disk[i].fd = -1;
385     if(!opt.disk[i].dev) continue;
386
387     opt.disk[i].fd = open(opt.disk[i].dev, O_RDONLY | O_LARGEFILE);
388     if(opt.disk[i].fd < 0) continue;
389
390     if(!opt.disk[i].heads || !opt.disk[i].sectors) {
391       j = disk_read(emu_0, 0, i, 0, 1, 0);
392       if(!j && x86emu_read_word(emu_0, 0x1fe) == 0xaa55) {
393         parse_ptable(emu_0, 0x1be, ptable, 0, 0, 4);
394         if(guess_geo(ptable, 4, &u, &u2)) {
395           if(!opt.disk[i].sectors) opt.disk[i].sectors = u;
396           if(!opt.disk[i].heads) opt.disk[i].heads = u2;
397         }
398       }
399     }
400
401     if(!opt.disk[i].heads) opt.disk[i].heads = 255;
402     if(!opt.disk[i].sectors) opt.disk[i].sectors = 63;
403
404     ul = 0;
405     if(!fstat(opt.disk[i].fd, &sbuf)) ul = sbuf.st_size;
406     if(!ul && ioctl(opt.disk[i].fd, BLKGETSIZE64, &ul)) ul = 0;
407     opt.disk[i].size = ul >> 9;
408     opt.disk[i].cylinders = opt.disk[i].size / (opt.disk[i].sectors * opt.disk[i].heads);
409
410     lprintf(";   0x%02x: %s, chs %u/%u/%u, %llu sectors\n",
411       i,
412       opt.disk[i].dev,
413       opt.disk[i].cylinders,
414       opt.disk[i].heads,
415       opt.disk[i].sectors,
416       (unsigned long long) opt.disk[i].size
417      );
418
419     dump_ptable(emu_0, i);
420   }
421
422   emu_0 = x86emu_done(emu_0);
423
424   lprintf("; boot device: 0x%02x\n", opt.boot);
425
426   fflush(stdout);
427
428   vm = vm_new();
429
430   prepare_bios(vm);
431
432   prepare_boot(vm->emu);
433
434   vm_run(vm);
435
436   dump_screen(vm->emu);
437
438   vm_free(vm);
439
440   for(u = 0; u < MAX_DISKS; u++) {
441     free(opt.disk[u].dev);
442   }
443
444   return 0;
445 }
446
447
448 void lprintf(const char *format, ...)
449 {
450   va_list args;
451
452   va_start(args, format);
453   if(opt.log_file) vfprintf(opt.log_file, format, args);
454   va_end(args);
455 }
456
457
458 void flush_log(x86emu_t *emu, char *buf, unsigned size)
459 {
460   if(!buf || !size || !opt.log_file) return;
461
462   fwrite(buf, size, 1, opt.log_file);
463 }
464
465
466 void help()
467 {
468   fprintf(stderr,
469     "Boot Loader Test\nUsage: bloat OPTIONS\n"
470     "\n"
471     "Options:\n"
472     "  --boot DEVICE\n"
473     "      boot from DEVICE\n"
474     "      DEVICE is either a number (0-0xff) or one of: floppy, disk, cdrom\n"
475     "  --disk device[,heads,sectors]\n"
476     "      add hard disk image [with geometry]\n"
477     "  --floppy device[,heads,sectors]\n"
478     "      add floppy disk image [with geometry]\n"
479     "  --cdrom device[,heads,sectors]\n"
480     "      add cdrom image [with geometry]\n"
481     "  --show LIST\n"
482     "      Things to log. LIST is a comma-separated list of code, regs, data, acc,\n"
483     "      io, ints, time, debug, dump.regs, dump.mem, dump.mem.acc, dump.mem.inv,\n"
484     "      dump.attr, dump.io, dump.ints, dump.time.\n"
485     "      Every item can be prefixed by '-' to turn it off.\n"
486     "      Use trace and dump as shorthands for a useful combination of items\n"
487     "      from the above list.\n"
488     "  --feature LIST\n"
489     "      features to enable\n"
490     "      LIST is a comma-separated list of: edd\n"
491     "  --no-feature LIST\n"
492     "      features to disable (see --features)\n"
493     "  --max N\n"
494     "      stop after N instructions\n"
495     "  --key STRING\n"
496     "      use STRING as keyboard input\n"
497     "  --log-size N\n"
498     "      internal log buffer size\n"
499     "  --help\n"
500     "      show this text\n"
501     "examples:\n"
502     "  bloat --floppy floppy.img --disk /dev/sda --disk foo.img --boot floppy\n"
503     "  bloat --disk linux.img\n"
504     "  bloat --cdrom foo.iso --show code,regs\n"
505   );
506 }
507
508
509 uint64_t vm_read_qword(x86emu_t *emu, unsigned addr)
510 {
511   return x86emu_read_dword(emu, addr) + ((uint64_t) x86emu_read_dword(emu, addr + 4) << 32);
512 }
513
514
515 unsigned vm_read_segofs16(x86emu_t *emu, unsigned addr)
516 {
517   return x86emu_read_word(emu, addr) + (x86emu_read_word(emu, addr + 2) << 4);
518 }
519
520
521 void vm_write_qword(x86emu_t *emu, unsigned addr, uint64_t val)
522 {
523   x86emu_write_dword(emu, addr, val);
524   x86emu_write_dword(emu, addr + 4, val >> 32);
525 }
526
527
528 unsigned cs2s(unsigned cs)
529 {
530   return cs & 0x3f;
531 }
532
533
534 unsigned cs2c(unsigned cs)
535 {
536   return ((cs >> 8) & 0xff) + ((cs & 0xc0) << 2);
537 }
538
539
540 int check_ip(x86emu_t *emu)
541 {
542   vm_t *vm = emu->private;
543   unsigned eip, u1, v;
544
545   eip = emu->x86.R_CS_BASE + emu->x86.R_EIP;
546
547   if(eip >= vm->bios.iv_base && eip < vm->bios.iv_base + 0x100) {
548     handle_int(emu, eip - vm->bios.iv_base);
549
550     return 0;
551   }
552
553   if(vm->maybe_linux_kernel) {
554     if(
555       emu->x86.R_EIP < 0x1000 &&
556       x86emu_read_word(emu, eip) == 0x8166 &&           // cmp dword [????],0x5a5aaa55
557       x86emu_read_dword(emu, eip + 5) == 0x5a5aaa55 &&
558       x86emu_read_dword(emu, emu->x86.R_CS_BASE + 0x202) == 0x53726448  // "HdrS"
559     ) {
560       // very likely we are starting a linux kernel
561       vm->is_linux_kernel = 1;
562
563       vm->kernel.loader_id = x86emu_read_byte(emu, emu->x86.R_CS_BASE + 0x210);
564       vm->kernel.boot_proto = x86emu_read_word(emu, emu->x86.R_CS_BASE + 0x206);
565
566       vm->kernel.code_start = x86emu_read_dword(emu, emu->x86.R_CS_BASE + 0x214);
567       vm->kernel.initrd_start = x86emu_read_dword(emu, emu->x86.R_CS_BASE + 0x218);
568       vm->kernel.initrd_length = x86emu_read_dword(emu, emu->x86.R_CS_BASE + 0x21c);
569
570       vm->kernel.cmdline_start = x86emu_read_dword(emu, emu->x86.R_CS_BASE + 0x228);
571
572       u1 = emu->x86.R_CS_BASE + x86emu_read_word(emu, emu->x86.R_CS_BASE + 0x20e) + 0x200;
573       for(v = 0; v < sizeof vm->kernel.version - 1; v++) {
574         if(!(vm->kernel.version[v] = x86emu_read_byte(emu, u1 + v))) break;
575       }
576       vm->kernel.version[v] = 0;
577
578       for(v = 0; v < sizeof vm->kernel.cmdline - 1; v++) {
579         if(!(vm->kernel.cmdline[v] = x86emu_read_byte(emu, vm->kernel.cmdline_start + v))) break;
580       }
581       vm->kernel.cmdline[v] = 0;
582
583       x86emu_log(emu, "; linux kernel version = \"%s\"\n", vm->kernel.version);
584       x86emu_log(emu, ";   boot proto = %u.%u, loader id = 0x%02x\n",
585         vm->kernel.boot_proto >> 8, vm->kernel.boot_proto & 0xff,
586         vm->kernel.loader_id
587       );
588       x86emu_log(emu, ";   code start = 0x%08x, initrd start = 0x%08x, length = 0x%08x\n",
589         vm->kernel.code_start,
590         vm->kernel.initrd_start, vm->kernel.initrd_length
591       );
592       x86emu_log(emu, ";   cmdline start = 0x%08x\n", vm->kernel.cmdline_start);
593       x86emu_log(emu, ";   cmdline = \"%s\"\n", vm->kernel.cmdline);
594       x86emu_log(emu, "# bzImage kernel start detected -- stopping\n");
595
596       return 1;
597     }
598   }
599
600   vm->maybe_linux_kernel = 0;
601
602   if(
603     emu->x86.R_EIP < 0x1000 &&
604     x86emu_read_word(emu, eip) == 0x1c75 &&             // jnz gfx_init_10
605     x86emu_read_dword(emu, eip + 2) == 0xf50073f5               // cmc ; jnc $ + 2 ; cmc
606   ) {
607     x86emu_log(emu, "# gfxboot init detected -- aborting\n");
608     emu->x86.R_EIP += 2;
609   }
610   else if(
611     emu->x86.R_EIP < 0x200 &&
612     x86emu_read_byte(emu, eip) == 0xcb &&                       // retf
613     x86emu_read_word(emu, eip + 1) == 0x8166 &&         // cmp dword [????],0x5a5aaa55
614     x86emu_read_dword(emu, eip + 6) == 0x5a5aaa55
615   ) {
616     vm->maybe_linux_kernel = 1;
617   }
618   else if(
619     emu->x86.R_EIP == 0 &&
620     eip == 0x90200 &&
621     x86emu_read_dword(emu, 0x90000) == 0x8e07c0b8 &&
622     x86emu_read_dword(emu, 0x90004) == 0x9000b8d8
623   ) {
624     vm->is_linux_kernel = 1;
625
626     x86emu_log(emu, "# zImage kernel start detected -- stopping\n");
627
628     return 1;
629   }
630
631   return 0;
632 }
633
634
635 void handle_int(x86emu_t *emu, unsigned nr)
636 {
637   vm_t *vm = emu->private;
638   int stop = 0;
639   u8 flags;
640
641   if(!vm->bios.iv_funcs[nr]) {
642     x86emu_log(emu, "# unhandled interrupt 0x%02x\n", nr);
643     stop = 1;
644   }
645   else {
646     stop = vm->bios.iv_funcs[nr](emu);
647     flags = emu->x86.R_FLG;
648     x86emu_write_byte(emu, emu->x86.R_SS_BASE + ((emu->x86.R_SP + 4) & 0xffff), flags);
649   }
650
651   if(stop) x86emu_stop(emu);
652 }
653
654
655 int do_int(x86emu_t *emu, u8 num, unsigned type)
656 {
657   if((type & 0xff) == INTR_TYPE_FAULT) {
658     x86emu_stop(emu);
659
660     return 0;
661   }
662
663   if(x86emu_read_word(emu, num * 4)) return 0;
664
665   x86emu_log(emu, "# unhandled interrupt 0x%02x\n", num);
666
667   return 1;
668 }
669
670
671 int do_int_10(x86emu_t *emu)
672 {
673   unsigned u, cnt, attr;
674   unsigned cur_x, cur_y, page;
675   unsigned x, y, x0, y0, x1, y1, width, d;
676
677   switch(emu->x86.R_AH) {
678     case 0x01:
679       x86emu_log(emu, "; int 0x10: ah 0x%02x (set cursor shape)\n", emu->x86.R_AH);
680       // emu->x86.R_CX: shape
681       break;
682
683     case 0x02:
684       x86emu_log(emu, "; int 0x10: ah 0x%02x (set cursor)\n", emu->x86.R_AH);
685       x86emu_log(emu, "; (x, y) = (%u, %u)\n", emu->x86.R_DL, emu->x86.R_DH);
686       page = emu->x86.R_BH & 7;
687       x86emu_write_byte(emu, 0x450 + 2 * page, emu->x86.R_DL);  // x
688       x86emu_write_byte(emu, 0x451 + 2 * page, emu->x86.R_DH);  // y
689       break;
690
691     case 0x03:
692       x86emu_log(emu, "; int 0x10: ah 0x%02x (get cursor)\n", emu->x86.R_AH);
693       page = emu->x86.R_BH & 7;
694       emu->x86.R_DL = x86emu_read_byte(emu, 0x450 + 2 * page);  // x
695       emu->x86.R_DH = x86emu_read_byte(emu, 0x451 + 2 * page);  // y
696       emu->x86.R_CX = 0x607;                                    // cursor shape
697       x86emu_log(emu, "; (x, y) = (%u, %u)\n", emu->x86.R_DL, emu->x86.R_DH);
698       break;
699
700     case 0x06:
701       x86emu_log(emu, "; int 0x10: ah 0x%02x (scroll up)\n", emu->x86.R_AH);
702       attr = 0x20 + (emu->x86.R_BH << 8);
703       x0 = emu->x86.R_CL;
704       y0 = emu->x86.R_CH;
705       x1 = emu->x86.R_DL;
706       y1 = emu->x86.R_DH;
707       d = emu->x86.R_AL;
708       x86emu_log(emu, ";   window (%u, %u) - (%u, %u), by %u lines\n", x0, y0, x1, y1, d);
709       width = x86emu_read_byte(emu, 0x44a);
710       if(x0 > width) x0 = width;
711       if(x1 > width) x1 = width;
712       u = x86emu_read_byte(emu, 0x484);
713       if(y0 > u) y0 = u;
714       if(y1 > u) y1 = u;
715       if(y1 > y0 && x1 > x0) {
716         if(d == 0) {
717           for(y = y0; y <= y1; y++) {
718             for(x = x0; x < x1; x++) {
719               x86emu_write_word(emu, 0xb8000 + 2 * (x + width * y), attr);
720             }
721           }
722         }
723         else {
724           for(y = y0; y < y1; y++) {
725             for(x = x0; x < x1; x++) {
726               u = x86emu_read_word(emu, 0xb8000 + 2 * (x + width * (y + 1)));
727               x86emu_write_word(emu, 0xb8000 + 2 * (x + width * y), u);
728             }
729           }
730           for(x = x0; x < x1; x++) {
731             x86emu_write_word(emu, 0xb8000 + 2 * (x + width * y), attr);
732           }
733         }
734       }
735       break;
736
737     case 0x09:
738       x86emu_log(emu, "; int 0x10: ah 0x%02x (write char & attr)\n", emu->x86.R_AH);
739       u = emu->x86.R_AL;
740       attr = emu->x86.R_BL;
741       page = emu->x86.R_BH & 7;
742       cnt = emu->x86.R_CX;
743       cur_x = x86emu_read_byte(emu, 0x450 + 2 * page);
744       cur_y = x86emu_read_byte(emu, 0x451 + 2 * page);
745       x86emu_log(emu, "; char 0x%02x '%c', attr 0x%02x, cnt %u\n", u, u >= 0x20 && u < 0x7f ? u : ' ', attr, cnt);
746       while(cnt--) {
747         x86emu_write_byte(emu, 0xb8000 + 2 * (cur_x + 80 * cur_y), u);
748         x86emu_write_byte(emu, 0xb8001 + 2 * (cur_x + 80 * cur_y), attr);
749         cur_x++;
750       }
751       break;
752
753     case 0x0e:
754       x86emu_log(emu, "; int 0x10: ah 0x%02x (tty print)\n", emu->x86.R_AH);
755       u = emu->x86.R_AL;
756       page = emu->x86.R_BH & 7;
757       cur_x = x86emu_read_byte(emu, 0x450 + 2 * page);
758       cur_y = x86emu_read_byte(emu, 0x451 + 2 * page);
759       x86emu_log(emu, "; char 0x%02x '%c'\n", u, u >= 0x20 && u < 0x7f ? u : ' ');
760       if(u == 0x0d) {
761         cur_x = 0;
762       }
763       else if(u == 0x0a) {
764         cur_y++;
765       }
766       else {
767         x86emu_write_byte(emu, 0xb8000 + 2 * (cur_x + 80 * cur_y), u);
768         x86emu_write_byte(emu, 0xb8001 + 2 * (cur_x + 80 * cur_y), 7);
769         cur_x++;
770         if(cur_x == 80) {
771           cur_x = 0;
772           cur_y++;
773         }
774       }
775       x86emu_write_byte(emu, 0x450 + 2 * page, cur_x);
776       x86emu_write_byte(emu, 0x451 + 2 * page, cur_y);
777       break;
778
779     case 0x0f:
780       emu->x86.R_AL = x86emu_read_byte(emu, 0x449);     // vide mode
781       emu->x86.R_AH = x86emu_read_byte(emu, 0x44a);     // screen width
782       emu->x86.R_BH = 0;                                // active page
783       break;
784
785     default:
786       x86emu_log(emu, "; int 0x10: ah 0x%02x\n", emu->x86.R_AH);
787       break;
788   }
789
790   return 0;
791 }
792
793
794 int do_int_11(x86emu_t *emu)
795 {
796   x86emu_log(emu, "; int 0x11: (get equipment list)\n");
797   emu->x86.R_AX = 0x4026;
798   x86emu_log(emu, "; eq mask: %04x\n", emu->x86.R_AX);
799
800   return 0;
801 }
802
803
804 int do_int_12(x86emu_t *emu)
805 {
806   x86emu_log(emu, "; int 0x12: (get base mem size)\n");
807   emu->x86.R_AX = x86emu_read_word(emu, 0x413);
808   x86emu_log(emu, "; base mem size: %u kB\n", emu->x86.R_AX);
809
810   return 0;
811 }
812
813
814 int do_int_13(x86emu_t *emu)
815 {
816   unsigned u, disk, cnt, sector, cylinder, head, addr;
817   uint64_t ul;
818   int i, j;
819
820   switch(emu->x86.R_AH) {
821     case 0x00:
822       x86emu_log(emu, "; int 0x13: ah 0x%02x (disk reset)\n", emu->x86.R_AH);
823       disk = emu->x86.R_DL;
824       x86emu_log(emu, "; drive 0x%02x\n", disk);
825       if(disk >= MAX_DISKS || !opt.disk[disk].dev) {
826         emu->x86.R_AH = 7;
827         X86EMU_SET_FLAG(emu, F_CF);
828       }
829       else {
830         emu->x86.R_AH = 0;
831         X86EMU_CLEAR_FLAG(emu, F_CF);
832       }
833       break;
834
835     case 0x02:
836       x86emu_log(emu, "; int 0x13: ah 0x%02x (disk read)\n", emu->x86.R_AH);
837       disk = emu->x86.R_DL;
838       cnt = emu->x86.R_AL;
839       head = emu->x86.R_DH;
840       cylinder = cs2c(emu->x86.R_CX);
841       sector = cs2s(emu->x86.R_CX);
842       addr = (emu->x86.R_ES << 4) + emu->x86.R_BX;
843       x86emu_log(emu, "; drive 0x%02x, chs %u/%u/%u, %u sectors, buf 0x%05x\n",
844         disk,
845         cylinder, head, sector,
846         cnt,
847         addr
848       );
849       if(cnt) {
850         if(!sector) {
851           emu->x86.R_AH = 0x04;
852           X86EMU_SET_FLAG(emu, F_CF);
853           break;
854         }
855         ul = (cylinder * opt.disk[disk].heads + head) * opt.disk[disk].sectors + sector - 1;
856         i = disk_read(emu, addr, disk, ul, cnt, 1);
857         if(i) {
858           emu->x86.R_AH = 0x04;
859           X86EMU_SET_FLAG(emu, F_CF);
860           break;
861         }
862       }      
863       emu->x86.R_AH = 0;
864       X86EMU_CLEAR_FLAG(emu, F_CF);
865       break;
866
867     case 0x08:
868       x86emu_log(emu, "; int 0x13: ah 0x%02x (get drive params)\n", emu->x86.R_AH);
869       disk = emu->x86.R_DL;
870       x86emu_log(emu, "; drive 0x%02x\n", disk);
871       if(
872         disk >= MAX_DISKS ||
873         !opt.disk[disk].dev ||
874         !opt.disk[disk].sectors ||
875         !opt.disk[disk].heads
876       ) {
877         emu->x86.R_AH = 0x07;
878         X86EMU_SET_FLAG(emu, F_CF);
879         break;
880       }
881       X86EMU_CLEAR_FLAG(emu, F_CF);
882       emu->x86.R_AH = 0;
883       emu->x86.R_ES = 0;
884       emu->x86.R_DI = 0;
885       emu->x86.R_BL = 0;
886       emu->x86.R_DL = disk < 0x80 ? opt.floppies : opt.disks;
887       emu->x86.R_DH = opt.disk[disk].heads - 1;
888       u = opt.disk[disk].cylinders;
889       if(u > 1023) u = 1023;
890       emu->x86.R_CX = ((u >> 8) << 6) + ((u & 0xff) << 8) + opt.disk[disk].sectors;
891       break;
892
893     case 0x41:
894       x86emu_log(emu, "; int 0x13: ah 0x%02x (edd install check)\n", emu->x86.R_AH);
895       disk = emu->x86.R_DL;
896       x86emu_log(emu, "; drive 0x%02x\n", disk);
897       if(!opt.feature.edd || disk >= MAX_DISKS || !opt.disk[disk].dev || emu->x86.R_BX != 0x55aa) {
898         emu->x86.R_AH = 1;
899         X86EMU_SET_FLAG(emu, F_CF);
900       }
901       else {
902         emu->x86.R_AX = 0x3000;
903         emu->x86.R_BX = 0xaa55;
904         emu->x86.R_CX = 0x000f;
905         X86EMU_CLEAR_FLAG(emu, F_CF);
906       }
907       break;
908
909     case 0x42:
910       x86emu_log(emu, "; int 0x13: ah 0x%02x (edd disk read)\n", emu->x86.R_AH);
911       disk = emu->x86.R_DL;
912       addr = (emu->x86.R_DS << 4) + emu->x86.R_SI;
913       x86emu_log(emu, "; drive 0x%02x, request packet:\n; 0x%05x:", disk, addr);
914       j = x86emu_read_byte(emu, addr);
915       j = j == 0x10 || j == 0x18 ? j : 0x10;
916       for(i = 0; i < j; i++) x86emu_log(emu, " %02x", x86emu_read_byte(emu, addr + i));
917       x86emu_log(emu, "\n");
918       if(
919         !opt.feature.edd || disk >= MAX_DISKS || !opt.disk[disk].dev ||
920         (x86emu_read_byte(emu, addr) != 0x10 && x86emu_read_byte(emu, addr) != 0x18)
921       ) {
922         emu->x86.R_AH = 1;
923         X86EMU_SET_FLAG(emu, F_CF);
924         break;
925       }
926       cnt = x86emu_read_word(emu, addr + 2);
927       u = x86emu_read_dword(emu, addr + 4);
928       ul = vm_read_qword(emu, addr + 8);
929       if(x86emu_read_byte(emu, addr) == 0x18 && u == 0xffffffff) {
930         u = x86emu_read_dword(emu, addr + 0x10);
931       }
932       else {
933         u = vm_read_segofs16(emu, addr + 4);
934       }
935       if(disk >= FIRST_CDROM) {
936         ul <<= 2;
937         cnt <<= 2;
938       }
939       i = disk_read(emu, u, disk, ul, cnt, 1);
940       if(i) {
941         emu->x86.R_AH = 0x04;
942         X86EMU_SET_FLAG(emu, F_CF);
943         break;
944       }
945       emu->x86.R_AH = 0;
946       X86EMU_CLEAR_FLAG(emu, F_CF);
947       break;
948
949     case 0x48:
950       x86emu_log(emu, "; int 0x13: ax 0x%02x (get drive params)\n", emu->x86.R_AH);
951       disk = emu->x86.R_DL;
952       x86emu_log(emu, "; drive 0x%02x\n", disk);
953       if(
954         disk >= MAX_DISKS ||
955         !opt.disk[disk].dev ||
956         !opt.disk[disk].sectors ||
957         !opt.disk[disk].heads
958       ) {
959         emu->x86.R_AH = 0x07;
960         X86EMU_SET_FLAG(emu, F_CF);
961         break;
962       }
963       X86EMU_CLEAR_FLAG(emu, F_CF);
964       emu->x86.R_AH = 0;
965
966       u = emu->x86.R_DS_BASE + emu->x86.R_SI;
967
968       x86emu_write_word(emu, u, 0x1a);  // buffer size
969       x86emu_write_word(emu, u + 2, 3);
970       x86emu_write_dword(emu, u + 4, opt.disk[disk].cylinders);
971       x86emu_write_dword(emu, u + 8, opt.disk[disk].heads);
972       x86emu_write_dword(emu, u + 0xc, opt.disk[disk].sectors);
973       vm_write_qword(emu, u + 0x10, opt.disk[disk].size);
974       x86emu_write_word(emu, u + 0x18, 0x200);  // sector size
975       break;
976
977     case 0x4b:
978       x86emu_log(emu, "; int 0x13: ax 0x%04x (terminate disk emulation)\n", emu->x86.R_AX);
979       if(emu->x86.R_AL == 1) {
980         emu->x86.R_AH = 0x01;
981         X86EMU_SET_FLAG(emu, F_CF);
982       }
983       else {
984         emu->x86.R_AH = 0x01;
985         X86EMU_SET_FLAG(emu, F_CF);
986       }
987       break;
988
989     default:
990       x86emu_log(emu, "; int 0x13: ah 0x%02x (not implemented)\n", emu->x86.R_AH);
991
992       emu->x86.R_AH = 0x01;
993       X86EMU_SET_FLAG(emu, F_CF);
994       break;
995   }
996
997   return 0;
998 }
999
1000
1001 int do_int_15(x86emu_t *emu)
1002 {
1003   vm_t *vm = emu->private;
1004   unsigned u, u1;
1005
1006   switch(emu->x86.R_AH) {
1007     case 0x24:
1008       x86emu_log(emu, "; int 0x15: ah 0x%02x (a20 gate)\n", emu->x86.R_AH);
1009       switch(emu->x86.R_AL) {
1010         case 0:
1011           vm->a20 = 0;
1012           x86emu_log(emu, "; a20 disabled\n");
1013           emu->x86.R_AH = 0;
1014           X86EMU_CLEAR_FLAG(emu, F_CF);
1015           break;
1016
1017         case 1:
1018           vm->a20 = 1;
1019           x86emu_log(emu, "; a20 enabled\n");
1020           emu->x86.R_AH = 0;
1021           X86EMU_CLEAR_FLAG(emu, F_CF);
1022           break;
1023
1024         case 2:
1025           x86emu_log(emu, "; a20 status: %u\n", vm->a20);
1026           emu->x86.R_AH = 0;
1027           emu->x86.R_AL = vm->a20;
1028           X86EMU_CLEAR_FLAG(emu, F_CF);
1029           break;
1030
1031         case 3:
1032           x86emu_log(emu, "; a20 support: 3\n");
1033           emu->x86.R_AH = 0;
1034           emu->x86.R_BX = 3;
1035           X86EMU_CLEAR_FLAG(emu, F_CF);
1036           break;
1037
1038         default:
1039           X86EMU_SET_FLAG(emu, F_CF);
1040           break;
1041       }
1042       break;
1043
1044     case 0x42:
1045       x86emu_log(emu, "; int 0x15: ax 0x%04x (thinkpad stuff)\n", emu->x86.R_AX);
1046       // emu->x86.R_AX = 0x8600;        // ask for F11
1047       // emu->x86.R_AX = 1;             // start rescue
1048       break;
1049
1050     case 0x88:
1051       x86emu_log(emu, "; int 0x15: ah 0x%02x (ext. mem size)\n", emu->x86.R_AH);
1052       u = vm->memsize - 1;
1053       x86emu_log(emu, "; ext mem size: %u MB\n", u);
1054       emu->x86.R_AX = u;
1055       X86EMU_CLEAR_FLAG(emu, F_CF);
1056       break;
1057
1058     case 0xe8:
1059       if(emu->x86.R_AL == 1) {
1060         x86emu_log(emu, "; int 0x15: ax 0x%04x (mem map (old))\n", emu->x86.R_AX);
1061         u = vm->memsize - 1;
1062         u1 = 0;
1063         if(u > 15) {
1064           u = 15;
1065           u1 = vm->memsize - 16;
1066         }
1067         emu->x86.R_AX = emu->x86.R_CX = u << 10;
1068         emu->x86.R_BX = emu->x86.R_DX = u1 << 4;
1069         x86emu_log(emu, "; ext mem sizes: %u MB + %u MB\n", u, u1);
1070         X86EMU_CLEAR_FLAG(emu, F_CF);
1071       }
1072       if(emu->x86.R_AL == 0x20 && emu->x86.R_EDX == 0x534d4150) {
1073         x86emu_log(emu, "; int 0x15: ax 0x%04x (mem map (new))\n", emu->x86.R_AX);
1074         u = vm->memsize;
1075         if(emu->x86.R_EBX == 0) {
1076           emu->x86.R_EAX = 0x534d4150;
1077           emu->x86.R_EBX = 0;
1078           emu->x86.R_ECX = 20;
1079           u1 = emu->x86.R_ES_BASE + emu->x86.R_DI;
1080           vm_write_qword(emu, u1, 0);
1081           vm_write_qword(emu, u1 + 8, (uint64_t) u << 20);
1082           x86emu_write_dword(emu, u1 + 0x10, 1);
1083           x86emu_log(emu, "; mem size: %u MB\n", u);
1084           X86EMU_CLEAR_FLAG(emu, F_CF);
1085         }
1086         else {
1087           X86EMU_SET_FLAG(emu, F_CF);
1088         }
1089       }
1090       break;
1091
1092     default:
1093       x86emu_log(emu, "; int 0x15: ax 0x%04x\n", emu->x86.R_AX);
1094       break;
1095   }
1096
1097   return 0;
1098 }
1099
1100
1101 int do_int_16(x86emu_t *emu)
1102 {
1103   vm_t *vm = emu->private;
1104   int stop = 0;
1105
1106   switch(emu->x86.R_AH) {
1107     case 0x00:
1108     case 0x10:
1109       x86emu_log(emu, "; int 0x16: ah 0x%02x (get key)\n", emu->x86.R_AH);
1110       emu->x86.R_AX = vm->key ?: next_bios_key(&opt.keyboard);
1111       if(!vm->key) {
1112         // we should rather stop here
1113         x86emu_log(emu, "; blocking key read - stopped\n");
1114         stop = 1;
1115       }
1116       vm->key = 0;
1117       vm->kbd_cnt = 0;
1118       break;
1119
1120     case 0x01:
1121     case 0x11:
1122       x86emu_log(emu, "; int 0x16: ah 0x%02x (check for key)\n", emu->x86.R_AH);
1123       vm->kbd_cnt++;
1124
1125       if(vm->key || ((vm->kbd_cnt % 4) == 3)) {
1126         X86EMU_CLEAR_FLAG(emu, F_ZF);
1127         vm->key = vm->key ?: next_bios_key(&opt.keyboard);
1128         emu->x86.R_AX = vm->key;
1129         if(!vm->key) X86EMU_SET_FLAG(emu, F_ZF);
1130       }
1131       else {
1132         X86EMU_SET_FLAG(emu, F_ZF);
1133       }
1134       break;
1135
1136     default:
1137       x86emu_log(emu, "; int 0x16: ah 0x%02x\n", emu->x86.R_AH);
1138       break;
1139   }
1140
1141   return stop;
1142 }
1143
1144
1145 int do_int_19(x86emu_t *emu)
1146 {
1147 //  vm_t *vm = emu->private;
1148
1149   x86emu_log(emu, "; int 0x19: (boot next device)\n");
1150
1151   return 1;
1152 }
1153
1154
1155 int do_int_1a(x86emu_t *emu)
1156 {
1157   // vm_t *vm = emu->private;
1158   unsigned t;
1159
1160   switch(emu->x86.R_AH) {
1161     case 0x00:
1162       x86emu_log(emu, "; int 0x1a: ah 0x%02x (get system time)\n", emu->x86.R_AH);
1163       t = x86emu_read_dword(emu, 0x46c);
1164       x86emu_write_dword(emu, 0x46c, ++t);
1165       emu->x86.R_DX = t;
1166       emu->x86.R_CX = t >> 16;
1167       x86emu_log(emu, "; time = 0x%08x\n", t);
1168       break;
1169
1170     default:
1171       x86emu_log(emu, "; int 0x1a: ah 0x%02x\n", emu->x86.R_AH);
1172       break;
1173   }
1174
1175   return 0;
1176 }
1177
1178
1179 vm_t *vm_new()
1180 {
1181   vm_t *vm;
1182
1183   vm = calloc(1, sizeof *vm);
1184
1185   vm->emu = x86emu_new(X86EMU_PERM_R | X86EMU_PERM_W | X86EMU_PERM_X, 0);
1186   vm->emu->private = vm;
1187
1188   if(opt.verbose >= 1 || opt.trace_flags || opt.dump_flags) {
1189     x86emu_set_log(vm->emu, opt.log_size ?: 100000000, flush_log);
1190   }
1191
1192   vm->emu->log.trace = opt.trace_flags;
1193
1194   x86emu_set_intr_handler(vm->emu, do_int);
1195   x86emu_set_code_handler(vm->emu, check_ip);
1196
1197   return vm;
1198 }
1199
1200
1201 void vm_free(vm_t *vm)
1202 {
1203   x86emu_done(vm->emu);
1204
1205   free(vm);
1206 }
1207
1208
1209 void vm_run(vm_t *vm)
1210 {
1211   unsigned flags;
1212
1213   vm->emu->x86.R_DL = opt.boot;
1214
1215   if(x86emu_read_word(vm->emu, 0x7c00) == 0) {
1216     x86emu_log(vm->emu, "# no boot code\n");
1217     x86emu_clear_log(vm->emu, 1);
1218
1219     return;
1220   }
1221
1222   flags = X86EMU_RUN_LOOP | X86EMU_RUN_NO_CODE;
1223   if(opt.inst_max) {
1224     vm->emu->max_instr = opt.inst_max;
1225     flags |= X86EMU_RUN_MAX_INSTR;
1226   }
1227
1228   x86emu_run(vm->emu, flags);
1229
1230   if(opt.dump_flags) {
1231     x86emu_log(vm->emu, "\n; - - - emulator state\n");
1232     x86emu_dump(vm->emu, opt.dump_flags);
1233     x86emu_log(vm->emu, "; - - -\n");
1234   }
1235
1236   x86emu_clear_log(vm->emu, 1);
1237 }
1238
1239
1240 void prepare_bios(vm_t *vm)
1241 {
1242   unsigned u;
1243   x86emu_t *emu = vm->emu;
1244
1245   vm->memsize = 1024;   // 1GB RAM
1246
1247   // start address 0:0x7c00
1248   x86emu_set_seg_register(vm->emu, vm->emu->x86.R_CS_SEL, 0);
1249   vm->emu->x86.R_EIP = 0x7c00;
1250
1251   x86emu_write_word(emu, 0x413, 640);   // mem size in kB
1252   x86emu_write_byte(emu, 0x449, 3);             // video mode
1253   x86emu_write_byte(emu, 0x44a, 80);            // columns
1254   x86emu_write_byte(emu, 0x484, 24);            // rows - 1
1255   x86emu_write_byte(emu, 0x485, 16);            // char height
1256   x86emu_write_byte(emu, 0x462, 0);             // current text page
1257   x86emu_write_word(emu, 0x450, 0);             // page 0 cursor pos
1258
1259   x86emu_write_dword(emu, 0x46c, 0);            // time
1260
1261   vm->bios.iv_base = 0xf8000 + 0x100;
1262   for(u = 0; u < 0x100; u++) {
1263     x86emu_write_byte(emu, vm->bios.iv_base + u, 0xcf); // iret
1264   }
1265
1266   x86emu_write_word(emu, 0x10*4, 0x100 + 0x10);
1267   x86emu_write_word(emu, 0x10*4+2, 0xf800);
1268   vm->bios.iv_funcs[0x10] = do_int_10;
1269
1270   x86emu_write_word(emu, 0x11*4, 0x100 + 0x11);
1271   x86emu_write_word(emu, 0x11*4+2, 0xf800);
1272   vm->bios.iv_funcs[0x11] = do_int_11;
1273
1274   x86emu_write_word(emu, 0x12*4, 0x100 + 0x12);
1275   x86emu_write_word(emu, 0x12*4+2, 0xf800);
1276   vm->bios.iv_funcs[0x12] = do_int_12;
1277
1278   x86emu_write_word(emu, 0x13*4, 0x100 + 0x13);
1279   x86emu_write_word(emu, 0x13*4+2, 0xf800);
1280   vm->bios.iv_funcs[0x13] = do_int_13;
1281
1282   x86emu_write_word(emu, 0x15*4, 0x100 + 0x15);
1283   x86emu_write_word(emu, 0x15*4+2, 0xf800);
1284   vm->bios.iv_funcs[0x15] = do_int_15;
1285
1286   x86emu_write_word(emu, 0x16*4, 0x100 + 0x16);
1287   x86emu_write_word(emu, 0x16*4+2, 0xf800);
1288   vm->bios.iv_funcs[0x16] = do_int_16;
1289
1290   x86emu_write_word(emu, 0x19*4, 0x100 + 0x19);
1291   x86emu_write_word(emu, 0x19*4+2, 0xf800);
1292   vm->bios.iv_funcs[0x19] = do_int_19;
1293
1294   x86emu_write_word(emu, 0x1a*4, 0x100 + 0x1a);
1295   x86emu_write_word(emu, 0x1a*4+2, 0xf800);
1296   vm->bios.iv_funcs[0x1a] = do_int_1a;
1297 }
1298
1299
1300 int el_torito_boot(x86emu_t *emu, unsigned disk)
1301 {
1302   unsigned char sector[2048];
1303   unsigned et, u;
1304   unsigned start, load_len, load_addr;
1305   int ok = 0;
1306
1307   disk_read(emu, 0x7c00, disk, 0x11 * 4, 4, 1); /* 1 sector from 0x8800 */
1308   for(u = 0; u < 2048; u++) {
1309     sector[u] = x86emu_read_byte_noperm(emu, 0x7c00 + u);
1310   }
1311
1312   if(
1313     sector[0] == 0 && sector[6] == 1 &&
1314     !memcmp(sector + 1, "CD001", 5) &&
1315     !memcmp(sector + 7, "EL TORITO SPECIFICATION", 23)
1316   ) {
1317     et = sector[0x47] + (sector[0x48] << 8) + (sector[0x49] << 16) + (sector[0x4a] << 24);
1318     lprintf("el_torito_boot: boot catalog at 0x%04x\n", et);
1319     if(!disk_read(emu, 0x7c00, disk, et * 4, 4, 1)) {
1320       if(x86emu_read_byte_noperm(emu, 0x7c20) == 0x88) {        /* bootable */
1321         load_addr = x86emu_read_word(emu, 0x7c22) << 4;
1322         if(!load_addr) load_addr = 0x7c00;
1323         load_len = x86emu_read_word(emu, 0x7c26) << 9;
1324         start = x86emu_read_dword(emu, 0x7c28);
1325
1326         lprintf(
1327           "el_torito_boot: load 0x%x bytes from sector 0x%x to 0x%x\n",
1328           load_len, start, load_addr
1329         );
1330
1331         disk_read(emu, load_addr, disk, start * 4, load_len >> 9, 1);
1332         ok = 1;
1333       }
1334     }
1335   }
1336
1337   return ok;
1338 }
1339
1340
1341 void prepare_boot(x86emu_t *emu)
1342 {
1343   if(opt.boot < FIRST_CDROM) {
1344     disk_read(emu, 0x7c00, opt.boot, 0, 1, 1);
1345   }
1346   else {
1347     el_torito_boot(emu, opt.boot);
1348   }
1349 }
1350
1351
1352 int disk_read(x86emu_t *emu, unsigned addr, unsigned disk, uint64_t sector, unsigned cnt, int log)
1353 {
1354   off_t ofs;
1355   unsigned char *buf;
1356   unsigned u;
1357
1358   if(log) x86emu_log(emu, "; read: disk 0x%02x, sector %llu (%u) @ 0x%05x - ",
1359     disk, (unsigned long long) sector, cnt, addr
1360   );
1361
1362   if(disk >= MAX_DISKS || !opt.disk[disk].dev) {
1363     if(log) x86emu_log(emu, "invalid disk\n");
1364     return 2;
1365   }
1366
1367   if(opt.disk[disk].fd < 0) {
1368     if(log) x86emu_log(emu, "failed to open disk\n");
1369     return 3;
1370   }
1371
1372   ofs = sector << 9;
1373
1374   if(lseek(opt.disk[disk].fd, ofs, SEEK_SET) != ofs) {
1375     if(log) x86emu_log(emu, "sector not found\n");
1376     return 4;
1377   }
1378
1379   buf = malloc(cnt << 9);
1380
1381   if(read(opt.disk[disk].fd, buf, cnt << 9) != (cnt << 9)) {
1382     if(log) x86emu_log(emu, "read error\n");
1383     free(buf);
1384
1385     return 5;
1386   }
1387
1388   for(u = 0; u < cnt << 9; u++) {
1389     x86emu_write_byte(emu, addr + u, buf[u]);
1390   }
1391
1392   free(buf);
1393
1394   if(log) x86emu_log(emu, "ok\n");
1395
1396   return 0;
1397 }
1398
1399
1400 void parse_ptable(x86emu_t *emu, unsigned addr, ptable_t *ptable, unsigned base, unsigned ext_base, int entries)
1401 {
1402   unsigned u;
1403
1404   memset(ptable, 0, entries * sizeof *ptable);
1405
1406   for(; entries; entries--, addr += 0x10, ptable++) {
1407     u = x86emu_read_byte(emu, addr);
1408     if(u & 0x7f) continue;
1409     ptable->boot = u >> 7;
1410     ptable->type = x86emu_read_byte(emu, addr + 4);
1411     u = x86emu_read_word(emu, addr + 2);
1412     ptable->start.c = cs2c(u);
1413     ptable->start.s = cs2s(u);
1414     ptable->start.h = x86emu_read_byte(emu, addr + 1);
1415     ptable->start.lin = x86emu_read_dword(emu, addr + 8);
1416     u = x86emu_read_word(emu, addr + 6);
1417     ptable->end.c = cs2c(u);
1418     ptable->end.s = cs2s(u);
1419     ptable->end.h = x86emu_read_byte(emu, addr + 5);
1420     ptable->end.lin = ptable->start.lin + x86emu_read_dword(emu, addr + 0xc);
1421
1422     ptable->base = is_ext_ptable(ptable) ? ext_base : base;
1423
1424     if(ptable->end.lin != ptable->start.lin && ptable->start.s && ptable->end.s) {
1425       ptable->valid = 1;
1426       ptable->end.lin--;
1427     }
1428   }
1429 }
1430
1431
1432 int guess_geo(ptable_t *ptable, int entries, unsigned *s, unsigned *h)
1433 {
1434   unsigned sectors, heads, u, c;
1435   int i, ok, cnt;
1436
1437   for(sectors = 63; sectors; sectors--) {
1438     for(heads = 255; heads; heads--) {
1439       ok = 1;
1440       for(cnt = i = 0; i < entries; i++) {
1441         if(!ptable[i].valid) continue;
1442
1443         if(ptable[i].start.h >= heads) { ok = 0; break; }
1444         if(ptable[i].start.s > sectors) { ok = 0; break; }
1445         if(ptable[i].start.c >= 1023) {
1446           c = ((ptable[i].start.lin + 1 - ptable[i].start.s)/sectors - ptable[i].start.h)/heads;
1447           if(c < 1023) { ok = 0; break; }
1448         }
1449         else {
1450           c = ptable[i].start.c;
1451         }
1452         u = (c * heads + ptable[i].start.h) * sectors + ptable[i].start.s - 1;
1453         if(u != ptable[i].start.lin) {
1454           ok = 0;
1455           break;
1456         }
1457         cnt++;
1458         if(ptable[i].end.h >= heads) { ok = 0; break; }
1459         if(ptable[i].end.s > sectors) { ok = 0; break; }
1460         if(ptable[i].end.c >= 1023) {
1461           c = ((ptable[i].end.lin + 1 - ptable[i].end.s)/sectors - ptable[i].end.h)/heads;
1462           if(c < 1023) { ok = 0; break; }
1463         }
1464         else {
1465           c = ptable[i].end.c;
1466         }
1467         u = (c * heads + ptable[i].end.h) * sectors + ptable[i].end.s - 1;
1468         if(u != ptable[i].end.lin) {
1469           ok = 0;
1470           break;
1471         }
1472         cnt++;
1473       }
1474       if(!cnt) ok = 0;
1475       if(ok) break;
1476     }
1477     if(ok) break;
1478   }
1479
1480   if(ok) {
1481     *h = heads;
1482     *s = sectors;
1483   }
1484
1485   return ok;
1486 }
1487
1488
1489 void print_ptable_entry(int nr, ptable_t *ptable)
1490 {
1491   unsigned u;
1492
1493   if(ptable->valid) {
1494     lprintf(";     ");
1495     if(nr > 4 && is_ext_ptable(ptable)) {
1496       lprintf("  -");
1497     }
1498     else {
1499       lprintf("%3d", nr);
1500     }
1501
1502     u = opt.show.rawptable ? 0 : ptable->base;
1503
1504     lprintf(": %c 0x%02x, start %4u/%3u/%2u %9u, end %4u/%3u/%2u %9u",
1505       ptable->boot ? '*' : ' ',
1506       ptable->type,
1507       ptable->start.c, ptable->start.h, ptable->start.s,
1508       ptable->start.lin + u,
1509       ptable->end.c, ptable->end.h, ptable->end.s,
1510       ptable->end.lin + u
1511     );
1512
1513     if(opt.show.rawptable) lprintf(" %+9d", ptable->base);
1514     lprintf("\n");
1515   }
1516 }
1517
1518
1519 int is_ext_ptable(ptable_t *ptable)
1520 {
1521   return ptable->type == 5 || ptable->type == 0xf;
1522 }
1523
1524
1525 ptable_t *find_ext_ptable(ptable_t *ptable, int entries)
1526 {
1527   for(; entries; entries--, ptable++) {
1528     if(ptable->valid && is_ext_ptable(ptable)) return ptable;
1529   }
1530   return NULL;
1531 }
1532
1533
1534 void dump_ptable(x86emu_t *emu, unsigned disk)
1535 {
1536   int i, j, pcnt, link_count;
1537   ptable_t ptable[4], *ptable_ext;
1538   unsigned s, h, ext_base;
1539
1540   i = disk_read(emu, 0, disk, 0, 1, 0);
1541
1542   if(i || x86emu_read_word(emu, 0x1fe) != 0xaa55) {
1543     lprintf(";     no partition table\n");
1544     return;
1545   }
1546
1547   parse_ptable(emu, 0x1be, ptable, 0, 0, 4);
1548   i = guess_geo(ptable, 4, &s, &h);
1549   lprintf(";     partition table (");
1550   if(i) {
1551     lprintf("hs %u/%u):\n", h, s);
1552   }
1553   else {
1554     lprintf("inconsistent chs):\n");
1555   }
1556
1557   for(i = 0; i < 4; i++) {
1558     print_ptable_entry(i + 1, ptable + i);
1559   }
1560
1561   pcnt = 5;
1562
1563   link_count = 0;
1564   ext_base = 0;
1565
1566   while((ptable_ext = find_ext_ptable(ptable, 4))) {
1567     if(!link_count++) {
1568       ext_base = ptable_ext->start.lin;
1569     }
1570     // arbitrary, but we don't want to loop forever
1571     if(link_count > 10000) {
1572       lprintf(";    too many partitions\n");
1573       break;
1574     }
1575     j = disk_read(emu, 0, disk, ptable_ext->start.lin + ptable_ext->base, 1, 0);
1576     if(j || x86emu_read_word(emu, 0x1fe) != 0xaa55) {
1577       lprintf(";    ");
1578       if(j) lprintf("disk read error - ");
1579       lprintf("not a valid extended partition\n");
1580       break;
1581     }
1582     parse_ptable(emu, 0x1be, ptable, ptable_ext->start.lin + ptable_ext->base, ext_base, 4);
1583     for(i = 0; i < 4; i++) {
1584       print_ptable_entry(pcnt, ptable + i);
1585       if(ptable[i].valid && !is_ext_ptable(ptable + i)) pcnt++;
1586     }
1587   }
1588 }
1589
1590
1591 char *get_screen(x86emu_t *emu)
1592 {
1593   unsigned u, x, y;
1594   unsigned base = 0xb8000;
1595   char *s, s_l[80 + 1];
1596   static char screen[80*25+1];
1597
1598   *screen = 0;
1599
1600   for(y = 0; y < 25; y++, base += 80 * 2) {
1601     for(x = 0; x < 80; x++) {
1602       u = x86emu_read_byte_noperm(emu, base + 2 * x);
1603       if(u < 0x20) u = ' ';
1604       s_l[x] = u;
1605     }
1606     s_l[x] = 0;
1607     for(s = s_l + x - 1; s >= s_l; s--) {
1608       if(*s != ' ') break;
1609       *s = 0;
1610     }
1611
1612     if(*s_l) strcat(strcat(screen, s_l), "\n");
1613   }
1614
1615   return screen;
1616 }
1617
1618
1619 void dump_screen(x86emu_t *emu)
1620 {
1621   unsigned char *s = (unsigned char *) get_screen(emu);
1622
1623   lprintf("; - - - screen\n");
1624   while(*s) {
1625     lprintf("%s", cp437[*s++]);
1626   }
1627   lprintf("; - - -\n");
1628 }
1629
1630
1631 unsigned next_bios_key(char **keys)
1632 {
1633   char *s, buf[3];
1634   unsigned char uc;
1635   unsigned k = 0, u, n;
1636
1637   if(!keys || !*keys || !**keys) return k;
1638
1639   if(**keys == '[') {
1640     (*keys)++;
1641     s = strchr(*keys, ']');
1642     if(s) {
1643       n = s - *keys;
1644       s++;
1645       for(u = 0; u < sizeof bios_key_list / sizeof *bios_key_list; u++) {
1646         if(!strncmp(bios_key_list[u].name, *keys, n)) {
1647           k = (bios_key_list[u].scan << 8) + bios_key_list[u].ascii;
1648           break;
1649         }
1650       }
1651     }
1652     *keys = s;
1653
1654     // fprintf(stderr, "key >0x%04x<\n", k);
1655
1656     return k;
1657   }
1658
1659   uc = *(*keys)++;
1660
1661   if(uc == '\\') {
1662     s = *keys;
1663     if(*s == 'x' && isxdigit(s[1]) && isxdigit(s[2])) {
1664       buf[0] = s[1];
1665       buf[1] = s[2];
1666       buf[2] = 0;
1667       *keys += 3;
1668       uc = strtoul(buf, NULL, 16);
1669     }
1670     else {
1671      uc = *s;
1672      (*keys)++;
1673     }
1674   }
1675
1676   k = uc;
1677
1678   for(u = 0; u < sizeof bios_key_list / sizeof *bios_key_list; u++) {
1679     if(bios_key_list[u].ascii == uc) {
1680       k = (bios_key_list[u].scan << 8) + uc;
1681       break;
1682     }
1683   }
1684
1685   // fprintf(stderr, "key >0x%04x<\n", k);
1686
1687   return k;
1688 }
1689
1690