Creating bootable FreeDOS DOS floppy diskette IMG file for V86 on OSX
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/
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.
-
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
-
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
andcount=1440
parameter values were determined to create the empty image file. Also, this is where the-F 12
parameter value was determined for thenewfs_msdos
command. -
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.
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 4 0
[-s] big total sectors getint 20 4 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.