using System;
using System.Collections.Generic;
using System.Linq;
namespace com.clusterrr.Famicom.Containers
{
///
/// Single FDS disk side: disk info block, file amount block and file blocks
///
public class FdsDiskSide
{
readonly FdsBlockDiskInfo diskInfoBlock;
///
/// Disk info block
///
public FdsBlockDiskInfo DiskInfoBlock { get => diskInfoBlock; }
///
/// Literal ASCII string: *NINTENDO-HVC*
///
public string DiskVerification { get => diskInfoBlock.DiskVerification; }
///
/// Manufacturer code. $00 = Unlicensed, $01 = Nintendo
///
public FdsBlockDiskInfo.Company ManufacturerCode { get => diskInfoBlock.LicenseeCode; set => diskInfoBlock.LicenseeCode = value; }
///
/// 3-letter ASCII code per game (e.g. ZEL for The Legend of Zelda)
///
public string GameName { get => diskInfoBlock.GameName; set => diskInfoBlock.GameName = value; }
///
/// $20 = " " — Normal disk
/// $45 = "E" — Event(e.g.Japanese national DiskFax tournaments)
/// $52 = "R" — Reduction in price via advertising
///
public char GameType { get => diskInfoBlock.GameType; set => diskInfoBlock.GameType = value; }
///
/// Game version/revision number. Starts at $00, increments per revision
///
public byte GameVersion { get => diskInfoBlock.GameVersion; set => diskInfoBlock.GameVersion = value; }
///
/// Side number. Single-sided disks use A
///
public FdsBlockDiskInfo.DiskSides DiskSide { get => diskInfoBlock.DiskSide; set => diskInfoBlock.DiskSide = value; }
///
/// Disk number. First disk is $00, second is $01, etc.
///
public byte DiskNumber { get => diskInfoBlock.DiskNumber; set => diskInfoBlock.DiskNumber = value; }
///
/// Disk type. $00 = FMC ("normal card"), $01 = FSC ("card with shutter"). May correlate with FMC and FSC product codes
///
public FdsBlockDiskInfo.DiskTypes DiskType { get => diskInfoBlock.DiskType; set => diskInfoBlock.DiskType = value; }
///
/// Boot read file code. Refers to the file code/file number to load upon boot/start-up
///
public byte BootFile { get => diskInfoBlock.BootFile; set => diskInfoBlock.BootFile = value; }
///
/// Manufacturing date
///
public DateTime ManufacturingDate { get => diskInfoBlock.ManufacturingDate; set => diskInfoBlock.ManufacturingDate = value; }
///
/// Country code. $49 = Japan
///
public FdsBlockDiskInfo.Country CountryCode { get => diskInfoBlock.CountryCode; set => diskInfoBlock.CountryCode = value; }
///
/// "Rewritten disk" date. It's speculated this refers to the date the disk was formatted and rewritten by something like a Disk Writer kiosk.
/// In the case of an original (non-copied) disk, this should be the same as Manufacturing date
///
public DateTime RewrittenDate { get => diskInfoBlock.RewrittenDate; set => diskInfoBlock.RewrittenDate = value; }
///
/// Disk Writer serial number
///
public ushort DiskWriterSerialNumber { get => diskInfoBlock.DiskWriterSerialNumber; set => diskInfoBlock.DiskWriterSerialNumber = value; }
///
/// Disk rewrite count. $00 = Original (no copies)
///
public byte DiskRewriteCount { get => diskInfoBlock.DiskRewriteCount; set => diskInfoBlock.DiskRewriteCount = value; }
///
/// Actual disk side
///
public FdsBlockDiskInfo.DiskSides ActualDiskSide { get => diskInfoBlock.ActualDiskSide; set => diskInfoBlock.ActualDiskSide = value; }
///
/// Price code
///
public byte Price { get => diskInfoBlock.Price; set => diskInfoBlock.Price = value; }
readonly FdsBlockFileAmount fileAmountBlock;
///
/// Non-hidden file amount
///
public byte FileAmount { get => fileAmountBlock.FileAmount; set => fileAmountBlock.FileAmount = value; }
readonly IList files;
///
/// Files on disk
///
public IList Files { get => files; }
///
/// Constructor to create empty FdsDiskSide object
///
public FdsDiskSide()
{
diskInfoBlock = new FdsBlockDiskInfo();
fileAmountBlock = new FdsBlockFileAmount();
files = new List();
}
///
/// Constructor to create FdsDiskSide object from blocks and files
///
/// Disk info block
/// File amount block
/// Files
public FdsDiskSide(FdsBlockDiskInfo diskInfoBlock, FdsBlockFileAmount fileAmountBlock, IEnumerable files)
{
this.diskInfoBlock = diskInfoBlock;
this.fileAmountBlock = fileAmountBlock;
this.files = files.ToList();
}
///
/// Constructor to create FdsDiskSide object from blocks
///
///
public FdsDiskSide(IEnumerable blocks)
{
this.diskInfoBlock = (FdsBlockDiskInfo)blocks.First();
this.fileAmountBlock = (FdsBlockFileAmount)blocks.Skip(1).First();
files = new List();
var fileBlocks = blocks.Skip(2).ToArray();
for (int i = 0; i < fileBlocks.Length / 2; i++)
{
files.Add(new FdsDiskFile((FdsBlockFileHeader)fileBlocks[i * 2], (FdsBlockFileData)fileBlocks[i * 2 + 1]));
}
}
///
/// Constructor to create FdsDiskSide object from raw data
///
///
public FdsDiskSide(byte[] data) : this()
{
int pos = 0;
this.diskInfoBlock = FdsBlockDiskInfo.FromBytes(data.Take(56).ToArray());
pos += 56;
this.fileAmountBlock = FdsBlockFileAmount.FromBytes(data.Skip(pos).Take(2).ToArray());
pos += 2;
while (pos < data.Length)
{
try
{
var fileHeaderBlock = FdsBlockFileHeader.FromBytes(data.Skip(pos).Take(16).ToArray());
if (!fileHeaderBlock.IsValid)
break;
pos += 16;
var fileDataBlock = FdsBlockFileData.FromBytes(data.Skip(pos).Take(fileHeaderBlock.FileSize + 1).ToArray());
if (!fileDataBlock.IsValid)
break;
pos += fileHeaderBlock.FileSize + 1;
files.Add(new FdsDiskFile(fileHeaderBlock, fileDataBlock));
}
catch
{
// just break on out of range
break;
}
}
}
///
/// Change file's "file number" fields orderly
///
public void FixFileNumbers()
{
for (var i = 0; i < files.Count; i++)
files[i].FileNumber = (byte)i;
}
///
/// Get FDS blocks
///
///
public IEnumerable GetBlocks()
{
var blocks = new List
{
diskInfoBlock,
fileAmountBlock
};
blocks.AddRange(files.SelectMany(f => new IFdsBlock[] { f.HeaderBlock, f.DataBlock }));
return blocks;
}
///
/// Create FdsDiskSide object from raw data
///
/// Data
/// FdsDiskSide object
public static FdsDiskSide FromBytes(byte[] data)
{
return new FdsDiskSide(data);
}
///
/// Return raw data
///
///
public byte[] ToBytes()
{
var data = Enumerable.Concat(Enumerable.Concat(diskInfoBlock.ToBytes(), fileAmountBlock.ToBytes()), files.SelectMany(f => f.ToBytes())).ToArray();
return Enumerable.Concat(data, new byte[65500 - data.Count()]).ToArray();
}
///
/// String representation
///
/// Game name, disk number, side number as string
public override string ToString() => $"{GameName} - disk {DiskNumber + 1}, side {DiskSide}";
}
}