using System; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace com.clusterrr.Famicom.Containers { /// /// File header FDS block (block type 3) /// [StructLayout(LayoutKind.Sequential, Size = 16, Pack = 1)] public class FdsBlockFileHeader : IFdsBlock, IEquatable { /// /// Kind of the file /// public enum Kind { /// /// PRG data /// Program = 0, /// /// CHR data /// Character = 1, /// /// Nametable data /// NameTable = 2 } [MarshalAs(UnmanagedType.U1)] private readonly byte blockType = 3; /// /// Valid block type ID /// public byte ValidTypeID { get => 3; } /// /// True if block type ID is valid /// public bool IsValid { get => blockType == 3; } [MarshalAs(UnmanagedType.U1)] private byte fileNumber; /// /// File number /// public byte FileNumber { get => fileNumber; set => fileNumber = value; } [MarshalAs(UnmanagedType.U1)] private byte fileIndicateCode; /// /// File indicate code (ID specified at disk-read function call) /// public byte FileIndicateCode { get => fileIndicateCode; set => fileIndicateCode = value; } [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] private byte[] fileName = Encoding.ASCII.GetBytes("FILENAME"); /// /// Filename /// public string FileName { get => Encoding.ASCII.GetString(fileName).Trim(new char[] { '\0', ' ' }); set => fileName = Encoding.ASCII.GetBytes(value.PadRight(8)).Take(8).ToArray(); } [MarshalAs(UnmanagedType.U2)] // the destination address when loading private ushort fileAddress; /// /// File address - the destination address when loading /// public ushort FileAddress { get => fileAddress; set => fileAddress = value; } [MarshalAs(UnmanagedType.U2)] private ushort fileSize; /// /// File size /// public ushort FileSize { get => fileSize; set => fileSize = value; } [MarshalAs(UnmanagedType.U1)] private byte fileKind; /// /// Kind of the file: program, character or nametable /// public Kind FileKind { get => (Kind)fileKind; set => fileKind = (byte)value; } /// /// Length of the block /// public uint Length { get => 16; } /// /// Create FdsBlockFileHeader object from raw data /// /// Data /// Offset /// /// public static FdsBlockFileHeader FromBytes(byte[] data, int offset = 0) { int rawsize = Marshal.SizeOf(typeof(FdsBlockFileHeader)); if (rawsize > data.Length - offset) { if (rawsize <= data.Length - offset + 2) { var newRawData = new byte[rawsize]; Array.Copy(data, offset, newRawData, 0, rawsize - 2); data = newRawData; offset = 0; } else { throw new InvalidDataException("Not enough data to fill FdsFileHeaderBlock class. Array length from position: " + (data.Length - offset) + ", struct length: " + rawsize); } } IntPtr buffer = Marshal.AllocHGlobal(rawsize); Marshal.Copy(data, offset, buffer, rawsize); FdsBlockFileHeader retobj = (FdsBlockFileHeader)Marshal.PtrToStructure(buffer, typeof(FdsBlockFileHeader)); Marshal.FreeHGlobal(buffer); return retobj; } /// /// Returns raw data /// /// Data public byte[] ToBytes() { int rawSize = Marshal.SizeOf(this); IntPtr buffer = Marshal.AllocHGlobal(rawSize); Marshal.StructureToPtr(this, buffer, false); byte[] data = new byte[rawSize]; Marshal.Copy(buffer, data, 0, rawSize); Marshal.FreeHGlobal(buffer); return data; } /// /// String representation /// /// File name and file kind as string public override string ToString() => $"{FileName} ({FileKind})"; /// /// Equality comparer /// /// Other FdsBlockFileHeader object /// True if objects are equal public bool Equals(FdsBlockFileHeader other) { return Enumerable.SequenceEqual(this.ToBytes(), other.ToBytes()); } } }