MacOS – Creating bootable FreeDOS DOS floppy diskette IMG file for V86 on OSX

bootable-diskmacos

I am trying to make a FreeDOS boot disk with OSX that's compatible with the V86 library. I need to do this in terminal (in a bash script). I'm doing all of this on OSX High Sierra.

The boot disk I'm attempting to create is an .img of a floppy diskette. I'm aware there are other ways to provide bootable disks to V86 such as a hard drive image or a cdrom .iso, but I'm trying this way and would like to at least understand what I'm doing wrong.

I suspect the problem I'm having is in attempting to properly create the diskette image's Master Boot Record (MBR).


For context, here's my understanding of the MBR/boot process works:

In the old days on DOS or on Win9x machines, I seem to remember needing to run a format command with some switches on it that would make the MBR on the disk rather than just a FAT table at the front of the disk – though I'm not sure if I'm understanding MBR correctly.

My basic, simplified understanding of the MBR as that the MBR is the first 512 bytes of the diskette, which specifies how large the disk is, as well instructions that tell the BIOS to jump to the first instruction of a boot loader that'll kickstart the operating system. For example, on a DOS diskette there is/was a COMMAND.COM fiel on it. Presumably in that case, the MBR was written to tell BIOS to start execution on the first instruction in COMMAND.COM.

Figuring out where the first instruction is seems like it could be complicated because a number of things need to happen (I think..). The first 512 bytes of the disk will be reserved for the MBR, then after that a FAT is written down which takes another X bytes of the disk, then after the FAT, there's empty space to put files down. When a file such as COMMAND.COM is written to the disk, the FAT is updated with information about where on the disk the bytes are stored. For the MBR to know where to jump to to find the first instruction of COMMAND.COM, it would need to know where COMMAND.COM is stored, which could be anywhere on the disk in the empty space after the FAT, right?


Here's my current attempt at creating the disk:

# create a 720k empty img file with all zeros in it

dd if=/dev/zero of=myfloppy.img bs=1024 count=1440

# attach our .img file without mounting it, saving the attached name such as "/dev/disk2" in the var ${DEVICE}

DEVICE=`hdiutil attach -nomount myfloppy.img`

# attempt to use diskutil to format the device as a bootable FAT12 diskette
# question: are dos boot diskettes supposed to be FAT12, FAT16, or FAT32?

diskutil eraseVolume "MS-DOS FAT12" MYFLOPPY bootable ${DEVICE}

# detach our unmounted .img file

hdiutil detach $DEVICE -force

# mount our source freedos image fetched from the V86 demo page, this mounts as /Volume/FREEDOS
# note that this sample image is also 720K

hdiutil mount freedos722.img 

# mount our target .img file we just created, it'll mount as /VOLUMES/MYFLOPPY

hdiutil mount myfloppy.img

# copy minimal set of files necessary to boot the disk (probably dont need autoexec.bat ..)
# question: does the copy order of these files matter? 

cp /Volumes/FREEDOS/COMMAND.COM /Volumes/MYFLOPPY/
cp /Volumes/FREEDOS/KERNEL.SYS /Volumes/MYFLOPPY/
cp /Volumes/FREEDOS/CONFIG.SYS /Volumes/MYFLOPPY/
cp /Volumes/FREEDOS/AUTOEXEC.BAT /Volumes/MYFLOPPY/

# unmount our disks

hdiutil unmount /Volumes/MYFLOPPY/
hdiutil unmount /Volumes/FREEDOS/

#TODO: might need to detach our image files even though we did mount/unmount above

That script above gets me close to bootable, the V86 startup shows "FREEDOS" rather than complaing about the disk not being bootable, but it just hangs after saying "FREEDOS", rather than continuing the boot process.


I've also tried various other ways/attempts of creating the MBR, in all of these cases I created the blank img file with the dd command, then ran these on it to create the MBR, then copied files as shown above.

# Attempt #1: After the files are copied to the target disk, try to copy the 512 byte MBR from the source image to mine, if this worked I would be surprised, it didn't.
# this shouldn't work afaik because the MBR on the source image will have instructions to jump to the first instruction in COMMAND.COM as it's layed down on the source disk, which likely isn't where it is on our target disk.

dd if=freedos722.img of=myfloppy.img bs=512 count=1 conv=notrunc

# Attempt #2: try using the diskutil partition instead of erase disk command

diskutil partitiondisk ${DEVICE} 1 MBR "MS-DOS FAT12" "MYFLOPPY" 100%

# Attempt #3: try same as above, but with FAT16 (shouldn't work, didn't, because source img was FAT12)

diskutil partitiondisk ${DEVICE} 1 MBR "MS-DOS FAT16" "MYFLOPPY" 0B

# Attempt #4: use the newfs_msdos command without any diskutil commands

newfs_msdos -F 12 -v MYFLOPPY $DEVICE

# Attempt #5: use fdisk rather than any diskutil or newfs_msdos commands
# I *think* this is making an MBR, but not sure, maybe just making a partition
#note that I have no idea what to specify here for cylinders (c) and heads (h) and just copy/pasted this from somewhere

fdisk -i -c 1024 -h 255 -s 1440 -b 512 -a dos ${DEVICE}

# Attempt #6: use fdisk to create MBR / FAT, then use fdisk to mark that partition 'active'

fdisk -i -c 1024 -h 255 -s 1440 -b 512 -a dos ${DEVICE}
fdisk -e ${DEVICE} 
#fdisk -e puts us in an interactive edit mode, so we do the following
type 'a', then enter # gets us into 'set partition as active' mode
type '1', then enter # sets partition as active
type 'w', then enter # writes the MBR with the partition now marked active

I've also tried various incantations where I mix the commands above, such as running diskutil partition then running the fdisk -i command to see if there's something fdisk will do to fix the MBR for the given existing partition.


I'm aware that there are long ways to create a bootable diskette, but I want to do this in a script. These methods are listed here for completeness sake in case others are looking for possible ways to do this:

1: Use the FreeDOS installer in a virtual env such as QEMU or VirtualBox, then save an image of the created floppy diskette.

2: Use an ancient machine to create a bootable diskette from an actual DOS or Windows installation.


I thought perhaps V86 has a very narrow chance of booting a diskette if anything is wrong, which wouldn't make sense as its just emulating x86, but I tried booting from several of the creation attempts with qemu too. For anyoen who's trying to do that, you can easily install qemu with homebrew, then boot a simple VM with this:

qemu-system-x86_64 -fda myfloppy.img

And a link to a V86 issue where the developer mentions how to make QEMU images via full OS installation processes that'll work with V86:

https://github.com/copy/v86/issues/128


If you're unfamiliar with V86, it's a x86 emulator written on top of asm.js that runs a virtualized OS in a browser.

The author has various Linux, Windows, and DOS demo operating systems running on v86 on his personal host:

https://copy.sh/v86/

It took a little bit for me to figure out how to run just his FreeDOS demo image locally without the big demo that requires downloading multiple OS images, here's my source for that:

<!doctype html>
<script src="libv86.js"></script>
<script>
    "use strict";

    window.onload = function() {
            var emulator = window.emulator = new V86Starter({
            memory_size: 32 * 1024 * 1024,
            vga_memory_size: 2 * 1024 * 1024,
            screen_container: document.getElementById("screen_container"),
            bios: { url: "seabios.bin", },
            vga_bios: { url: "vgabios.bin", },
            fda: { "url": "freedos722-t.img", },
            autostart: true,
        });
    }
</script>

<div id="screen_container">
    <div style="white-space: pre; font: 14px monospace; line-height: 14px"></div>
    <canvas style="display: none"></canvas>
</div>

The FreeDOS .img file that works with V86 was fetched from here:

https://github.com/copy/images/

Best Answer

Here's my current attempt at creating the disk:

# create a 720k empty img file with all zeros in it

dd if=/dev/zero of=myfloppy.img bs=512 count=1440

# after using the freedos722.img fetched from the V86 demo page 
# to determine the reserve was 1 logical sector, extract the boot code.

dd if=freedos722.img of=boot.img bs=512 count=1

# after using the 720K freedos722.img to determine the command 
# options, format the floppy image

newfs_msdos -B ./boot.img -v MYFLOPPY -f 720 -b 1024 -S 512 -r 1 -F 12 ./myfloppy.img

# remove the boot code file

rm boot.img

# mount our source freedos image fetched from the V86 demo page, 
# this mounts as /Volumes/FREEDOS

hdiutil attach -readonly freedos722.img 

# mount our target .img file we just created, it'll mount as /Volumes/MYFLOPPY

hdiutil attach myfloppy.img

# copy minimal set of files necessary to boot the disk (probably dont need autoexec.bat ..)
# note: the order of the files does not seem to matter.

cp /Volumes/FREEDOS/COMMAND.COM /Volumes/MYFLOPPY/
cp /Volumes/FREEDOS/KERNEL.SYS /Volumes/MYFLOPPY/
cp /Volumes/FREEDOS/CONFIG.SYS /Volumes/MYFLOPPY/
cp /Volumes/FREEDOS/AUTOEXEC.BAT /Volumes/MYFLOPPY/
mkdir /Volumes/MYFLOPPY/FDOS/
cp /Volumes/FREEDOS/FDOS/HIMEM.EXE /Volumes/MYFLOPPY/FDOS/

# eject our disks

hdiutil eject /Volumes/MYFLOPPY/
hdiutil eject /Volumes/FREEDOS/

How to Determine the Integer Values Used in the Above Commands

To determine the size of the floppy image and the type of FAT, enter the following sequence of commands.

  1. The command below mounts the image.

    hdiutil attach -readonly freedos722.img
    

    The output from this command is shown below. This output shows the volume can be identified as FREEDOS.

    /dev/disk1                                              /Volumes/FREEDOS
    
  2. Enter the command below to get information about the image.

    diskutil info FREEDOS
    

    Below is the pertinent output from this command.

       File System Personality:  MS-DOS FAT12
       Disk Size:                737.3 KB (737280 Bytes) (exactly 1440 512-Byte-Units)
    

    So this is where the bs=512 and count=1440 parameter values were determined to create the empty image file. Also, this is where the -F 12 parameter value was determined for the newfs_msdos command.

  3. Enter the command below to eject the volume.

    diskutil eject FREEDOS
    

Below is a table taken from this pcguide website. The "Total Sectors Per Disk" row shows the image to represent a 720 KB floppy. This where the -f 720 parameter value was determined for the newfs_msdos command.

xa1

Some of the rest of the integer values can be taken from this table. The values can also be extracted from the BIOS Parameter Block stored in the freedos722.img file.

The command below can be use to display the Volume Boot Record (VBR) stored in the freedos722.img file. The BIOS Parameter Block starts at offset 0x0B.

dd if=freedos722.img  count=1 bs=512 | hexdump -Cv

The structure of the BIOS Parameter Block is outlined in the output from the man newfs_msdos command. Below is the pertinent output from this command.

Note: The output below shows the mapping between the newfs_msdos command options and the values stored in the BIOS Parameter Block.

struct bsbpb {
    u_int16_t   bps;            /* [-S] bytes per sector */
    u_int8_t    spc;            /* [-c] sectors per cluster */
    u_int16_t   res;            /* [-r] reserved sectors */
    u_int8_t    nft;            /* [-n] number of FATs */
    u_int16_t   rde;            /* [-e] root directory entries */
    u_int16_t   sec;            /* [-s] total sectors */
    u_int8_t    mid;            /* [-m] media descriptor */
    u_int16_t   spf;            /* [-a] sectors per FAT */
    u_int16_t   spt;            /* [-u] sectors per track */
    u_int16_t   hds;            /* [-h] drive heads */
    u_int32_t   hid;            /* [-o] hidden sectors */
    u_int32_t   bsec;           /* [-s] big total sectors */
};

Note: The parameter [-s] big total sectors should be ignored. Use the parameter [-s] total sectors instead.

A more eloquent way to determine the values in the the BIOS Parameter Block would be to first enter the function definitions given below.

getint() { hexdump -s 0x$1 -n $2 -e \"%u\\n\" freedos722.img; }
gethex() { hexdump -s 0x$1 -n $2 -e \"0x%x\\n\" freedos722.img; }

These functions have two positional parameters. The first is the offset into the freedos722.img file. This value must be entered using hexadecimal digits. The second is the number of bytes to read. Valid values are 1, 2 and 4.

The table below shows the commands used to extract the following BIOS Parameter Block values from the freedos722.img file.

         Parameter               Command     Returned Value
----------------------------   -----------   --------------
[-S] bytes per sector          getint b  2         512
[-c] sectors per cluster       getint d  1           2
[-r] reserved sectors          getint e  2           1
[-n] number of FATs            getint 10 1           2 
[-e] root directory entries    getint 11 2         112
[-s] total sectors             getint 13 2        1440
[-m] media descriptor          gethex 15 1        0xf9
[-a] sectors per FAT           getint 16 2           3
[-u] sectors per track         getint 18 2           9
[-h] drive heads               getint 1a 2           2
[-o] hidden sectors            getint 1c 2           0
[-s] big total sectors         getint 1e 2           0

The bytes per sector and reserved sectors values were used to determine the bs=512 and count=1 parameter values for the dd command used to create the boot code file boot.img. The -b 1024 parameter value for the newfs_msdos command was determined by multiplying the bytes per sector and sectors per cluster values together.