using System.Collections.Generic; using SabreTools.Data.Models.NES; namespace SabreTools.Serialization.CrossModel { /// /// public partial class FamicomDiskSystem : ICrossModel { /// public QD? Serialize(FDS? obj) => Serialize(obj, false); /// /// True to insert null checksums on conversion, false otherwise 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 converted = []; for (int i = 0; i < sides; i++) { // Convert the data to manipulate List 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 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; } } }