mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-06 06:11:45 +00:00
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.
132 lines
4.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|