├── .gitignore ├── Makefile ├── Makefile.mxe ├── README.md ├── bin2exe.c ├── bochs-config ├── booterify.c ├── bootsector.asm ├── bootstrap.c ├── bootstrap.h ├── changelog.txt ├── exeinfo.c ├── generatetests.sh ├── jrromchk.c ├── makewin32archive.sh ├── pcjrloader.asm ├── release.sh ├── testing ├── README ├── disks │ └── .gitignore ├── executables │ └── .gitignore └── logs │ └── .gitignore └── version.inc /.gitignore: -------------------------------------------------------------------------------- 1 | booterify 2 | exeinfo 3 | bootsector.map 4 | *.o 5 | *.dsk 6 | *.bin 7 | *.BIN 8 | .DS_Store 9 | *.exe 10 | *.EXE 11 | *.com 12 | *.COM 13 | *.zip 14 | *.swp 15 | *.map 16 | *.jrc 17 | *.JRC 18 | *.lst 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NASM=nasm 2 | CC=gcc 3 | LD=$(CC) 4 | include version.inc 5 | CFLAGS=-Wall -O0 -DVERSION=\"$(VERSION)\" 6 | 7 | all: bootsector.bin booterify exeinfo jrromchk pcjrloader.bin bin2exe 8 | 9 | bootsector.bin: bootsector.asm 10 | nasm $< -fbin -o $@ -O0 -DVERSION=\"$(VERSION)\" -l bootsector.lst 11 | ls -lh $@ 12 | 13 | pcjrloader.bin: pcjrloader.asm 14 | nasm $< -fbin -o $@ -O0 -DVERSION=\"$(VERSION)\" -l pcjrloader.lst 15 | ls -lh $@ 16 | 17 | booterify: booterify.o bootstrap.o 18 | $(LD) $(LDFLAGS) $^ -o $@ 19 | 20 | exeinfo: exeinfo.o 21 | $(LD) $(LDFLAGS) $^ -o $@ 22 | 23 | jrromchk: jrromchk.o 24 | $(LD) $(LDFLAGS) $^ -o $@ 25 | 26 | bin2exe: bin2exe.o 27 | $(LD) $(LDFLAGS) $^ -o $@ 28 | 29 | clean: 30 | rm -f bootsector.bin *.o booterify exeinfo pcjrloader.bin jrromchk 31 | -------------------------------------------------------------------------------- /Makefile.mxe: -------------------------------------------------------------------------------- 1 | NASM=nasm 2 | CC=i686-w64-mingw32.static-gcc 3 | LD=$(CC) 4 | include version.inc 5 | CFLAGS=-Wall -O0 -DVERSION=\"$(VERSION)\" 6 | 7 | PRG=bootsector.bin booterify.exe exeinfo.exe jrromchk.exe pcjrloader.bin bin2exe.exe 8 | 9 | all: $(PRG) 10 | 11 | pcjrloader.bin: pcjrloader.asm 12 | nasm $< -fbin -o $@ -O0 -DVERSION=\"$(VERSION)\" -l pcjrloader.lst 13 | ls -lh $@ 14 | 15 | bootsector.bin: bootsector.asm 16 | nasm $< -fbin -o $@ -O0 -DVERSION=\"$(VERSION)\" 17 | ls -lh $@ 18 | 19 | booterify.exe: booterify.o bootstrap.o 20 | $(LD) $(LDFLAGS) $^ -o $@ 21 | 22 | exeinfo.exe: exeinfo.o 23 | $(LD) $(LDFLAGS) $^ -o $@ 24 | 25 | jrromchk.exe: jrromchk.o 26 | $(LD) $(LDFLAGS) $^ -o $@ 27 | 28 | bin2exe.exe: bin2exe.o 29 | $(LD) $(LDFLAGS) $^ -o $@ 30 | 31 | 32 | clean: 33 | rm -f bootsector.bin *.o booterify.exe exeinfo.exe jrromchk.exe pcjrloader.bin 34 | 35 | dist: 36 | rm -f booterify_win32-$(VERSION).zip 37 | zip booterify_win32-$(VERSION).zip $(PRG) *.c *.h *.asm *.map *.txt *.md *.inc bochs-config Makefile* 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Booterify: Bootloader for 16-bit .COM and .EXE executables 2 | 3 | Booterify is a simple bootloader that fits in the first sector of a floppy (512 bytes) and a tool to create boot disks form a DOS executable (.COM or .EXE). 4 | 5 | ## Features 6 | 7 | - Supports .COM executables 8 | - Supports .EXE executables (64 kB max.) 9 | - Provides a BPB (Bios parameter block) to enable booting from USB keys 10 | - Detects potential DOS interrupt calls, displays their address and hex dump around the call 11 | - Can generate a list of breakpoints in bochs syntax for each DOS service call instance 12 | - Implemented DOS services: 13 | - int 21h/AH=02h : Display character 14 | - int 21h/AH=09h : Display string 15 | - int 21h/AH=25h : Get interrupt vector 16 | - int 21h/AH=35h : Set interrupt vector 17 | 18 | ## For more information... 19 | 20 | Visit the [project homepage](http://www.raphnet.net/programmation/booterify/index_en.php) for example 21 | uses, etc. 22 | 23 | ## License 24 | 25 | MIT License. 26 | -------------------------------------------------------------------------------- /bin2exe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define EXTRA_BTTES_OFFSET 2 6 | #define NUM_PAGES_OFFSET 4 7 | #define INITIAL_IP_OFFSET 20 8 | 9 | #define DEFAULT_IP 0x0000 10 | 11 | static void writeLe16(unsigned char *dst, int offset, int value) 12 | { 13 | dst[offset] = value; 14 | dst[offset+1] = value >> 8; 15 | } 16 | 17 | static void printHelp(void) 18 | { 19 | printf("Usage: ./bin2exe [options] input_file output_file.exe\n"); 20 | printf(" -i offset Set initial instruction pointer (IP). Default: %d\n", DEFAULT_IP); 21 | printf(" -v Enable verbose output\n"); 22 | } 23 | 24 | int main(int argc, char **argv) 25 | { 26 | FILE *infptr, *outfptr; 27 | const char *infile, *outfile; 28 | long insize; 29 | int initial_ip = DEFAULT_IP; 30 | int verbose = 0; 31 | int opt; 32 | int pages, extra_bytes; 33 | unsigned char *binary; 34 | unsigned char exe_header[32] = { 35 | 'M','Z', // SIG 36 | 0,0, // Extra bytes 37 | 0,0, // Pages 38 | 0,0, // Relocation items (none) 39 | 2,0, // Header size (in paragraphs. 32 bytes) 40 | 0x00, 0x10, // minimum paragraphs 41 | 0x00, 0x10, // required paragraphs 42 | 0x00, 0x00, // initial SS 43 | 0xFF, 0xFF, // initial SP 44 | 0x00, 0x00, // checksum 45 | 0x00, 0x00, // Initial IP 46 | 0x00, 0x00, // initial CS 47 | 0x00, 0x00, // relocation segment offset 48 | 0x00, 0x00, // overlay 49 | 0x00, 0x00, // overlay information 50 | }; 51 | 52 | while (-1 != (opt = getopt(argc, argv, "hi:v"))) { 53 | switch (opt) 54 | { 55 | case 'h': 56 | printHelp(); 57 | return 0; 58 | 59 | case 'i': 60 | initial_ip = strtol(optarg, NULL, 0); 61 | break; 62 | 63 | case 'v': 64 | verbose = 1; 65 | break; 66 | 67 | case '?': 68 | break; 69 | 70 | default: 71 | fprintf(stderr, "unknown option. try -h\n"); 72 | return -1; 73 | } 74 | } 75 | 76 | if (argc - optind < 2) { 77 | fprintf(stderr, "An input and output file must be specified. See -h\n"); 78 | return -1; 79 | } 80 | 81 | infile = argv[optind]; 82 | outfile = argv[optind+1]; 83 | 84 | if (verbose) { 85 | printf("Input file: %s\n", infile); 86 | printf("Output file: %s\n", outfile); 87 | printf("initial ip: $%04x\n", initial_ip); 88 | } 89 | 90 | infptr = fopen(infile, "rb"); 91 | if (!infptr) { 92 | perror(infile); 93 | return -1; 94 | } 95 | 96 | fseek(infptr, 0, SEEK_END); 97 | insize = ftell(infptr); 98 | if (insize > 0x10000) { 99 | fprintf(stderr, "Input file too large (> 64k)\n"); 100 | fclose(infptr); 101 | return -1; 102 | } 103 | 104 | binary = malloc(insize); 105 | if (!binary) { 106 | perror("could not allocate memory to load file\n"); 107 | fclose(infptr); 108 | return -1; 109 | } 110 | 111 | fseek(infptr, 0, SEEK_SET); 112 | if (fread(binary, insize, 1, infptr) != 1) { 113 | perror("cound not read input file\n"); 114 | free(binary); 115 | fclose(infptr); 116 | return -1; 117 | } 118 | 119 | fclose(infptr); 120 | 121 | outfptr = fopen(outfile, "wb"); 122 | if (!outfptr) { 123 | perror(outfile); 124 | free(binary); 125 | return -1; 126 | } 127 | 128 | // update the header 129 | 130 | pages = (insize + 511) / 512; 131 | extra_bytes = insize - (insize / 512) * 512; 132 | 133 | if (verbose) { 134 | printf("Size: %ld bytes (%d pages + %d byte(s))\n", insize, pages, extra_bytes); 135 | } 136 | 137 | writeLe16(exe_header, INITIAL_IP_OFFSET, initial_ip); 138 | writeLe16(exe_header, EXTRA_BTTES_OFFSET, extra_bytes); 139 | writeLe16(exe_header, NUM_PAGES_OFFSET, pages); 140 | 141 | 142 | fwrite(exe_header, sizeof(exe_header), 1, outfptr); 143 | fwrite(binary, insize, 1, outfptr); 144 | 145 | fclose(outfptr); 146 | 147 | free(binary); 148 | 149 | return 0; 150 | } 151 | -------------------------------------------------------------------------------- /bochs-config: -------------------------------------------------------------------------------- 1 | # configuration file generated by Bochs 2 | plugin_ctrl: unmapped=1, biosdev=1, speaker=1, extfpuirq=1, parallel=1, serial=1, gameport=1, iodebug=1 3 | config_interface: textconfig 4 | display_library: sdl 5 | memory: host=32, guest=32 6 | romimage: file="/usr/share/bochs/BIOS-bochs-latest" 7 | vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest" 8 | boot: floppy 9 | floppy_bootsig_check: disabled=0 10 | floppya: type=1_2, 360k="test.dsk", status=inserted, write_protected=1 11 | # no floppyb 12 | ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 13 | ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15 14 | ata2: enabled=0 15 | ata3: enabled=0 16 | pci: enabled=1, chipset=i440fx 17 | vga: extension=none, update_freq=60 18 | cpu: count=1, ips=4000000, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 19 | cpuid: family=6, model=0x03, stepping=3, mmx=1, apic=xapic, sse=sse2, sse4a=0, sep=1, aes=0, xsave=0, xsaveopt=0, movbe=0, adx=0, smep=0, avx=0, avx_f16c=0, avx_fma=0, bmi=0, xop=0, tbm=0, fma4=0, vmx=1, x86_64=1, 1g_pages=0, pcid=0, fsgsbase=0, mwait=1 20 | cpuid: vendor_string="GenuineIntel" 21 | cpuid: brand_string=" Intel(R) Pentium(R) 4 CPU " 22 | 23 | print_timestamps: enabled=0 24 | debugger_log: - 25 | magic_break: enabled=1 26 | port_e9_hack: enabled=0 27 | private_colormap: enabled=0 28 | clock: sync=slowdown, time0=local, rtc_sync=0 29 | # no cmosimage 30 | # no loader 31 | log: - 32 | logprefix: %t%e%d 33 | panic: action=ask 34 | error: action=report 35 | info: action=report 36 | debug: action=ignore 37 | keyboard: type=mf, serial_delay=250, paste_delay=100000, keymap= 38 | user_shortcut: keys=none 39 | mouse: enabled=0, type=ps2, toggle=ctrl+mbutton 40 | parport1: enabled=1, file="" 41 | parport2: enabled=0 42 | com1: enabled=1, mode=null, dev="" 43 | com2: enabled=0 44 | com3: enabled=0 45 | com4: enabled=0 46 | -------------------------------------------------------------------------------- /booterify.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "bootstrap.h" 7 | 8 | #define DESTINATION_SEGMENT 0x1000 9 | 10 | #define DEFAULT_DISK_LABEL "Booterify" 11 | 12 | unsigned char payload[0x10000]; 13 | 14 | // EXE header fields 15 | #define SIG 0 16 | #define LAST_PAGE_SIZE 1 17 | #define FILE_PAGES 2 18 | #define RELOCATION_ITEMS 3 19 | #define HEADER_PARAGRAPHS 4 20 | #define MINALLOC 5 21 | #define MAXALLOC 6 22 | #define INITIAL_SS 7 23 | #define INITIAL_SP 8 24 | #define CHECKSUM 9 25 | #define INITIAL_IP 10 26 | #define PRE_RELOCATE_CS 11 27 | #define RELOCATION_TABLE_OFFSET 12 28 | #define OVERLAY_NUMBER 13 29 | 30 | void hexdump(const unsigned char *data, int len, int address) 31 | { 32 | int i; 33 | 34 | for (i=0; i 11) { 186 | fprintf(stderr, "Volume label is too long\n"); 187 | return -1; 188 | } 189 | memcpy(directory_entry, name, namelength); 190 | 191 | directory_entry[0x0B] = attr; 192 | directory_entry[0x16] = mod_time; 193 | directory_entry[0x17] = mod_time >> 8; 194 | directory_entry[0x18] = mod_date; 195 | directory_entry[0x19] = mod_date >> 8; 196 | directory_entry[0x1A] = start_cluster; 197 | directory_entry[0x1B] = start_cluster >> 8; 198 | directory_entry[0x1C] = file_size; 199 | directory_entry[0x1D] = file_size >> 8; 200 | directory_entry[0x1E] = file_size >> 16; 201 | directory_entry[0x1F] = file_size >> 24; 202 | 203 | fwrite(directory_entry, sizeof(directory_entry), 1, outfptr); 204 | return 0; 205 | } 206 | 207 | int writeDirectoryEntry(FILE *outfptr, const char *filename, const char *extension, 208 | uint8_t attr, uint16_t mod_time, uint16_t mod_date, 209 | uint16_t start_cluster, uint32_t file_size) 210 | { 211 | char tmp[12]; 212 | snprintf(tmp, 12, "%-8s%-3s", filename, extension); 213 | return writeVolumeLabel(outfptr, tmp, attr, mod_time, mod_date, start_cluster, file_size); 214 | } 215 | 216 | int main(int argc, char **argv) 217 | { 218 | FILE *fptr_payload, *fptr_disk; 219 | int bootstrap_size; 220 | int payload_size; 221 | unsigned short n_sectors, initial_ip = 0x100; 222 | unsigned short initial_cs = DESTINATION_SEGMENT; 223 | unsigned short initial_ds = DESTINATION_SEGMENT; 224 | unsigned short initial_ss = DESTINATION_SEGMENT; 225 | unsigned short initial_sp = 0xFFFF; 226 | unsigned char sectors_per_track = 9; 227 | int opt, i, retval = -1; 228 | const char *bootstrap_file, *executable_file, *output_file; 229 | uint16_t exe_header[14]; 230 | char is_exe; 231 | int dos_interrupts = 0; 232 | FILE *breakpoints_ints_fptr = NULL; 233 | struct bootstrap_bpb bpb; 234 | int first_cluster_sector = 512; 235 | int disk_size = 1440; 236 | const char *disk_label = DEFAULT_DISK_LABEL; 237 | unsigned short destination_segment = DESTINATION_SEGMENT; 238 | int write_bpb = 1; 239 | 240 | while (-1 != (opt = getopt(argc, argv, "hB:f:l:vs:"))) { 241 | switch(opt) 242 | { 243 | case 'h': 244 | printHelp(); 245 | return 0; 246 | case 'v': 247 | printVersion(); 248 | return 0; 249 | case '?': 250 | fprintf(stderr, "Unknown option. Try -h\n"); 251 | return 1; 252 | break; 253 | case 'f': 254 | disk_size = atoi(optarg); 255 | if (disk_size == 0) { 256 | write_bpb = 0; 257 | } 258 | break; 259 | case 'l': 260 | disk_label = optarg; 261 | break; 262 | case 'B': 263 | breakpoints_ints_fptr = fopen(optarg, "w"); 264 | if (!breakpoints_ints_fptr) { 265 | perror("fopen breakpoint file"); 266 | return 1; 267 | } 268 | break; 269 | case 's': 270 | destination_segment = strtol(optarg, NULL, 0); 271 | initial_cs = initial_ds = initial_ss = destination_segment; 272 | break; 273 | } 274 | } 275 | 276 | if (argc - optind < 3) { 277 | fprintf(stderr, "Missing arguments\n"); 278 | return 1; 279 | } 280 | 281 | if (disk_size > 0) { 282 | if (getBPB_for_size(disk_size, &bpb)) { 283 | fprintf(stderr, "Invalid disk size\n"); 284 | return -1; 285 | } 286 | } else { 287 | memset(&bpb, 0, sizeof(bpb)); 288 | } 289 | 290 | bootstrap_file = argv[optind]; 291 | executable_file = argv[optind+1]; 292 | output_file = argv[optind+2]; 293 | 294 | bootstrap_size = bootstrap_load(bootstrap_file); 295 | if (bootstrap_size < 0) { 296 | return -1; 297 | } 298 | 299 | fptr_payload = fopen(executable_file, "rb"); 300 | if (!fptr_payload) { 301 | perror("could not open payload"); 302 | return -1; 303 | } 304 | 305 | read_uint16le(fptr_payload, exe_header); 306 | fseek(fptr_payload, 0, SEEK_SET); 307 | 308 | if (exe_header[SIG] != 0x5a4d) { 309 | is_exe = 0; 310 | } else { 311 | is_exe = 1; 312 | } 313 | 314 | printf("Bootstrap file: %s\n", bootstrap_file); 315 | printf("Bootstrap size: %d\n", bootstrap_size); 316 | printf("Payload file: %s\n", executable_file); 317 | printf("Payload file type: %s\n", is_exe ? ".EXE" : ".COM"); 318 | printf("Output file: %s\n", output_file); 319 | printf("Sectors per track: %d\n", sectors_per_track); 320 | printf("Output file padding: %d B / %.2f kB / %.2f MB)\n", bpb.disk_image_size, bpb.disk_image_size / 1024.0, bpb.disk_image_size / 1024.0 / 1024.0); 321 | 322 | if (is_exe) { 323 | int file_size; 324 | 325 | printf("Reading exe file...\n"); 326 | for (i=0; i<14; i++) { 327 | if (read_uint16le(fptr_payload, &exe_header[i])) { 328 | goto err; 329 | } 330 | } 331 | 332 | file_size = exe_header[FILE_PAGES]*512; 333 | if (exe_header[LAST_PAGE_SIZE]) { 334 | file_size -= 512 - exe_header[LAST_PAGE_SIZE]; 335 | } 336 | printf("File size: %d\n", file_size); 337 | 338 | payload_size = file_size - exe_header[HEADER_PARAGRAPHS] * 16; 339 | 340 | printf("Load module size: %d\n", payload_size); 341 | 342 | if (payload_size > 0x10000) { 343 | fprintf(stderr, "Payload too large"); 344 | goto err; 345 | } 346 | 347 | fseek(fptr_payload, exe_header[HEADER_PARAGRAPHS] * 16, SEEK_SET); 348 | if (1 != fread(payload, payload_size, 1, fptr_payload)) { 349 | perror("error reading exe loadmodule\n"); 350 | goto err; 351 | } 352 | 353 | initial_ip = exe_header[INITIAL_IP]; 354 | initial_sp = exe_header[INITIAL_SP]; 355 | 356 | printf("Relocating...\n"); 357 | initial_cs = exe_header[PRE_RELOCATE_CS] + destination_segment; 358 | initial_ss = exe_header[INITIAL_SS] + destination_segment; 359 | 360 | fseek(fptr_payload, exe_header[RELOCATION_TABLE_OFFSET], SEEK_SET); 361 | for (i=0; i= 0x10000) { 371 | fprintf(stderr, "Relocation out of bounds\n"); 372 | goto err; 373 | } 374 | val = payload[addr] + (payload[addr+1] << 8); 375 | printf("0x%04x -> 0x%04x\n", val, val + destination_segment); 376 | val += destination_segment; 377 | payload[addr] = val & 0xff; 378 | payload[addr+1] = val >> 8; 379 | } 380 | 381 | // bootsector.asm always points ES to 0x100 bytes before load image. 382 | // But DS will be equal to CS for .COM executables, but for .EXEs, it 383 | // must equals ES. 384 | initial_ds = destination_segment - 0x10; 385 | 386 | } else { 387 | printf("Reading com file...\n"); 388 | // .com origin is at 0x100 389 | // If there are command line options, we could place them 390 | // somewhere here like DOS does.... 391 | memset(payload, 0, 0x100); 392 | 393 | payload[0x80] = 1; // count of characters in command tail 394 | payload[0x81] = 0x0D; // all characters entered after the program name followed by a CR byte 395 | 396 | payload_size = fread(payload + 0x100, 1, 0x10001-0x100, fptr_payload); 397 | payload_size += 0x100; 398 | if (payload_size < 0) { 399 | perror("fread"); 400 | goto err; 401 | } 402 | if (payload_size > 0x10000) { 403 | fprintf(stderr, "Payload too large"); 404 | goto err; 405 | } 406 | } 407 | 408 | printf("Payload size: %d\n", payload_size); 409 | printf("Code : 0x%04x:0x%04x\n", initial_cs, initial_ip); 410 | printf("Stack : 0x%04x:0x%04x\n", initial_ss, initial_sp); 411 | 412 | 413 | for (i=0; i= 0x20 && intno <= 0x29) || intno == 0x2E || intno == 0x2F) { 418 | printf("Warning: Potential DOS %02xh interrupt call at 0x%04x\n", intno, i); 419 | hexdump(&payload[i>16 ? (i-16) :0], 24, i>16 ? (i-16):0); 420 | dos_interrupts = 1; 421 | 422 | if (breakpoints_ints_fptr) { 423 | fprintf(breakpoints_ints_fptr, "vbreak 0x%04x:0x%04x\n", destination_segment, i); 424 | } 425 | } 426 | } 427 | } 428 | 429 | /** Write the file */ 430 | fptr_disk = fopen(output_file, "wb"); 431 | if (!fptr_disk) { 432 | perror("fopen"); 433 | goto err; 434 | } 435 | 436 | printf("Writing %s...\n", output_file); 437 | // 1 : Number of sectors to copy 438 | if (payload_size < 512) { 439 | n_sectors = 1; 440 | } else { 441 | n_sectors = payload_size / 512 + 1; 442 | } 443 | printf("Bootstrap: %d sectors to copy, %d sectors per track\n\tinitial IP 0x%04x\n", n_sectors, sectors_per_track, initial_ip); 444 | printf("\tdestination segment 0x%04x\n", destination_segment); 445 | printf("\tinitial SP 0x%04x\n", initial_sp); 446 | printf("\tinitial CS 0x%04x\n", initial_cs); 447 | printf("\tinitial DS 0x%04x\n", initial_ds); 448 | printf("\tinitial SS 0x%04x\n", initial_ss); 449 | 450 | bootstrap_write(n_sectors, destination_segment, 451 | initial_ip, initial_sp, initial_cs, initial_ds, initial_ss, 452 | NULL, // no bios parameter block 453 | disk_label, 454 | fptr_disk); 455 | 456 | if (!write_bpb) 457 | { 458 | fwrite(payload, payload_size, 1, fptr_disk); 459 | } 460 | else 461 | { 462 | int root_dir_sectors = bpb.root_dir_entries * 32 / bpb.bytes_per_logical_sector; 463 | int track_size = bpb.sectors_per_track * bpb.num_heads; 464 | 465 | first_cluster_sector = bpb.reserved_logical_sectors + bpb.logical_sectors_per_fat * bpb.n_fats + root_dir_sectors; 466 | 467 | printf("First sector: 0x%04x\n", first_cluster_sector); 468 | printf("Sectors per track: 0x%04x\n", track_size); 469 | if (first_cluster_sector % track_size) { 470 | first_cluster_sector = (first_cluster_sector / track_size + 1) * track_size; 471 | printf("Target sector: 0x%04x\n", first_cluster_sector); 472 | } 473 | printf("Payload starting at track 0x%04x\n", first_cluster_sector / track_size); 474 | fseek(fptr_disk, first_cluster_sector * bpb.bytes_per_logical_sector, SEEK_SET); 475 | // fseek(fptr_disk, 1*512, SEEK_SET); 476 | 477 | ///// 478 | fseek(fptr_disk, first_cluster_sector * bpb.bytes_per_logical_sector, SEEK_SET); 479 | fwrite(payload, payload_size, 1, fptr_disk); 480 | ///// 481 | 482 | if (bpb.disk_image_size > 512+payload_size) { 483 | fseek(fptr_disk, bpb.disk_image_size-1, SEEK_SET); 484 | // Pad file size 485 | payload[0] = 0; 486 | fwrite(payload, 1, 1, fptr_disk); 487 | } 488 | 489 | int fat_size_bytes = bpb.bytes_per_logical_sector * bpb.logical_sectors_per_fat; 490 | uint8_t fat[fat_size_bytes]; 491 | int cluster; 492 | int n_reserved_clusters = (first_cluster_sector * bpb.bytes_per_logical_sector + payload_size) / bpb.bytes_per_logical_sector / bpb.sectors_per_cluster; 493 | int directory_offset; 494 | 495 | directory_offset = (bpb.reserved_logical_sectors + bpb.logical_sectors_per_fat * bpb.n_fats) * bpb.bytes_per_logical_sector; 496 | 497 | printf("Payload size: 0x%04x\n", payload_size); 498 | printf("Payload first sector: 0x%04x\n", first_cluster_sector); 499 | printf("Reserving %d clusters (0x%04x bytes)\n", 500 | n_reserved_clusters, 501 | n_reserved_clusters * bpb.sectors_per_cluster * bpb.bytes_per_logical_sector); 502 | 503 | memset(fat, 0, sizeof(fat)); 504 | fat[0] = bpb.media_descriptor; 505 | fat[1] = 0xFF; 506 | fat[2] = 0xFF; 507 | for (cluster=2; cluster> 4; 511 | fat[cluster/2*3+1] |= fat_entry << 4; 512 | } else { 513 | fat[cluster/2*3] = fat_entry; 514 | fat[cluster/2*3+1] = fat_entry>>8; 515 | } 516 | } 517 | 518 | dos_interrupts = 0; 519 | fseek(fptr_disk, 520 | bpb.bytes_per_logical_sector * bpb.reserved_logical_sectors, SEEK_SET); 521 | fwrite(fat, sizeof(fat), 1, fptr_disk); 522 | if (bpb.n_fats > 1) 523 | fwrite(fat, sizeof(fat), 1, fptr_disk); 524 | 525 | printf("Directory table offset: 0x%04x\n", directory_offset); 526 | fseek(fptr_disk, directory_offset, SEEK_SET); 527 | writeVolumeLabel(fptr_disk, disk_label, 0x08, 0, 0, 0, 0); 528 | } 529 | 530 | 531 | fclose(fptr_disk); 532 | retval = 0; 533 | err: 534 | fclose(fptr_payload); 535 | 536 | if (dos_interrupts) { 537 | printf("* * * * * * *\n"); 538 | printf("Warning: Byte sequences looking like DOS interrupt calls (eg: CD 21 int 21h ) were found!\n"); 539 | printf("\n"); 540 | printf("This may be a coincidence as code and data are undistinguishable without more analysis. If " 541 | "your software does not boot, you should disassemble your executable and investigate.\n" 542 | "See above for int number(s) and address(es).\n"); 543 | printf("\n"); 544 | printf("Note that int 21h (ah=09h, ah=25h and ah=35h) are supported by bootsector.asm.\n"); 545 | printf("* * * * * * *\n"); 546 | } 547 | 548 | if (breakpoints_ints_fptr) { 549 | fclose(breakpoints_ints_fptr); 550 | } 551 | 552 | return retval; 553 | } 554 | -------------------------------------------------------------------------------- /bootsector.asm: -------------------------------------------------------------------------------- 1 | org 7c00h 2 | bits 16 3 | cpu 8086 4 | [map symbols bootsector.map] 5 | 6 | %define INT21H_HELPERS 7 | 8 | %define DESTINATION_SEGMENT 1000H 9 | ;%define FIRST_SECTOR 2 10 | ;%define FIRST_TRACK 0 11 | 12 | ; Payload start must be at the first 13 | ; sector of track 1 14 | %define FIRST_SECTOR 1 15 | %define FIRST_TRACK 1 16 | 17 | section .text 18 | 19 | init: 20 | jmp short start 21 | nop 22 | banner: 23 | db 'MSWIN4.1' ; OEM identifier 24 | ;db 'raphnet ' ; OEM identifier 25 | bytes_per_sector: 26 | dw 512 ; Bytes per logical sector 27 | sectors_per_cluster: 28 | db 1 ; Logical sectors per cluster 29 | reserved_sectors: 30 | dw 129 ; Reserved logical sectors (Boot sector + our 64K payload) 31 | n_fats: 32 | db 1 ; Number of FATs 33 | root_directory_entries: 34 | dw 224 ; Root directory entries 35 | total_logical_sectors: 36 | dw 2880 ; Total logical sectors 37 | media_descriptor: 38 | db 0xF0 ; Media descriptor 39 | logical_sectors_per_fat: 40 | dw 9 ; Logical sectors per fat 41 | sectors_per_track: 42 | dw 9 43 | num_heads: 44 | dw 2 ; Number of heads 45 | num_hidden_sectors: 46 | dd 0 ; Hiden sectors 47 | large_number_of_sectors: 48 | dd 0 ; Large number of sectors 49 | drive_number: 50 | db 0 ; Drive number 51 | db 0 ; Winnt flags 52 | signature: db 0x29 ; Signature 53 | volume_id: db '0123' ; Volume ID/Serial 54 | volume_label: db ' ' ; Label 55 | fstype: db 'FAT12 ' ; System identifier string 56 | 57 | nop 58 | nop 59 | 60 | sectors_to_copy: dw 128 61 | destination_segment: dw DESTINATION_SEGMENT 62 | initial_ip: dw 0100h 63 | initial_sp: dw 0FFFEh 64 | initial_cs: dw DESTINATION_SEGMENT 65 | initial_ds: dw DESTINATION_SEGMENT 66 | initial_ss: dw DESTINATION_SEGMENT 67 | 68 | start: 69 | jmp 0000:start2 70 | start2: 71 | ; This boot code is at 0000:7c00 (512 bytes) 72 | ; 73 | ;cli 74 | 75 | ; Setup data segment to 0000 76 | mov ax, 0 77 | mov ds, ax 78 | 79 | ; Setup stack 80 | mov ss, ax 81 | mov sp, 7C00h 82 | 83 | %ifdef INT21H_HELPERS 84 | ; Install our handler at 0000:0084 85 | mov ax, int21 86 | mov word [21h * 4], ax 87 | mov ax, 0 88 | mov word [21h * 4 + 2], ax 89 | %endif 90 | 91 | ; Trace 92 | mov ah, 00Eh 93 | mov al, '1' 94 | mov bh, 0 95 | mov bl, 1 96 | mov cx, 1 97 | int 10h 98 | 99 | ; ES:BX is the destination 100 | mov ax, [destination_segment] 101 | mov es, ax 102 | mov bx, 0h 103 | 104 | ; Setup registers for the loop 105 | ; DL is never touched. Should still hold boot drive number 106 | mov ch, FIRST_TRACK 107 | mov dh, 0 ; Side 108 | mov cl, FIRST_SECTOR ; the first read start after the loader 109 | mov al, [sectors_per_track] 110 | sub al, FIRST_SECTOR-1 ; Loader sector skipped 111 | jmp _same_track 112 | 113 | copy_loop: 114 | call progress 115 | xor dh, 1 ; Toggle head 116 | jne _same_track 117 | inc ch ; Increment track when returning to head 0 118 | _same_track: 119 | push ax 120 | xor ah,ah 121 | mov al, [sectors_per_track] 122 | cmp word [sectors_to_copy], ax 123 | pop ax 124 | jge _complete_track ; al stays untouched 125 | mov al, [sectors_to_copy] 126 | _complete_track: 127 | mov ah, 02h 128 | push ax ; Keep AL around. Not all BIOSes preserve it. 129 | int 13h 130 | pop ax 131 | jc error 132 | 133 | push cx 134 | ; AL equals the number of sectors that were just read 135 | xor ah,ah 136 | sub word [sectors_to_copy], ax 137 | 138 | ; Multiply by 512 and increment BX 139 | mov cl, 9 140 | shl ax, cl 141 | add bx, ax 142 | pop cx 143 | 144 | ; Prepare values for the next phase. 145 | mov al, [sectors_per_track] 146 | mov cl, 1 ; all reads now start at sector 1 147 | 148 | test word [sectors_to_copy], 0xffff 149 | jnz copy_loop 150 | 151 | 152 | loading_done: 153 | mov ah, 00Eh 154 | mov al, '2' 155 | mov bh, 0 156 | mov bl, 1 157 | mov cx, 1 158 | int 10h 159 | 160 | start_payload: 161 | 162 | ; Setup stack 163 | mov ax, [initial_ss] 164 | mov ss, ax 165 | mov sp, [initial_sp] 166 | 167 | ; Put far return address on stack 168 | mov dx, [initial_cs] 169 | push dx 170 | mov dx, [initial_ip] 171 | push dx 172 | 173 | mov ax, [initial_cs] 174 | sub ax, 0x10 175 | mov es, ax 176 | xor ax,ax 177 | 178 | ; Data segment 179 | mov dx, [initial_ds] 180 | mov ds, dx 181 | 182 | 183 | final_jump: 184 | retf 185 | 186 | progress: 187 | push ax 188 | push bx 189 | mov ah, 0Eh 190 | mov al, '.' 191 | mov bh, 0 192 | mov bl, 1 193 | int 10h 194 | pop bx 195 | pop ax 196 | ret 197 | 198 | error: 199 | mov al, ah ; Return code from int13h 200 | add al, 'A' 201 | mov ah, 00Eh 202 | mov bh, 0 203 | mov bl, 1 204 | mov cx, 1 205 | int 10h 206 | 207 | hang: 208 | jmp hang 209 | 210 | %ifdef INT21H_HELPERS 211 | int21: 212 | cmp ah, 02h 213 | je int21_02 214 | cmp ah, 25h 215 | je int21_25 216 | cmp ah, 35h 217 | je int21_35 218 | cmp ah, 09h 219 | je int21_09 220 | iret 221 | 222 | ; AH : 02 223 | ; DL : Character 224 | int21_02: 225 | push ax 226 | push bx 227 | ; Int 10,E : write text in tty mode 228 | ; AL : Ascii character 229 | ; BH : Page no 230 | ; BL : Forground color 231 | xor bx,bx 232 | mov al, dl 233 | mov ah, 0x0e 234 | int 10h 235 | pop bx 236 | pop ax 237 | iret 238 | 239 | ; AH : 09 240 | ; DX : $tring 241 | int21_09: 242 | push ax 243 | push bx 244 | mov bx, dx 245 | mov ah, 0Eh 246 | _int21_09_lp: 247 | mov al, [bx] 248 | push bx 249 | mov bx, 0x0001 250 | int 10h 251 | pop bx 252 | 253 | inc bx 254 | cmp byte [bx], '$' 255 | jne _int21_09_lp 256 | pop bx 257 | pop ax 258 | iret 259 | 260 | ; AH: 25h 261 | ; AL: interrupt number 262 | ; DS:DX -> new interrupt handler 263 | int21_25: 264 | push ax 265 | push es 266 | push di 267 | 268 | xor ah,ah 269 | shl ax, 1 270 | shl ax, 1 271 | mov di, ax 272 | 273 | mov ax, 0 274 | mov es, ax 275 | 276 | cld 277 | mov ax, dx 278 | stosw ; offset 279 | mov ax, ds 280 | stosw ; segment 281 | 282 | pop di 283 | pop es 284 | pop ax 285 | iret 286 | 287 | ; AH : 35h 288 | ; AL : Interrupt number 289 | ; 290 | ; Return 291 | ; ES:BX -> current interrupt handler 292 | int21_35: 293 | push ax 294 | push ds 295 | push si 296 | 297 | xor ah,ah 298 | shl ax, 1 299 | shl ax, 1 300 | mov si, ax 301 | 302 | mov ax, 0 303 | mov ds, ax 304 | 305 | cld 306 | lodsw ; offset 307 | mov bx, ax 308 | lodsw ; segment 309 | mov es, ax 310 | 311 | pop si 312 | pop ds 313 | pop ax 314 | iret 315 | %endif 316 | 317 | db "Booterify version ",VERSION,0 318 | 319 | section .marker start=0x7dFE 320 | db 0x55, 0xaa 321 | 322 | section .data 323 | -------------------------------------------------------------------------------- /bootstrap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "bootstrap.h" 4 | 5 | // This must match bootsector.map 6 | #define SECTORS_TO_COPY 0x40 7 | #define DST_SEGMENT 0x42 8 | #define INITIAL_IP 0x44 9 | #define INITIAL_SP 0x46 10 | #define INITIAL_CS 0x48 11 | #define INITIAL_DS 0x4A 12 | #define INITIAL_SS 0x4C 13 | 14 | #define BYTES_PER_SECTOR 0x0B 15 | #define SECTORS_PER_CLUSTER 0x0D 16 | #define RESERVED_SECTORS 0x0E 17 | #define N_FATS 0x10 18 | #define MAX_ROOT_DIR_ENTRIES 0x11 19 | #define TOTAL_LOGICAL_SECTORS 0x13 20 | #define MEDIA_DESCRIPTOR 0x15 21 | #define LOGICAL_SECTORS_PER_FAT 0x16 22 | #define SECTORS_PER_TRACK 0x18 23 | #define NUM_HEADS 0x1A 24 | #define NUM_HIDDEN_SECTORS 0x1C 25 | 26 | static unsigned char bootstrap_buf[513]; 27 | 28 | #define SET_16LE(off, val) do { bootstrap_buf[off] = val; bootstrap_buf[off+1] = val >> 8; } while(0) 29 | #define SET_32LE(off, val) do { bootstrap_buf[off] = val; bootstrap_buf[off+1] = val >> 8; bootstrap_buf[off+2] = val >> 16; bootstrap_buf[off+3] = val >> 24; } while(0) 30 | 31 | int bootstrap_write(unsigned short num_sectors_to_copy, 32 | unsigned short dst_segment, 33 | unsigned short initial_ip, 34 | unsigned short initial_sp, 35 | unsigned short initial_cs, 36 | unsigned short initial_ds, 37 | unsigned short initial_ss, 38 | struct bootstrap_bpb *bpb, 39 | const char *label, 40 | FILE *out_fptr) 41 | { 42 | char label_buf[12]; 43 | int label_len; 44 | 45 | memset(label_buf, ' ', sizeof(label_buf)); 46 | label_len = strlen(label); 47 | if (label_len > 11) { 48 | fprintf(stderr, "Disk label '%s' is too long (max 11 chars.).\n", label); 49 | return -1; 50 | } 51 | 52 | SET_16LE(DST_SEGMENT, dst_segment); 53 | bootstrap_buf[SECTORS_TO_COPY] = num_sectors_to_copy & 0xff; 54 | bootstrap_buf[SECTORS_TO_COPY+1] = num_sectors_to_copy >> 8; 55 | 56 | if (bpb) { 57 | SET_16LE(BYTES_PER_SECTOR, bpb->bytes_per_logical_sector); 58 | bootstrap_buf[SECTORS_PER_CLUSTER] = bpb->sectors_per_cluster; 59 | SET_16LE(RESERVED_SECTORS, bpb->reserved_logical_sectors); 60 | bootstrap_buf[N_FATS] = bpb->n_fats; 61 | SET_16LE(MAX_ROOT_DIR_ENTRIES, bpb->root_dir_entries); 62 | SET_16LE(TOTAL_LOGICAL_SECTORS, bpb->total_logical_sectors); 63 | bootstrap_buf[MEDIA_DESCRIPTOR] = bpb->media_descriptor; 64 | SET_16LE(LOGICAL_SECTORS_PER_FAT, bpb->logical_sectors_per_fat); 65 | 66 | bootstrap_buf[SECTORS_PER_TRACK] = bpb->sectors_per_track; 67 | SET_16LE(NUM_HEADS, bpb->num_heads); 68 | SET_32LE(NUM_HIDDEN_SECTORS, 0); 69 | bootstrap_buf[0x26] = 0x29; // EBPB signature 70 | memcpy(bootstrap_buf + 0x2b, label_buf, sizeof(label_buf)); 71 | memcpy(bootstrap_buf + 0x36, "FAT12 ", 8); 72 | } 73 | 74 | bootstrap_buf[INITIAL_IP] = initial_ip & 0xff; 75 | bootstrap_buf[INITIAL_IP+1] = initial_ip >> 8; 76 | 77 | bootstrap_buf[INITIAL_SP] = initial_sp & 0xff; 78 | bootstrap_buf[INITIAL_SP+1] = initial_sp >> 8; 79 | bootstrap_buf[INITIAL_CS] = initial_cs & 0xff; 80 | bootstrap_buf[INITIAL_CS+1] = initial_cs >> 8; 81 | 82 | bootstrap_buf[INITIAL_DS] = initial_ds & 0xff; 83 | bootstrap_buf[INITIAL_DS+1] = initial_ds >> 8; 84 | 85 | bootstrap_buf[INITIAL_SS] = initial_ss & 0xff; 86 | bootstrap_buf[INITIAL_SS+1] = initial_ss >> 8; 87 | 88 | return fwrite(bootstrap_buf, 512, 1, out_fptr); 89 | } 90 | 91 | int bootstrap_load(const char *filename) 92 | { 93 | FILE *fptr_bootstrap; 94 | int bootstrap_size; 95 | 96 | fptr_bootstrap = fopen(filename, "rb"); 97 | if (!fptr_bootstrap) { 98 | perror("could not open bootstrap"); 99 | return -1; 100 | } 101 | 102 | memset(bootstrap_buf, 0, sizeof(bootstrap_buf)); 103 | bootstrap_size = fread(bootstrap_buf, 1, 513, fptr_bootstrap); 104 | if (bootstrap_size < 0) { 105 | perror("fread"); 106 | fclose(fptr_bootstrap); 107 | return -1; 108 | } 109 | if (bootstrap_size > 512) { 110 | fprintf(stderr, "Bootstrap too large"); 111 | fclose(fptr_bootstrap); 112 | return -1; 113 | } 114 | 115 | fclose(fptr_bootstrap); 116 | 117 | return bootstrap_size; 118 | } 119 | 120 | -------------------------------------------------------------------------------- /bootstrap.h: -------------------------------------------------------------------------------- 1 | #ifndef _bootstrap_h__ 2 | #define _bootstrap_h__ 3 | 4 | #include 5 | 6 | struct bootstrap_bpb { 7 | /* DOS 2.0 BPB */ 8 | uint16_t bytes_per_logical_sector; 9 | uint8_t sectors_per_cluster; 10 | uint16_t reserved_logical_sectors; 11 | uint8_t n_fats; 12 | uint16_t root_dir_entries; 13 | uint16_t total_logical_sectors; 14 | uint8_t media_descriptor; 15 | uint16_t logical_sectors_per_fat; 16 | 17 | /* DOS 3.0 BPB */ 18 | uint16_t sectors_per_track; 19 | uint8_t num_heads; 20 | 21 | // Internal stuff 22 | uint32_t disk_image_size; 23 | }; 24 | 25 | int bootstrap_load(const char *filename); 26 | int bootstrap_write(unsigned short num_sectors_to_copy, 27 | unsigned short dst_segment, 28 | unsigned short initial_ip, 29 | unsigned short initial_sp, 30 | unsigned short initial_cs, 31 | unsigned short initial_ds, 32 | unsigned short initial_ss, 33 | struct bootstrap_bpb *bpb, 34 | const char *label, 35 | FILE *out_fptr); 36 | 37 | #endif // _bootstrap_h__ 38 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Version 1.6 2 | - Add a loader for PCjr cartridge (pcjrloader.asm) 3 | - Add the jrromchk tool, to convert between .JRC and binary PCjr ROM formats 4 | 5 | Version 1.5 6 | - Bios parameter block and output file padding can now be disabled using -f 0 7 | 8 | Version 1.4 9 | - Initialize the command-line fields of the PSP correctly 10 | for .COM executables. (Use length of 1 and add a 0D terminator 11 | instead of zero'ing everything) 12 | - Implement int 21h,02 (DOS putchar) 13 | - Add a version string in the loader and in the tool 14 | - Add a makefile for cross-compiling the windows version with MXE 15 | 16 | Version 1.3 17 | - Disk images now have a standard FAT12 filesystem so 18 | besides being bootable, floppies may also contain extra 19 | data (.EXE version of the game, documentation, etc...) 20 | 21 | Version 1.2 22 | - Add a hexdump when a potential DOS interrupt call is found 23 | - Fix incorrect use of load address in some cases 24 | 25 | Version 1.1 26 | - Add a bios parameter block (To make bootable USB sticks) 27 | - Add int 21h,09 (DOS print string) 28 | - Larger bootloader stack 29 | 30 | Version 1.0 31 | - Initial release 32 | -------------------------------------------------------------------------------- /exeinfo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define SIG 0 6 | #define LAST_PAGE_SIZE 1 7 | #define FILE_PAGES 2 8 | #define RELOCATION_ITEMS 3 9 | #define HEADER_PARAGRAPHS 4 10 | #define MINALLOC 5 11 | #define MAXALLOC 6 12 | #define INITIAL_SS 7 13 | #define INITIAL_SP 8 14 | #define CHECKSUM 9 15 | #define INITIAL_IP 10 16 | #define PRE_RELOCATE_CS 11 17 | #define RELOCATION_TABLE_OFFSET 12 18 | #define OVERLAY_NUMBER 13 19 | 20 | #define PRINT_HEADER_ITEM(a) printf(#a": 0x%04x\n", header[a]) 21 | 22 | 23 | int read_uint16le(FILE *fptr, uint16_t *dst) 24 | { 25 | unsigned char buf[2]; 26 | 27 | if (1 != fread(buf, 2, 1, fptr)) { 28 | perror("fread"); 29 | return -1; 30 | } 31 | 32 | *dst = buf[0] | (buf[1]<<8); 33 | 34 | return 0; 35 | } 36 | 37 | int main(int argc, char **argv) 38 | { 39 | FILE *fptr, *out_fptr = NULL; 40 | int retval = 0; 41 | int i; 42 | uint16_t header[14]; 43 | int file_size; 44 | int load_module_size; 45 | const char *loadmodule_outfn = NULL; 46 | 47 | if (argc<2) { 48 | printf("Usage: ./exeinfo file.exe [loadmodule.bin]\n"); 49 | printf("\n"); 50 | printf("The 3rd argument is optional. Used to extract the load module from the .exe\n"); 51 | return 1; 52 | } 53 | 54 | if (argc >= 3) { 55 | loadmodule_outfn = argv[2]; 56 | printf("Loadmodule output file: %s\n", loadmodule_outfn); 57 | } 58 | 59 | fptr = fopen(argv[1], "rb"); 60 | if (!fptr) { 61 | perror("fopen"); 62 | return 2; 63 | } 64 | 65 | for (i=0; i<14; i++) { 66 | if (read_uint16le(fptr, &header[i])) { 67 | retval = -1; 68 | goto err; 69 | } 70 | } 71 | 72 | if (header[SIG] != 0x5a4d) { 73 | fprintf(stderr, "Not an MZ executable\n"); 74 | retval = -1; 75 | goto err; 76 | } 77 | 78 | PRINT_HEADER_ITEM(SIG); 79 | PRINT_HEADER_ITEM(LAST_PAGE_SIZE); 80 | PRINT_HEADER_ITEM(FILE_PAGES); 81 | PRINT_HEADER_ITEM(RELOCATION_ITEMS); 82 | PRINT_HEADER_ITEM(HEADER_PARAGRAPHS); 83 | PRINT_HEADER_ITEM(MINALLOC); 84 | PRINT_HEADER_ITEM(MAXALLOC); 85 | PRINT_HEADER_ITEM(INITIAL_SS); 86 | PRINT_HEADER_ITEM(INITIAL_SP); 87 | PRINT_HEADER_ITEM(CHECKSUM); 88 | PRINT_HEADER_ITEM(INITIAL_IP); 89 | PRINT_HEADER_ITEM(PRE_RELOCATE_CS); 90 | PRINT_HEADER_ITEM(RELOCATION_TABLE_OFFSET); 91 | PRINT_HEADER_ITEM(OVERLAY_NUMBER); 92 | 93 | file_size = header[FILE_PAGES]*512; 94 | if (header[LAST_PAGE_SIZE]) { 95 | file_size -= 512 - header[LAST_PAGE_SIZE]; 96 | } 97 | printf("File size: %d\n", file_size); 98 | 99 | load_module_size = file_size - header[HEADER_PARAGRAPHS] * 16; 100 | 101 | printf("Load module size: %d\n", load_module_size); 102 | 103 | fseek(fptr, header[RELOCATION_TABLE_OFFSET], SEEK_SET); 104 | for (i=0; i0) { 113 | unsigned char *lm; 114 | int chunk_size, todo = load_module_size; 115 | 116 | out_fptr = fopen(loadmodule_outfn, "wb"); 117 | if (!out_fptr) { 118 | perror("fopen"); 119 | retval = -1; 120 | goto err; 121 | } 122 | 123 | lm = malloc(2048); 124 | if (!lm) { 125 | perror("malloc"); 126 | retval = -1; 127 | goto err; 128 | } 129 | fseek(fptr, header[HEADER_PARAGRAPHS] * 16, SEEK_SET); 130 | 131 | do { 132 | chunk_size = todo > 2048 ? 2048 : todo; 133 | fread(lm, chunk_size, 1, fptr); 134 | fwrite(lm, chunk_size, 1, out_fptr); 135 | printf("Chunk %d\n", chunk_size); 136 | todo -= chunk_size; 137 | } while (todo); 138 | 139 | free(lm); 140 | } 141 | 142 | err: 143 | if (out_fptr) { 144 | free(out_fptr); 145 | } 146 | fclose(fptr); 147 | 148 | return retval; 149 | } 150 | -------------------------------------------------------------------------------- /generatetests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | SRCDIR=testing/executables 4 | LOGDIR=testing/logs 5 | OUTDIR=testing/disks 6 | OPTS="-f 360" 7 | 8 | BOOTERIFY=./booterify 9 | 10 | if [ ! -x $BOOTERIFY ]; then 11 | echo "Booterify not found. You should compile it first." 12 | exit 1 13 | fi 14 | 15 | for i in `ls $SRCDIR/*.EXE $SRCDIR/*.exe $SRCDIR/*.COM $SRCDIR/*.com`; do 16 | NM=`basename $i` 17 | 18 | echo -n Processing $NM... 19 | $BOOTERIFY bootsector.bin $i $OUTDIR/$NM.DSK $OPTS -B $LOGDIR/$NM.BRK > $LOGDIR/$NM.LOG 20 | if [ $? -ne 0 ]; then 21 | echo ERROR 22 | continue 23 | fi 24 | 25 | # There will be breakpoints if there were potential ints 26 | if [ -s $LOGDIR/$NM.BRK ]; then 27 | echo -n " Warning: Potential DOS int calls found: " 28 | wc -l $LOGDIR/$NM.BRK | cut -f 1 -d ' ' 29 | else 30 | echo "OK" 31 | fi 32 | done 33 | -------------------------------------------------------------------------------- /jrromchk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define JRC_SIG "PCjr Cartridge image file\r\n" 8 | #define JRC_CREATOR "Booterify " VERSION 9 | 10 | struct jrc_header { 11 | uint8_t signature[27+1]; 12 | char creator[30+1]; 13 | char comment[400+1]; 14 | uint8_t version[2]; 15 | uint16_t address; 16 | uint16_t adrmask; 17 | uint8_t reserved[46]; 18 | }; 19 | 20 | struct pcjr_rom { 21 | uint8_t *data; 22 | int offset; 23 | int romsize; 24 | int crc_ok; 25 | uint16_t crc; 26 | int has_jrc_header; 27 | struct jrc_header jrc; 28 | }; 29 | 30 | void loadJrcHeader(uint8_t *data, struct jrc_header *dst) 31 | { 32 | memset(dst, 0, sizeof(struct jrc_header)); 33 | memcpy(dst->signature, data, sizeof(dst->signature)-1); 34 | memcpy(dst->creator, data+27, sizeof(dst->creator)-1); 35 | memcpy(dst->comment, data+59, sizeof(dst->comment)-1); 36 | dst->version[0] = data[460]; 37 | dst->version[1] = data[461]; 38 | dst->address = data[462] | (data[463] << 8); 39 | dst->adrmask = data[464] | (data[465] << 8); 40 | } 41 | 42 | void writeJRCheader(FILE *fptr, const struct jrc_header *jrc) 43 | { 44 | uint8_t tmp[401]; 45 | 46 | // Signature 47 | fprintf(fptr, JRC_SIG); 48 | 49 | // Creator + CR 50 | fprintf(fptr, "%-30s\r\n", JRC_CREATOR); 51 | 52 | // Comment (terminated by 0x1A) 53 | memset(tmp, 0x1A, sizeof(tmp)); 54 | memcpy(tmp, jrc->comment, strlen(jrc->comment)); 55 | fwrite(tmp, 400, 1, fptr); 56 | 57 | // EOF 58 | fprintf(fptr, "%c", 0x1A); 59 | 60 | // Version 61 | fwrite(jrc->version, 2, 1, fptr); 62 | 63 | // Address 64 | tmp[0] = jrc->address; 65 | tmp[1] = jrc->address >> 8; 66 | fwrite(tmp, 2, 1, fptr); 67 | 68 | // Address mask 69 | tmp[0] = jrc->adrmask; 70 | tmp[1] = jrc->adrmask >> 8; 71 | fwrite(tmp, 2, 1, fptr); 72 | 73 | // Reserved 74 | memset(tmp, 0x00, 46); 75 | fwrite(tmp, 46, 1, fptr); 76 | 77 | } 78 | 79 | void buildJRCHeader(struct jrc_header *jrc, uint16_t address, uint16_t mask) 80 | { 81 | memset(jrc, 0, sizeof(struct jrc_header)); 82 | jrc->version[0] = 1; 83 | jrc->version[1] = 0; 84 | jrc->address = address; 85 | jrc->adrmask = mask; 86 | } 87 | 88 | void printJrcHeader(const struct jrc_header *jrc) 89 | { 90 | printf("Creator.........: %s\n", jrc->creator); 91 | printf("Comment.........: %s\n", jrc->comment); 92 | printf("Image version...: %d.%d\n", jrc->version[0], jrc->version[1]); 93 | printf("Segment address.: 0x%04x\n", jrc->address); 94 | printf("Address mask....: 0x%04x\n", jrc->adrmask); 95 | } 96 | 97 | uint16_t crc_xmodem_update(uint16_t crc, uint8_t data) 98 | { 99 | int i; 100 | 101 | crc = crc ^ ((uint16_t)data << 8); 102 | for (i=0; i<8; i++) 103 | { 104 | if (crc & 0x8000) 105 | crc = (crc << 1) ^ 0x1021; 106 | else 107 | crc <<= 1; 108 | } 109 | 110 | return crc; 111 | } 112 | 113 | uint16_t crc_compute(const unsigned char *data, int len) 114 | { 115 | uint16_t crc = 0xFFFF; 116 | int i; 117 | 118 | for (i=0; ihas_jrc_header) { 157 | buildJRCHeader(&rom->jrc, 0xE000, 0x0000); 158 | } 159 | 160 | writeJRCheader(fptr, &rom->jrc); 161 | } 162 | 163 | 164 | if (fwrite(rom->data + rom->offset, rom->romsize, 1, fptr) != 1) { 165 | perror("Error writing ROM data\n"); 166 | fclose(fptr); 167 | return -1; 168 | } 169 | 170 | fclose(fptr); 171 | 172 | return 0; 173 | } 174 | 175 | /** 176 | * \param from_data The source data (eg: read from a file) to load the ROM from. 177 | * \param size The size of the source data 178 | * \param dst The destination structure 179 | * \param verbose Verbose output if non-zero 180 | */ 181 | int loadRom(unsigned char *from_data, int size,struct pcjr_rom *dst, int verbose, int fix_size) 182 | { 183 | uint8_t magic[2] = { 0x55, 0xAA }; 184 | 185 | memset(dst, 0, sizeof(struct pcjr_rom)); 186 | 187 | dst->data = from_data; 188 | 189 | // Check for presence of a JRC file header 190 | if (size >= 512) { 191 | if (0 == memcmp(JRC_SIG, from_data, strlen(JRC_SIG))) { 192 | loadJrcHeader(from_data, &dst->jrc); 193 | 194 | if (verbose) { 195 | printf("Detected a JRC header\n"); 196 | printJrcHeader(&dst->jrc); 197 | } 198 | 199 | dst->offset = 512; // skip the header 200 | } 201 | } 202 | 203 | // 204 | // [0-1] : 55AA 205 | // [2] : Length (length / 512) 206 | // code! 207 | // 208 | 209 | if (size < (dst->offset + 3)) { 210 | fprintf(stderr, "ROM too small to be valid\n"); 211 | return -1; 212 | } 213 | 214 | if (memcmp(&from_data[dst->offset], magic, sizeof(magic))) { 215 | fprintf(stderr, "ROM does not start with 55AA\n"); 216 | return -1; 217 | } 218 | 219 | if (fix_size) { 220 | dst->romsize = (size-dst->offset); 221 | from_data[dst->offset + 2] = dst->romsize / 512; 222 | if (verbose) { 223 | printf("Patched size byte to %d\n", from_data[dst->offset + 2]); 224 | } 225 | } 226 | else { 227 | dst->romsize = from_data[dst->offset + 2] * 512; 228 | if (dst->romsize < 1) { 229 | fprintf(stderr, "Error: A rom size of 0 does not make sense\n"); 230 | return -1; 231 | } 232 | } 233 | 234 | if (verbose) { 235 | printf("File size: %d\n", size); 236 | printf("Offset: %d\n", dst->offset); 237 | printf("ROM size: %d\n", dst->romsize); 238 | } 239 | 240 | // The file could be larger if, for instance, a 16K game was 241 | // burned to a 32K eeprom. But the file may not be smaller. 242 | if (dst->romsize > (size - dst->offset)) { 243 | fprintf(stderr, "Error: ROM declares a size larger than the file\n"); 244 | return -1; 245 | } 246 | 247 | dst->crc = crc_compute(from_data + dst->offset, dst->romsize); 248 | 249 | if (dst->crc == 0) { 250 | dst->crc_ok = 1; 251 | } 252 | 253 | return 0; 254 | } 255 | 256 | void setRomCRC(struct pcjr_rom *rom, uint16_t crc) 257 | { 258 | rom->data[rom->offset + rom->romsize - 2] = crc >> 8; 259 | rom->data[rom->offset + rom->romsize - 1] = crc; 260 | } 261 | 262 | void printHelp(void) 263 | { 264 | printf("jrromchk, part of Booterify version %s\n\n", VERSION); 265 | printf("Usage: ./jrromchk [options] inputFile\n"); 266 | printf("\n"); 267 | printf(" -h Print usage information and exit\n"); 268 | printf(" -v Enable verbose output\n"); 269 | printf(" -p Compute CRC and patch the file\n"); 270 | printf(" -s Fix the size byte based on input file size\n"); 271 | printf(" -i Display information about the file (default)\n"); 272 | printf(" -o file Write (modified) ROM to file (default: Binary format)\n"); 273 | printf(" -j Enable JRC format output (use with -o)\n"); 274 | } 275 | 276 | int main(int argc, char **argv) 277 | { 278 | FILE *fptr; 279 | unsigned char *data; 280 | long size; 281 | int alloc_size; 282 | int res; 283 | int opt; 284 | int patch_crc = 0; 285 | int verbose = 0; 286 | int fix_size = 0; 287 | int jrc_output = 0; 288 | struct pcjr_rom rom; 289 | const char *outfilename = NULL; 290 | 291 | 292 | while ((opt = getopt(argc, argv, "vipho:sj")) != -1) { 293 | switch(opt) 294 | { 295 | case 'j': 296 | jrc_output = 1; 297 | break; 298 | case 'o': 299 | outfilename = optarg; 300 | break; 301 | case 's': 302 | fix_size = 1; 303 | break; 304 | case 'v': 305 | verbose =1; 306 | break; 307 | case 'h': 308 | printHelp(); 309 | return 0; 310 | case 'i': 311 | break; 312 | case 'p': 313 | patch_crc = 1; 314 | break; 315 | default: 316 | fprintf(stderr, "Unknown option. Try -h\n"); 317 | return 1; 318 | } 319 | } 320 | 321 | if (optind >= argc) { 322 | printf("No file specified\n"); 323 | return 1; 324 | } 325 | 326 | fptr = fopen(argv[optind], "rb"); 327 | if (!fptr) { 328 | perror(argv[1]); 329 | return -1; 330 | } 331 | 332 | fseek(fptr, 0, SEEK_END); 333 | size = ftell(fptr); 334 | alloc_size = size; 335 | fseek(fptr, 0, SEEK_SET); 336 | 337 | // Round up to 512 bytes 338 | if (fix_size) { 339 | alloc_size = (size + 511) / 512 * 512; 340 | 341 | if (verbose) { 342 | printf("Input file size is %d\n", (int)size); 343 | printf("Rounded up to %d\n", (int)alloc_size); 344 | } 345 | } 346 | 347 | data = malloc(alloc_size); 348 | if (!data) { 349 | perror("could not allocate memory for file"); 350 | fclose(fptr); 351 | return -1; 352 | } 353 | 354 | memset(data, 0xff, alloc_size); 355 | 356 | if (1 != fread(data, size, 1, fptr)) { 357 | perror("could not read file"); 358 | free(data); 359 | fclose(fptr); 360 | return -1; 361 | } 362 | 363 | res = loadRom(data, alloc_size, &rom, verbose, fix_size); 364 | if (res) { 365 | printf("ROM INVALID\n"); 366 | fclose(fptr); 367 | return -1; 368 | } 369 | 370 | if (rom.crc_ok) { 371 | printf("ROM OK\n"); 372 | res = 0; 373 | } else { 374 | 375 | // CRC invalid. But do we correct it? 376 | if (patch_crc) { 377 | uint16_t crc = crc_compute(rom.data + rom.offset, rom.romsize-2); 378 | 379 | setRomCRC(&rom, crc); 380 | 381 | res = loadRom(data, alloc_size, &rom, verbose, 0); 382 | if (res || !rom.crc_ok) { 383 | printf("ROM INVALID - Failed to fix it? (bug?)\n"); 384 | res = -1; 385 | } else { 386 | printf("ROM OK (CRC PATCHED)\n"); 387 | res = 0; 388 | } 389 | } else { 390 | printf("ROM INVALID (BAD CRC)\n"); 391 | res = -1; 392 | } 393 | } 394 | 395 | // at this point, if res is 0, we have a perfectly valid ROM to 396 | // write to the output file, if specified. 397 | if ((res == 0) && outfilename) { 398 | res = writeROM(outfilename, &rom, jrc_output); 399 | if (res == 0) { 400 | printf("Wrote %s\n", outfilename); 401 | } else { 402 | fprintf(stderr, "Could not write ROM\n"); 403 | } 404 | } 405 | 406 | fclose(fptr); 407 | 408 | return res; 409 | } 410 | -------------------------------------------------------------------------------- /makewin32archive.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function errorExit 4 | { 5 | echo $1 6 | exit 1 7 | } 8 | 9 | source version.inc 10 | 11 | make -f Makefile.mxe clean 12 | make -f Makefile.mxe || errorExit "Compilation error" 13 | 14 | FILENAME=booterify_win32-$VERSION.zip 15 | 16 | [ ! -f $FILENAME ] || errorExit "$FILENAME already exists!" 17 | 18 | echo "Preparing $FILENAME" 19 | zip -r $FILENAME *.exe *.c *.h Makefile* README.md bootsector.* pcjrloader.* version.inc bochs-config changelog.txt || errorExit "Error building archive. Missing file?" 20 | 21 | echo "Done!" 22 | ls -lh $FILENAME 23 | -------------------------------------------------------------------------------- /pcjrloader.asm: -------------------------------------------------------------------------------- 1 | org 0x0000 2 | bits 16 3 | cpu 8086 4 | [map symbols pcjrloader.map] 5 | 6 | %define INT21H_HELPERS 7 | 8 | ; If enabled, the cartridge code only runs if no bootable 9 | ; disk is found. (Like the BASIC cartridge) 10 | ;%define INT18_MODE 11 | 12 | ; Start after the BIOS Data Area 13 | ; use one paragraph for the stack (SS=0050) 14 | 15 | %define DESTINATION_SEGMENT 0052H 16 | 17 | ; Payload start must be at the first 18 | ; sector of track 1 19 | %define FIRST_SECTOR 1 20 | %define FIRST_TRACK 1 21 | 22 | section .text 23 | 24 | db 0x55, 0xAA ; Marker 25 | db 0 ; Size / 512. Will be patched by jrromchk 26 | jmp init ; Code follows 27 | 28 | times 58 db 0 ; Todo : Align to match bootsector.asm (40h) 29 | 30 | sectors_to_copy: dw 128 31 | destination_segment: dw DESTINATION_SEGMENT 32 | initial_ip: dw 0100h 33 | initial_sp: dw 0FFFEh 34 | initial_cs: dw DESTINATION_SEGMENT 35 | initial_ds: dw DESTINATION_SEGMENT 36 | initial_ss: dw DESTINATION_SEGMENT 37 | 38 | 39 | init: 40 | push ax 41 | push ds 42 | 43 | %ifdef INT18_MODE 44 | ; Install the start routine at INT 18h. The cartridge code 45 | ; will run if there is no bootable diskette. (Like the BASIC cartridge does) 46 | xor ax, ax 47 | mov ds, ax 48 | mov ax, start 49 | mov word [18h * 4], ax 50 | mov ax, cs 51 | mov word [18h * 4 + 2], ax 52 | %else 53 | ; First enable the interrupt timer. Games that use the timer 54 | ; may need it! Normally it's done at F000:08B7 after the floppy test. 55 | in al, 21h 56 | and al, 0xFE 57 | out 21h, al 58 | pop ds 59 | pop ax 60 | jmp start 61 | %endif 62 | 63 | pop ds 64 | pop ax 65 | retf 66 | 67 | start: 68 | ; Setup data segment to 0000 69 | mov ax, cs 70 | mov ds, ax 71 | 72 | %ifdef TRACE 73 | ; Setup stack 74 | mov ax, [destination_segment] 75 | dec ax 76 | mov ss, ax 77 | mov sp, 15 78 | %endif 79 | 80 | %ifdef INT21H_HELPERS 81 | ; Install our handler at 0000:0084 82 | push ds 83 | xor ax, ax 84 | mov ds, ax 85 | mov ax, int21 86 | mov word [21h * 4], ax 87 | mov ax, cs 88 | mov word [21h * 4 + 2], ax 89 | pop ds 90 | %endif 91 | 92 | %ifdef TRACE 93 | ; Trace 94 | mov ah, 00Eh 95 | mov al, '1' 96 | mov bh, 0 97 | mov bl, 1 98 | mov cx, 1 99 | int 10h 100 | %endif 101 | 102 | ; ES:DI is the destination 103 | mov ax, [destination_segment] 104 | mov es, ax 105 | xor di, di 106 | 107 | mov cx, [sectors_to_copy] 108 | 109 | ; DS:SI is the source (CS + 512 to skip this loader) 110 | mov ax, cs 111 | add ax, 0x20 112 | mov ds, ax 113 | xor si, si 114 | 115 | ; multiply by 512 (sector size) 116 | shl cx, 1 117 | shl cx, 1 118 | shl cx, 1 119 | shl cx, 1 120 | shl cx, 1 121 | shl cx, 1 122 | shl cx, 1 123 | shl cx, 1 124 | shl cx, 1 125 | 126 | rep movsb 127 | 128 | ; Restore data segment 129 | mov ax, cs 130 | mov ds, ax 131 | 132 | 133 | loading_done: 134 | %ifdef TRACE 135 | mov ah, 00Eh 136 | mov al, '2' 137 | mov bh, 0 138 | mov bl, 1 139 | mov cx, 1 140 | int 10h 141 | %endif 142 | 143 | start_payload: 144 | 145 | ; Setup stack 146 | mov ax, [initial_ss] 147 | mov ss, ax 148 | mov sp, [initial_sp] 149 | 150 | ; Put far return address on stack 151 | mov dx, [initial_cs] 152 | push dx 153 | mov dx, [initial_ip] 154 | push dx 155 | 156 | mov ax, [initial_cs] 157 | sub ax, 0x10 158 | mov es, ax 159 | xor ax,ax 160 | 161 | ; Data segment 162 | mov dx, [initial_ds] 163 | mov ds, dx 164 | 165 | sti 166 | final_jump: 167 | retf 168 | 169 | progress: 170 | push ax 171 | push bx 172 | mov ah, 0Eh 173 | mov al, '.' 174 | mov bh, 0 175 | mov bl, 1 176 | int 10h 177 | pop bx 178 | pop ax 179 | ret 180 | 181 | error: 182 | mov al, ah ; Return code from int13h 183 | add al, 'A' 184 | mov ah, 00Eh 185 | mov bh, 0 186 | mov bl, 1 187 | mov cx, 1 188 | int 10h 189 | 190 | hang: 191 | jmp hang 192 | 193 | %ifdef INT21H_HELPERS 194 | int21: 195 | cmp ah, 02h 196 | je int21_02 197 | cmp ah, 25h 198 | je int21_25 199 | cmp ah, 35h 200 | je int21_35 201 | cmp ah, 09h 202 | je int21_09 203 | iret 204 | 205 | ; AH : 02 206 | ; DL : Character 207 | int21_02: 208 | push ax 209 | push bx 210 | ; Int 10,E : write text in tty mode 211 | ; AL : Ascii character 212 | ; BH : Page no 213 | ; BL : Forground color 214 | xor bx,bx 215 | mov al, dl 216 | mov ah, 0x0e 217 | int 10h 218 | pop bx 219 | pop ax 220 | iret 221 | 222 | ; AH : 09 223 | ; DX : $tring 224 | int21_09: 225 | push ax 226 | push bx 227 | mov bx, dx 228 | mov ah, 0Eh 229 | _int21_09_lp: 230 | mov al, [bx] 231 | push bx 232 | mov bx, 0x0001 233 | int 10h 234 | pop bx 235 | 236 | inc bx 237 | cmp byte [bx], '$' 238 | jne _int21_09_lp 239 | pop bx 240 | pop ax 241 | iret 242 | 243 | ; AH: 25h 244 | ; AL: interrupt number 245 | ; DS:DX -> new interrupt handler 246 | int21_25: 247 | push ax 248 | push es 249 | push di 250 | 251 | xor ah,ah 252 | shl ax, 1 253 | shl ax, 1 254 | mov di, ax 255 | 256 | mov ax, 0 257 | mov es, ax 258 | 259 | cld 260 | mov ax, dx 261 | stosw ; offset 262 | mov ax, ds 263 | stosw ; segment 264 | 265 | pop di 266 | pop es 267 | pop ax 268 | iret 269 | 270 | ; AH : 35h 271 | ; AL : Interrupt number 272 | ; 273 | ; Return 274 | ; ES:BX -> current interrupt handler 275 | int21_35: 276 | push ax 277 | push ds 278 | push si 279 | 280 | xor ah,ah 281 | shl ax, 1 282 | shl ax, 1 283 | mov si, ax 284 | 285 | mov ax, 0 286 | mov ds, ax 287 | 288 | cld 289 | lodsw ; offset 290 | mov bx, ax 291 | lodsw ; segment 292 | mov es, ax 293 | 294 | pop si 295 | pop ds 296 | pop ax 297 | iret 298 | %endif 299 | 300 | db "Booterify version ",VERSION,0 301 | 302 | 303 | ; Make sure exactly 512 bytes are occupied 304 | section .marker start=511 305 | db 0xff 306 | 307 | 308 | section .data 309 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PREFIX=booterify 4 | HEXFILE=$PREFIX.hex 5 | 6 | echo "Release script for $PREFIX" 7 | 8 | if [ $# -ne 2 ]; then 9 | echo "Syntax: ./release.sh version releasedir" 10 | echo 11 | echo "ex: './release 1.0' will produce $PREFIX-1.0.tar.gz in releasedir out of git HEAD." 12 | exit 13 | fi 14 | 15 | VERSION=$1 16 | RELEASEDIR=$2 17 | DIRNAME=$PREFIX-$VERSION 18 | FILENAME=$PREFIX-$VERSION.tar.gz 19 | TAG=v$VERSION 20 | 21 | echo "Version: $VERSION" 22 | echo "Filename: $FILENAME" 23 | echo "Release directory: $RELEASEDIR" 24 | echo "--------" 25 | echo "Ready? Press ENTER to go ahead (or CTRL+C to cancel)" 26 | 27 | read 28 | 29 | if [ -f $RELEASEDIR/$FILENAME ]; then 30 | echo "Release file already exists!" 31 | exit 1 32 | fi 33 | 34 | git tag $TAG -f -a 35 | git archive --format=tar --prefix=$DIRNAME/ HEAD | gzip > $RELEASEDIR/$FILENAME 36 | -------------------------------------------------------------------------------- /testing/README: -------------------------------------------------------------------------------- 1 | 1) Drop your .EXE and .COM files in executables/ 2 | 2) From the project root, run generatetests.sh 3 | 3) Run the disks images found in disks/ in bochs (or any appropriate emulator or hardware) 4 | 5 | Optional 6 | 7 | 4) If it fails, have a look to the logs in logs/ to find out why. 8 | -------------------------------------------------------------------------------- /testing/disks/.gitignore: -------------------------------------------------------------------------------- 1 | *.DSK 2 | -------------------------------------------------------------------------------- /testing/executables/.gitignore: -------------------------------------------------------------------------------- 1 | *.EXE 2 | *.exe 3 | *.COM 4 | *.com 5 | -------------------------------------------------------------------------------- /testing/logs/.gitignore: -------------------------------------------------------------------------------- 1 | *.BRK 2 | *.LOG 3 | -------------------------------------------------------------------------------- /version.inc: -------------------------------------------------------------------------------- 1 | VERSION=1.6 2 | --------------------------------------------------------------------------------