Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alexbevi
GitHub Repository: alexbevi/BizHawk
Path: blob/master/BizHawk.Emulation.Cores/Computers/Commodore64/Media/DiskBuilder.cs
2 views
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace BizHawk.Emulation.Cores.Computers.Commodore64.Media
{
    public class DiskBuilder
    {
        public enum FileType
        {
            Deleted = 0,
            Sequential = 1,
            Program = 2,
            User = 3,
            Relative = 4
        }

        protected class BamEntry
        {
            public int Data { get; private set; }
            public int Sectors { get; private set; }

            public BamEntry(int sectors)
            {
                Data = 0;
                for (var i = 0; i < sectors; i++)
                {
                    Data >>= 1;
                    Data |= 0x800000;
                }
                Data |= (sectors << 24);
                Sectors = sectors;
            }

            private int GetBit(int sector)
            {
                if (sector < 0 || sector >= Sectors)
                {
                    return 0;
                }
                return 0x800000 >> sector;
            }

            public void Allocate(int sector)
            {
                var bit = GetBit(sector);
                if (bit != 0 && (Data & bit) != 0)
                {
                    Data &= ~bit;
                    Data -= 0x1000000;
                }
            }

            public void Free(int sector)
            {
                var bit = GetBit(sector);
                if (bit != 0 && (Data & bit) == 0)
                {
                    Data |= bit;
                    Data += 0x1000000;
                }
            }

            public int SectorsRemaining
            {
                get { return (Data >> 24) & 0xFF; }
            }

            public bool this[int sector]
            {
                get { return (Data & (1 << sector)) != 0; }
                set
                {
                    if (value)
                        Free(sector);
                    else
                        Allocate(sector);
                }
            }

            public byte[] GetBytes()
            {
                return GetBytesEnumerable().ToArray();
            }

            private IEnumerable<byte> GetBytesEnumerable()
            {
                yield return unchecked((byte)(Data >> 24));
                yield return unchecked((byte)(Data >> 16));
                yield return unchecked((byte)(Data >> 8));
                yield return unchecked((byte)Data);
            }

            public IEnumerable<bool> Entries
            {
                get
                {
                    var d = Data;
                    for (var i = 0; i < Sectors; i++)
                    {
                        d <<= 1;
                        yield return (d & 0x1000000) != 0;
                    }
                }
            } 
        }

        protected class LocatedEntry
        {
            public Entry Entry { get; set; }
            public int DirectoryTrack { get; set; }
            public int DirectorySector { get; set; }
            public int Track { get; set; }
            public int Sector { get; set; }
            public int SideTrack { get; set; }
            public int SideSector { get; set; }
            public int LengthInSectors { get; set; }
        }

        public class Entry
        {
            public FileType Type { get; set; }
            public bool Locked { get; set; }
            public bool Closed { get; set; }
            public string Name { get; set; }
            public int RecordLength { get; set; }
            public byte[] Data { get; set; }
        }

        private static readonly int[] SectorsPerTrack =
        {
            21, 21, 21, 21, 21,
            21, 21, 21, 21, 21,
            21, 21, 21, 21, 21,
            21, 21, 19, 19, 19,
            19, 19, 19, 19, 18,
            18, 18, 18, 18, 18,
            17, 17, 17, 17, 17,
            17, 17, 17, 17, 17
        };

        public List<Entry> Entries { get; set; }
        public int VersionType { get; set; }
        public string Title { get; set; }

        public DiskBuilder()
        {
            Entries = new List<Entry>();
            VersionType = 0x41;
        }

        public Disk Build()
        {
            const int tracks = 35;
            var trackByteOffsets = new int[tracks];
            var bam = new BamEntry[tracks];
            var diskFull = false;

            for (var i = 0; i < tracks; i++)
            {
                bam[i] = new BamEntry(SectorsPerTrack[i]);
                if (i > 0)
                {
                    trackByteOffsets[i] = trackByteOffsets[i - 1] + (SectorsPerTrack[i - 1] * 256);
                }
            }
            var bytes = new byte[trackByteOffsets[tracks - 1] + (SectorsPerTrack[tracks - 1] *256)];

            var currentTrack = 16;
            var currentSector = 0;
            var interleaveStart = 0;
            var sectorInterleave = 3;
            var directory = new List<LocatedEntry>();

            Func<int, int, int> GetOutputOffset = (t, s) => trackByteOffsets[t] + (s*256);

            foreach (var entry in Entries)
            {
                var sourceOffset = 0;
                var dataLength = entry.Data == null ? 0 : entry.Data.Length;
                var lengthInSectors = dataLength / 254;
                var dataRemaining = dataLength;
                var directoryEntry = new LocatedEntry
                {
                    Entry = entry,
                    LengthInSectors = lengthInSectors + 1,
                    Track = currentTrack,
                    Sector = currentSector
                };
                directory.Add(directoryEntry);

                while (!diskFull)
                {
                    var outputOffset = GetOutputOffset(currentTrack, currentSector);

                    if (dataRemaining > 254)
                    {
                        Array.Copy(entry.Data, sourceOffset, bytes, outputOffset + 2, 254);
                        dataRemaining -= 254;
                        sourceOffset += 254;
                    }
                    else
                    {
                        if (dataRemaining > 0)
                        {
                            Array.Copy(entry.Data, sourceOffset, bytes, outputOffset + 2, dataRemaining);
                            bytes[outputOffset + 0] = 0;
                            bytes[outputOffset + 1] = (byte)(dataRemaining + 1);
                            dataRemaining = 0;
                        }
                    }

                    bam[currentTrack].Allocate(currentSector);
                    currentSector += sectorInterleave;
                    if (currentSector >= SectorsPerTrack[currentTrack])
                    {
                        interleaveStart++;
                        if (interleaveStart >= sectorInterleave)
                        {
                            interleaveStart = 0;
                            if (currentTrack >= 17)
                            {
                                currentTrack++;
                                if (currentTrack >= 35)
                                {
                                    diskFull = true;
                                    break;
                                }
                            }
                            else
                            {
                                currentTrack--;
                                if (currentTrack < 0)
                                    currentTrack = 18;
                            }
                        }
                        currentSector = interleaveStart;
                    }

                    if (dataRemaining <= 0)
                        break;

                    bytes[outputOffset + 0] = (byte)(currentTrack + 1);
                    bytes[outputOffset + 1] = (byte) currentSector;
                }

                if (diskFull)
                    break;
            }

            // write Directory
            var directoryOffset = -(0x20);
            currentTrack = 17;
            currentSector = 1;
            var directoryOutputOffset = GetOutputOffset(currentTrack, currentSector);
            var fileIndex = 0;
            bam[currentTrack].Allocate(currentSector);
            foreach (var entry in directory)
            {
                directoryOffset += 0x20;
                if (directoryOffset == 0x100)
                {
                    directoryOffset = 0;
                    currentSector += 3;
                    bytes[directoryOutputOffset] = (byte) currentTrack;
                    bytes[directoryOutputOffset + 1] = (byte) currentSector;
                    directoryOutputOffset = GetOutputOffset(currentTrack, currentSector);
                    bam[currentTrack].Allocate(currentSector);
                }
                bytes[directoryOutputOffset + directoryOffset + 0x00] = 0x00;
                bytes[directoryOutputOffset + directoryOffset + 0x01] = 0x00;
                bytes[directoryOutputOffset + directoryOffset + 0x02] = (byte)((int)entry.Entry.Type | (entry.Entry.Locked ? 0x40 : 0x00) | (entry.Entry.Closed ? 0x80 : 0x00));
                bytes[directoryOutputOffset + directoryOffset + 0x03] = (byte)(entry.Track + 1);
                bytes[directoryOutputOffset + directoryOffset + 0x04] = (byte)entry.Sector;
                for (var i = 0x05; i <= 0x14; i++)
                    bytes[directoryOutputOffset + directoryOffset + i] = 0xA0;
                var fileNameBytes = Encoding.ASCII.GetBytes(entry.Entry.Name ?? string.Format("FILE{0:D3}", fileIndex));
                Array.Copy(fileNameBytes, 0, bytes, directoryOutputOffset + directoryOffset + 0x05, Math.Min(fileNameBytes.Length, 0x10));
                bytes[directoryOutputOffset + directoryOffset + 0x1E] = (byte)(entry.LengthInSectors & 0xFF);
                bytes[directoryOutputOffset + directoryOffset + 0x1F] = (byte)((entry.LengthInSectors >> 8) & 0xFF);
                fileIndex++;
            }
            bytes[directoryOutputOffset + 0x00] = 0x00;
            bytes[directoryOutputOffset + 0x01] = 0xFF;

            // write BAM
            var bamOutputOffset = GetOutputOffset(17, 0);
            bytes[bamOutputOffset + 0x00] = 18;
            bytes[bamOutputOffset + 0x01] = 1;
            bytes[bamOutputOffset + 0x02] = (byte)VersionType;
            for (var i = 0; i < 35; i++)
            {
                Array.Copy(bam[i].GetBytes(), 0, bytes, bamOutputOffset + 4 + (i * 4), 4);
            }
            for (var i = 0x90; i <= 0xAA; i++)
            {
                bytes[bamOutputOffset + i] = 0xA0;
            }
            var titleBytes = Encoding.ASCII.GetBytes(Title ?? "UNTITLED");
            Array.Copy(titleBytes, 0, bytes, bamOutputOffset + 0x90, Math.Min(titleBytes.Length, 0x10));

            return D64.Read(bytes);
        }
    }
}