From 5c973bac96de7011c4feab4815cf82c39ca99a61 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 6 Mar 2026 13:18:49 -0500 Subject: [PATCH] Fill in large number of extensions for NES cart --- SabreTools.Serialization/Readers/NESCart.cs | 2 + .../Wrappers/NESCart.Printing.cs | 2 +- SabreTools.Serialization/Wrappers/NESCart.cs | 416 +++++++++++++++++- 3 files changed, 413 insertions(+), 7 deletions(-) diff --git a/SabreTools.Serialization/Readers/NESCart.cs b/SabreTools.Serialization/Readers/NESCart.cs index a0e34888..abd57f35 100644 --- a/SabreTools.Serialization/Readers/NESCart.cs +++ b/SabreTools.Serialization/Readers/NESCart.cs @@ -44,11 +44,13 @@ namespace SabreTools.Serialization.Readers // Read the PRG-ROM data // TODO: Make model for PRG-ROM data blocks + // TODO: Use combined size from 2.0 int prgRomSize = cart.Header.PRGROMSize * 16384; if (prgRomSize > 0) cart.PRGROMData = data.ReadBytes(prgRomSize); // Read the CHR-ROM data, if necessary + // TODO: Use combined size from 2.0 int chrRomSize = cart.Header.CHRROMSize * 8192; if (chrRomSize > 0) cart.CHRROMData = data.ReadBytes(chrRomSize); diff --git a/SabreTools.Serialization/Wrappers/NESCart.Printing.cs b/SabreTools.Serialization/Wrappers/NESCart.Printing.cs index 2ed63241..071ef331 100644 --- a/SabreTools.Serialization/Wrappers/NESCart.Printing.cs +++ b/SabreTools.Serialization/Wrappers/NESCart.Printing.cs @@ -229,7 +229,7 @@ namespace SabreTools.Serialization.Wrappers byte chrRamShiftCount = (byte)(header2.CHRRAMSize & 0x0F); int chrRamSize = chrRamShiftCount > 0 ? 64 << chrRamShiftCount : 0; byte chrNvramShiftCount = (byte)(header2.CHRRAMSize >> 4); - int chrNvramSize = eepromShiftCount > 0 ? 64 << chrNvramShiftCount : 0; + int chrNvramSize = chrNvramShiftCount > 0 ? 64 << chrNvramShiftCount : 0; builder.AppendLine(chrRamShiftCount, " CHR-RAM shift count"); builder.AppendLine(chrRamSize, " CHR-RAM size"); diff --git a/SabreTools.Serialization/Wrappers/NESCart.cs b/SabreTools.Serialization/Wrappers/NESCart.cs index ab194d53..b7286cec 100644 --- a/SabreTools.Serialization/Wrappers/NESCart.cs +++ b/SabreTools.Serialization/Wrappers/NESCart.cs @@ -3,7 +3,7 @@ using SabreTools.Data.Models.NES; namespace SabreTools.Serialization.Wrappers { - // TODO: Add better extension properties + // TODO: Add extension properties for more 1.0 and 2.0 header pieces public partial class NESCart : WrapperBase { #region Descriptive Properties @@ -18,24 +18,428 @@ namespace SabreTools.Serialization.Wrappers /// public Header? Header => Model.Header; - /// - public byte[] Trainer => Model.Trainer; + /// + public byte[] CHRROMData => Model.CHRROMData; + + /// + /// CHR-NVRAM size in bytes + /// + public int CHRNVRAMSize + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return 0; + + byte shift = (byte)(header2.CHRRAMSize >> 4); + return shift > 0 ? 64 << shift : 0; + } + } + + /// + /// CHR-RAM size in bytes + /// + public int CHRRAMSize + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return 0; + + byte shift = (byte)(header2.CHRRAMSize & 0x0F); + return shift > 0 ? 64 << shift : 0; + } + } + + /// + /// CHR-ROM size in bytes + /// + public int CHRROMSize + { + get + { + // Missing header + if (Header is null) + return 0; + + int chrRomSize = Header.CHRROMSize * 8192; + if (Header is Header2 header2) + chrRomSize = ((header2.PRGCHRMSB >> 4) << 8) | chrRomSize; + + return chrRomSize; + } + } + + /// + /// Extended console type + /// + /// Only valid if is set + public ExtendedConsoleType ExtendedConsoleType + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return ExtendedConsoleType.RegularSystem; + +#if NET20 || NET35 + if ((Header.Flag7 & Flag7.ExtendedConsoleType) != 0) +#else + if (Header.Flag7.HasFlag(Flag7.ExtendedConsoleType)) +#endif + return (ExtendedConsoleType)(header2.ExtendedSystemType & 0x0F); + + // If flag is unset + return ExtendedConsoleType.RegularSystem; + } + } + + /// + /// Indicates if an alternative nametable layout is in use + /// + public bool HasAlternativeNametableLayout + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag6 & Flag6.AlternativeNametableLayout) != 0; +#else + return Header.Flag6.HasFlag(Flag6.AlternativeNametableLayout); +#endif + } + } + + /// + /// Indicates if battery-backed PRG RAM is present + /// + public bool HasBatteryBackedPRGRAM + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag6 & Flag6.BatteryBackedPRGRAMPresent) != 0; +#else + return Header.Flag6.HasFlag(Flag6.BatteryBackedPRGRAMPresent); +#endif + } + } + + /// + /// Indicates if trainer data is present + /// + public bool HasTrainer + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag6 & Flag6.TrainerPresent) != 0; +#else + return Header.Flag6.HasFlag(Flag6.TrainerPresent); +#endif + } + } + + /// + /// Indicates if the game is meant for an extended console type + /// + public bool IsExtendedConsole + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag7 & Flag7.ExtendedConsoleType) != 0; +#else + return Header.Flag7.HasFlag(Flag7.ExtendedConsoleType); +#endif + } + } + + /// + /// Indicates if the cart is using an NES 2.0 header + /// + public bool IsNES20 + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag7 & Flag7.NES20) != 0; +#else + return Header.Flag7.HasFlag(Flag7.NES20); +#endif + } + } + + /// + /// Indicates if the game is meant for PlayChoice-10 + /// + public bool IsPlayChoice10 + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag7 & Flag7.PlayChoice10) != 0 + && (Header.Flag7 & Flag7.VSUnisystem) == 0; +#else + return Header.Flag7.HasFlag(Flag7.PlayChoice10) + && !Header.Flag7.HasFlag(Flag7.VSUnisystem); +#endif + } + } + + /// + /// Indicates if the game is meant for a standard console + /// + public bool IsStandardConsole + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag7 & Flag7.PlayChoice10) == 0 + && (Header.Flag7 & Flag7.VSUnisystem) == 0; +#else + return !Header.Flag7.HasFlag(Flag7.PlayChoice10) + && !Header.Flag7.HasFlag(Flag7.VSUnisystem); +#endif + } + } + + /// + /// Indicates if the game is meant for Vs. Unisystem + /// + public bool IsVsUnisystem + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag7 & Flag7.PlayChoice10) == 0 + && (Header.Flag7 & Flag7.VSUnisystem) != 0; +#else + return !Header.Flag7.HasFlag(Flag7.PlayChoice10) + && Header.Flag7.HasFlag(Flag7.VSUnisystem); +#endif + } + } + + /// + /// Mapper number + /// + public int MapperNumber + { + get + { + // Missing header + if (Header is null) + return 0; + + int mapperNumber = (((byte)Header.Flag7 >> 4) << 4) | ((byte)Header.Flag6 >> 4); + if (Header is Header2 header2) + mapperNumber = ((header2.MapperMSBSubmapper & 0x0F) << 8) | mapperNumber; + + return mapperNumber; + } + } + + /// + /// PRG-NVRAM/EEPROM size in bytes + /// + public int PRGRAMEEPROMSize + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return 0; + + byte shift = (byte)(header2.PRGRAMEEPROMSize >> 4); + return shift > 0 ? 64 << shift : 0; + } + } + + /// + /// PRG-RAM size in bytes + /// + public int PRGRAMSize + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return 0; + + byte shift = (byte)(header2.PRGRAMEEPROMSize & 0x0F); + return shift > 0 ? 64 << shift : 0; + } + } + + /// + /// PRG-ROM size in bytes + /// + public int PRGROMSize + { + get + { + // Missing header + if (Header is null) + return 0; + + int prgRomSize = Header.PRGROMSize * 16384; + if (Header is Header2 header2) + prgRomSize = ((header2.PRGCHRMSB & 0x0F) << 8) | prgRomSize; + + return prgRomSize; + } + } /// public byte[] PRGROMData => Model.PRGROMData; - /// - public byte[] CHRROMData => Model.CHRROMData; - /// public byte[] PlayChoiceINSTROM => Model.PlayChoiceINSTROM; /// public byte[] PlayChoicePROM => Model.PlayChoicePROM; + /// + /// Submapper number + /// + public int SubmapperNumber + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return 0; + + return header2.MapperMSBSubmapper >> 4; + } + } + /// public byte[] Title => Model.Title; + /// + public byte[] Trainer => Model.Trainer; + + /// + /// Indicates if horizontal nametable arrangement is in use + /// + public bool UsesHorizontalNametableArrangement + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag6 & Flag6.NametableArrangementHorizontal) != 0; +#else + return Header.Flag6.HasFlag(Flag6.NametableArrangementHorizontal); +#endif + } + } + + /// + /// Indicates if vertical nametable arrangement is in use + /// + public bool UsesVerticalNametableArrangement + { + get + { + // Missing header + if (Header is null) + return false; + +#if NET20 || NET35 + return (Header.Flag6 & Flag6.NametableArrangementHorizontal) == 0; +#else + return !Header.Flag6.HasFlag(Flag6.NametableArrangementHorizontal); +#endif + } + } + + /// + /// Vs. Hardware Type + /// + /// Only valid if is set + public VsHardwareType VsHardwareType + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return VsHardwareType.VsUnisystem; + +#if NET20 || NET35 + else if ((Header.Flag7 & Flag7.VSUnisystem) != 0) +#else + else if (Header.Flag7.HasFlag(Flag7.VSUnisystem)) +#endif + return (VsHardwareType)(header2.ExtendedSystemType >> 4); + + // If flag is unset + return VsHardwareType.VsUnisystem; + } + } + + /// + /// Vs. System Type + /// + /// Only valid if is set + public VsSystemType VsSystemType + { + get + { + // Missing or invalid header + if (Header is null || Header is not Header2 header2) + return VsSystemType.AnyRP2C03RC2C03Variant; + +#if NET20 || NET35 + else if ((Header.Flag7 & Flag7.VSUnisystem) != 0) +#else + else if (Header.Flag7.HasFlag(Flag7.VSUnisystem)) +#endif + return (VsSystemType)(header2.ExtendedSystemType & 0x0F); + + // If flag is unset + return VsSystemType.AnyRP2C03RC2C03Variant; + } + } + #endregion #region Constructors