Files
SabreTools.Serialization/SabreTools.Serialization.CrossModel/FDS.Serializer.cs
Matt Nadareski 7689c6dd07 Libraries
This change looks dramatic, but it's just separating out the already-split namespaces into separate top-level folders. In theory, every single one could be built into their own Nuget package. `SabreTools.Serialization` still builds the normal Nuget package that is used by all other projects and includes all namespaces.
2026-03-21 16:26:56 -04:00

132 lines
4.3 KiB
C#

using System.Collections.Generic;
using SabreTools.Data.Models.NES;
namespace SabreTools.Serialization.CrossModel
{
/// <see href="https://www.nesdev.org/wiki/FDS_disk_format"/>
/// <see href="https://gist.github.com/einstein95/6545066905680466cdf200c4cc8ca4f0"/>
public partial class FamicomDiskSystem : ICrossModel<FDS, QD>
{
/// <inheritdoc/>
public QD? Serialize(FDS? obj)
=> Serialize(obj, false);
/// <inheritdoc/>
/// <param name="nul">True to insert null checksums on conversion, false otherwise</param>
public QD? Serialize(FDS? obj, bool nul)
{
// Ignore invalid data
if (obj?.Data is null || obj.Data.Length == 0)
return null;
// Ignore incomplete data
if ((obj.Data.Length % 65500) != 0)
return null;
// Determine the number of sides
byte sides = obj.Header != null
? obj.Header.DiskSides
: (byte)(obj.Data.Length / 65500);
if (sides < 1)
return null;
// Create the QD for output
var qd = new QD();
// Loop over the sides and convert
List<byte> converted = [];
for (int i = 0; i < sides; i++)
{
// Convert the data to manipulate
List<byte> data = [.. obj.Data];
// If the data is unexpected
if (data[0] != 0x01)
break;
// Insert block 01 checksum
InsertCrc(data, 0, 0x38, nul);
// Insert block 02 checksum
int pos = 0x3A;
InsertCrc(data, pos, pos + 2, nul);
// Loop while there are more files
pos = 0x3E;
while (data[pos] == 3)
{
// Get the filesize
ushort filesize = (ushort)((data[pos + 0x0E] << 8) | data[pos + 0x0D]);
// Insert block 03 checksum
InsertCrc(data, pos, pos + 0x10, nul);
// Skip the file data
pos += 0x10 + 2;
// Insert block 04 checksum
InsertCrc(data, pos, pos + 1 + filesize, nul);
pos += 1 + filesize + 2;
}
// Ensure the data is correctly sized
if (data.Count > 65536)
data = data.GetRange(0, 65536);
else if (data.Count < 65536)
data.AddRange(new byte[65536 - data.Count]);
// Add the data to the output
converted.AddRange(data);
}
// Assign the converted data and return
qd.Data = [.. converted];
return qd;
}
private void InsertCrc(List<byte> data, int start, int end, bool nul)
{
if (!nul)
{
byte[] temp = new byte[end - start];
data.CopyTo(start, temp, 0, temp.Length);
ushort crc = FdsCrc(temp);
data.Insert(end + 0, (byte)(crc & 0xFF));
data.Insert(end + 1, (byte)(crc >> 0x08));
}
else
{
data.Insert(end + 0, 0);
data.Insert(end + 1, 0);
}
}
// TODO: Replace with CRC-16/KERMIT from Hashing
private ushort FdsCrc(byte[] data)
{
// http://forums.nesdev.com/viewtopic.php?p=194867
// Do not include any existing checksum, not even the blank checksums 00 00 or FF FF.
// The formula will automatically count 2 0x00 bytes without the programmer adding them manually.
// Also, do not include the gap terminator (0x80) in the data.
// If you wish to do so, change sum to 0x0000.
ushort s = 0x8000;
byte[] padded = [.. data, 0x00, 0x00];
foreach (byte b in padded)
{
s |= (ushort)(b << 16);
for (int i = 0; i < 8; i++)
{
if ((s & 1) != 0)
{
unchecked { s ^= (ushort)(0x8408 << 1); }
}
s >>= 1;
}
}
return s;
}
}
}