322 lines
7.9 KiB
C#
322 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
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; }
|
|
|
|
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 => (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 ?? $"FILE{fileIndex:D3}");
|
|
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);
|
|
}
|
|
}
|
|
}
|