#!/bin/bash
#
# mkfat.sh
#
# Copyright (C) 2017 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

OUTPUT_FILE=floppy.img
ROOT_DIR_ENTRIES=224
NUMBER_OF_FATS=2
TOTAL_SECTORS=2880
BYTES_PER_SECTOR=512

SECTORS_PER_FAT=$((((TOTAL_SECTORS * 3 / 2) + BYTES_PER_SECTOR - 1) / BYTES_PER_SECTOR))
FIRST_ROOT_SECTOR=$((1 + NUMBER_OF_FATS * SECTORS_PER_FAT))
ROOT_DIR_OFFSET=$((FIRST_ROOT_SECTOR * BYTES_PER_SECTOR))
FIRST_DATA_SECTOR=$((FIRST_ROOT_SECTOR + (ROOT_DIR_ENTRIES * 32) / BYTES_PER_SECTOR))

function get_free_cluster()
{
    local cluster=2

    while [[ "$cluster" -lt $TOTAL_SECTORS ]]
    do
        local cluster_pair=`dd if=$OUTPUT_FILE skip=$((BYTES_PER_SECTOR + 3 * (cluster / 2))) bs=1 count=3 2>/dev/null | xxd -p`

        if [[ "${cluster_pair:3:1}${cluster_pair:0:2}" == "000" ]] && [[ "$cluster" -ne 2 ]]
        then
            echo "Found a free cluster: $cluster" > /dev/stderr
            echo $cluster
            return
        elif [[ "${cluster_pair:4:2}${cluster_pair:2:1}" == "000" ]]
        then
            echo "Found a free cluster: $((cluster + 1))" > /dev/stderr
            echo $((cluster + 1))
            return
        fi

        cluster=$((cluster + 2))
    done

    echo 'Ran out of the free clusters.' > /dev/stderr
    rm $OUTPUT_FILE
    exit 1
}

function set_next_cluster()
{
    echo "Setting the next cluster of $1 to $2" > /dev/stderr

    local cluster=$1
    local value=$(printf "%03X" $2)
    local cluster_pair=`dd if=$OUTPUT_FILE skip=$((BYTES_PER_SECTOR + 3 * (cluster / 2))) bs=1 count=3 2>/dev/null | xxd -p`

    if [[ "$((cluster % 2))" -eq 0 ]]
    then
        cluster_pair="${value:1:2}${cluster_pair:2:1}${value:0:1}${cluster_pair:4:2}"
    else
        cluster_pair="${cluster_pair:0:2}${value:2:1}${cluster_pair:3:1}${value:0:2}"
    fi

    for fat in `seq 0 $((NUMBER_OF_FATS - 1))`
    do
        echo $cluster_pair | xxd -r -p | dd conv=notrunc \
                                            iflag=fullblock \
                                            oflag=seek_bytes \
                                            of=$OUTPUT_FILE \
                                            seek=$((BYTES_PER_SECTOR * (1 + fat * SECTORS_PER_FAT) + 3 * (cluster / 2))) \
                                            bs=3 \
                                            count=1 \
                                            2>/dev/null
    done
}

dd if=/dev/zero of=$OUTPUT_FILE bs=$BYTES_PER_SECTOR count=$TOTAL_SECTORS 2>/dev/null
/sbin/mkfs.vfat -r $ROOT_DIR_ENTRIES -f $NUMBER_OF_FATS -s 1 -S $BYTES_PER_SECTOR $OUTPUT_FILE >/dev/null 2>&1

echo "Created empty FAT12 disk image $OUTPUT_FILE" > /dev/stderr

for file in *.prg
do
    [[ -f $file ]] || break
    echo "Copying $file to the disk image..." > /dev/stderr

    size=`stat --printf '%s' $file`
    clusters=$(((size + BYTES_PER_SECTOR - 1) / BYTES_PER_SECTOR))
    dosname=$(printf '%-8s%-3s' `echo ${file%%.*} | tr 'a-z' 'A-Z'` `echo ${file##*.} | tr 'a-z' 'A-Z'`)

    if [[ ${#dosname} -ne 11 ]]
    then
        echo "Cannot easily create a DOS name for $file" > /dev/stderr
        continue
    fi

    for dirent in `seq 0 $((ROOT_DIR_ENTRIES - 1))`
    do
        if [[ `dd if=$OUTPUT_FILE skip=$((dirent * 32 + ROOT_DIR_OFFSET)) bs=1 count=1 2>/dev/null | xxd -p` == "00" ]]
        then
            break
        fi
    done

    if [[ $dirent -ge $ROOT_DIR_ENTRIES ]]
    then
        echo 'Too many files, sorry.' > /dev/stderr
        exit 1
    fi

    echo "$file will be placed in directory entry number $dirent" > /dev/stderr

    if [[ $clusters -ne 0 ]]
    then
        first_cluster=`get_free_cluster`
        if [[ $? -ne 0 ]]
        then
            echo 'No more free clusters...'
            exit 1
        fi
    else
        first_cluster=0
    fi

    (
        echo -en "${dosname}\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        printf '%02X%02X' $((first_cluster & 0xFF)) $((first_cluster >> 8)) | xxd -r -p
        printf '%02X%02X%02X%02X' \
               $((size & 0xFF)) \
               $(((size >> 8) & 0xFF)) \
               $(((size >> 16) & 0xFF)) \
               $(((size >> 24) & 0xFF)) | xxd -r -p
    ) | dd conv=notrunc \
           iflag=fullblock \
           of=$OUTPUT_FILE \
           seek=$(((ROOT_DIR_OFFSET / 32) + dirent)) \
           bs=32 \
           count=1 \
           2>/dev/null

    current_cluster=$first_cluster

    for ((i = 0; i < $clusters; i++))
    do
        echo "Writing cluster #$i of $file to cluster $current_cluster" > /dev/stderr
        dd conv=notrunc \
           if=$file \
           of=$OUTPUT_FILE \
           skip=$i \
           seek=$((FIRST_DATA_SECTOR + current_cluster - 2)) \
           bs=$BYTES_PER_SECTOR \
           count=1 \
           2>/dev/null

        set_next_cluster $current_cluster 4095

        if [[ "$i" -ne "$((clusters - 1))" ]]
        then
            next_cluster=`get_free_cluster`
            set_next_cluster $current_cluster $next_cluster
            current_cluster=$next_cluster
        fi
    done
done

echo "All done!" > /dev/stderr
