using System.IO; using SabreTools.Data.Models.NES; namespace SabreTools.Wrappers { public partial class NESCart : WrapperBase { #region Descriptive Properties /// public override string DescriptionString => "Nintendo Entertainment System Cart Image"; #endregion #region Extension Properties #region Common /// public CartHeader? Header => Model.Header; /// public bool AlternativeNametableLayout => Header?.AlternativeNametableLayout ?? false; /// public bool BatteryBackedPrgRam => Header?.BatteryBackedPrgRam ?? false; /// public byte[] ChrRomData => Model.ChrRomData; /// /// CHR-ROM size in bytes /// /// Extended by NES 2.0 public int ChrRomSize { get { // Missing header if (Header is null) return 0; int chrRomSize = Header.ChrRomSize * 8192; if (Header is CartHeader2 header2) chrRomSize = (header2.ChrRomSizeMSB << 8) | chrRomSize; return chrRomSize; } } /// public ConsoleType ConsoleType => Header?.ConsoleType ?? ConsoleType.StandardSystem; /// /// Mapper ID /// /// Extended by NES 2.0 public int Mapper { get { // Missing header if (Header is null) return 0; int mapperNumber = (Header.MapperUpperNibble << 4) | Header.MapperLowerNibble; if (Header is CartHeader2 header2) mapperNumber = (header2.MapperMSB << 8) | mapperNumber; return mapperNumber; } } /// public NametableArrangement NametableArrangement => Header?.NametableArrangement ?? NametableArrangement.Vertical; /// public bool NES20 => Header?.NES20 ?? false; /// /// PRG-RAM size in bytes /// /// Extended by NES 2.0 public int PrgRamSize { get { // Missing header if (Header is null) return 0; if (Header is CartHeader1 header1) return header1.PrgRamSize > 0 ? header1.PrgRamSize * 8192 : 8192; else if (Header is CartHeader2 header2) return header2.PrgRamShiftCount > 0 ? 64 << header2.PrgRamShiftCount : 0; else return 0; } } /// /// PRG-ROM size in bytes /// /// Extended by NES 2.0 public int PrgRomSize { get { // Missing header if (Header is null) return 0; int prgRomSize = Header.PrgRomSize * 16384; if (Header is CartHeader2 header2) prgRomSize = (header2.PrgRomSizeMSB << 8) | prgRomSize; return prgRomSize; } } /// public byte[] PrgRomData => Model.PrgRomData; /// public byte[] PlayChoiceInstRom => Model.PlayChoiceInstRom; /// public byte[] PlayChoiceProm => Model.PlayChoiceProm; /// public byte[] Title => Model.Title; /// public byte[] Trainer => Model.Trainer; /// public bool TrainerPresent => Header?.TrainerPresent ?? false; #endregion #region NES 1.0 /// /// Defined only for NES 1.0 public bool HasBusConflicts { get { // Missing or invalid header if (Header is null || Header is not CartHeader1 header1) return false; return header1.HasBusConflicts; } } /// /// Defined only for NES 1.0 public bool PrgRamPresent { get { // Missing or invalid header if (Header is null || Header is not CartHeader1 header1) return false; return header1.PrgRamPresent; } } /// /// Defined only for NES 1.0 public TVSystem TVSystem { get { // Missing or invalid header if (Header is null || Header is not CartHeader1 header1) return TVSystem.NTSC; return header1.TVSystem; } } /// /// Defined only for NES 1.0 public TVSystemExtended TVSystemExtended { get { // Missing or invalid header if (Header is null || Header is not CartHeader1 header1) return TVSystemExtended.NTSC; return header1.TVSystemExtended; } } #endregion #region NES 2.0 /// /// CHR-NVRAM size in bytes /// /// Defined only for NES 2.0 public int ChrNvramSize { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return 0; return header2.ChrNvramShiftCount > 0 ? 64 << header2.ChrNvramShiftCount : 0; } } /// /// CHR-RAM size in bytes /// /// Defined only for NES 2.0 public int ChrRamSize { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return 0; return header2.ChrRamShiftCount > 0 ? 64 << header2.ChrRamShiftCount : 0; } } /// /// CPU/PPU Timing /// /// Defined only for NES 2.0 public CPUPPUTiming CPUPPUTiming { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return CPUPPUTiming.RP2C02; return header2.CPUPPUTiming; } } /// /// Default expansion device /// /// Defined only for NES 2.0 public DefaultExpansionDevice DefaultExpansionDevice { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return DefaultExpansionDevice.Unspecified; return header2.DefaultExpansionDevice; } } /// /// Extended console type /// /// Defined only for NES 2.0 public ExtendedConsoleType ExtendedConsoleType { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return ExtendedConsoleType.RegularSystem; // Invalid console type if (ConsoleType != ConsoleType.ExtendedConsoleType) return ExtendedConsoleType.RegularSystem; return header2.ExtendedConsoleType; } } /// /// Number of miscellaneous ROMs present /// /// Defined only for NES 2.0 public int MiscellaneousROMs { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return 0; return header2.MiscellaneousROMs; } } /// /// PRG-NVRAM/EEPROM size in bytes /// /// Defined only for NES 2.0 public int PrgNvramEepromSize { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return 0; return header2.PrgNvramEepromShiftCount > 0 ? 64 << header2.PrgNvramEepromShiftCount : 0; } } /// /// Defined only for NES 2.0 public int Submapper { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return 0; return header2.Submapper; } } /// /// Vs. Hardware Type /// /// Defined only for NES 2.0 public VsHardwareType VsHardwareType { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return VsHardwareType.VsUnisystem; // Invalid console type if (ConsoleType != ConsoleType.VSUnisystem) return VsHardwareType.VsUnisystem; return header2.VsHardwareType; } } /// /// Vs. System Type /// /// Defined only for NES 2.0 public VsSystemType VsSystemType { get { // Missing or invalid header if (Header is null || Header is not CartHeader2 header2) return VsSystemType.AnyRP2C03RC2C03Variant; // Invalid console type if (ConsoleType != ConsoleType.VSUnisystem) return VsSystemType.AnyRP2C03RC2C03Variant; return header2.VsSystemType; } } #endregion #endregion #region Constructors /// public NESCart(Cart model, byte[] data) : base(model, data) { } /// public NESCart(Cart model, byte[] data, int offset) : base(model, data, offset) { } /// public NESCart(Cart model, byte[] data, int offset, int length) : base(model, data, offset, length) { } /// public NESCart(Cart model, Stream data) : base(model, data) { } /// public NESCart(Cart model, Stream data, long offset) : base(model, data, offset) { } /// public NESCart(Cart model, Stream data, long offset, long length) : base(model, data, offset, length) { } #endregion #region Static Constructors /// /// Create an NES cart image from a byte array and offset /// /// Byte array representing the archive /// Offset within the array to parse /// An NES cart image wrapper on success, null on failure public static NESCart? Create(byte[]? data, int offset) { // If the data is invalid if (data is null || data.Length == 0) return null; // If the offset is out of bounds if (offset < 0 || offset >= data.Length) return null; // Create a memory stream and use that var dataStream = new MemoryStream(data, offset, data.Length - offset); return Create(dataStream); } /// /// Create an NES cart image from a Stream /// /// Stream representing the archive /// An NES cart image wrapper on success, null on failure public static NESCart? Create(Stream? data) { // If the data is invalid if (data is null || !data.CanRead) return null; try { // Cache the current offset long currentOffset = data.Position; var model = new Serialization.Readers.NESCart().Deserialize(data); if (model is null) return null; return new NESCart(model, data, currentOffset); } catch { return null; } } #endregion } }