NES 2.0 header for cc65

From NESdev Wiki
Jump to navigationJump to search

A cc65 compatible header file for generating a NES 2.0 with macros

The original ASM version for ca65 is available NES 2.0 header for ca65

/**
 * NES 2.0 header generator for cc65 (nes2header.h)
 * 
 * USAGE: Generates a header for the NES2 format with C macros
 *
 * NES2_HEADER(segmentname) - Creates a NES header in the segment provided by segment name.
 * The values can be configured using all the of the options described below.
 *
 * EXAMPLE:
 * 
 * #define NES2_MAPPER 4    // REQUIRED: Choose mapper number 4
 * #define NES2_PRG 65536   // REQUIRED: With 64kb PRG ROM
 * #define NES2_BRAM 8192   // And 8kb PRG RAM
 * #define NES2_MIRROR 'V'  // Vertical screen mirroring
 * #define NES2_TV 'N'      // For NTSC tvs
 * NES2_HEADER("INESHDR");  // and now build the header into the segment named `INESHDR`
 * 
 * DESCRIPTION: The following values can be defined to set the corresponding NES 2.0 header fields
 *
 * - NES2_MAPPER, NES2_SUBMAPPER
 *    Sets the mapper (board class) ID.  For example, MMC3 is usually
 *    mapper 4, but TLSROM is 118 and TQROM is 119.  Some mappers have
 *    variants.
 *    NO DEFAULT (Required)
 *    Example: (sets the mapper to MMC3 with submapper variant 1 used by StarTropics)
 *     #define NES2_MAPPER 4
 *     #define NES2_SUBMAPPER 1
 * 
 * - NES2_PRG
 *    Sets the PRG ROM size to sz bytes. Must be multiple of 16384;
 *    should be a power of 2.
 *    NO DEFAULT (Required)
 *    Example: #define NES2_PRG 131072
 * 
 * - NES2_CHR
 *    Sets the CHR ROM size to sz bytes. Must be multiple of 8192;
 *    should be a power of 2.
 *    Default value: 0
 *    Example: #define NES2_CHR 32768
 * 
 * - NES2_WRAM
 *    Sets the (not battery-backed) work RAM size in bytes.
 *    Default is 0.
 *    Example: #define NES2_WRAM 8192
 * 
 * - NES2_BRAM
 *    Sets the battery-backed work RAM size in bytes.
 *    Default is 0.
 *    Example: #define NES2_BRAM 8192
 * 
 * - NES2_CHRBRAM
 *    Sets the battery-backed CHR RAM size in bytes.
 *    Default is 0.
 *    Example: #define NES2_CHRBRAM 8192
 * 
 * - NES2_MIRROR
 *    Sets the mirroring to one of these values:
 *    'H' (horizontal mirroring, vertical arrangement)
 *    'V' (vertical mirroring, horizontal arrangement)
 *    '4' (four-screen VRAM)
 *    '8' (four-screen and vertical bits on, primarily for mapper 218)
 *    Default is 'H'
 *    Example: #define NES2_MIRROR 'V'
 * 
 * - NES2_TV
 *    Sets the ROM's intended TV system:
 *    'N' for NTSC NES/FC/PC10
 *    'P' for PAL NES
 *    '1' for dual compatible, preferring NTSC
 *    '2' for dual compatible, preferring PAL NES
 *    Default is 'N'
 *    Example: #define NES2_TV '1'
 * 
 * Original ca65 macro Copyright 2016 Damian Yerrick
 * cc65 macro Copyright 2022 James Rowe
 * Copying and distribution of this file, with or without
 * modification, are permitted in any medium without royalty provided
 * the copyright notice and this notice are preserved in all source
 * code copies.  This file is offered as-is, without any warranty.
 * 
 */

#ifndef NES2HEADER_P_H
#define NES2HEADER_P_H

#ifndef NES2_SUBMAPPER
  #define NES2_SUBMAPPER 0x0
#endif //NES2_SUBMAPPER

// Define the bytes for the mapper fields
#define _NES2_MAPPER6 (((NES2_MAPPER) & 0x0F) << 4)
#define _NES2_MAPPER7 ((NES2_MAPPER) & 0xF0)
#define _NES2_MAPPER8 (((NES2_MAPPER) >> 8) | ((NES2_SUBMAPPER) << 4))

#define _NES2_PRG_SIZE (((NES2_PRG) / 16384) & 0xFF)
#define _NES2_PRG_SIZE_HI ((((NES2_PRG) / 16384) >> 8) & 0xFF)

// Define the defaults for the constants
#ifdef NES2_CHR
  #define _NES2_CHR_SIZE (((NES2_CHR) / 8192) & 0xFF)
  #define _NES2_CHR_SIZE_HI ((((NES2_CHR) / 8192) >> 8) & 0xFF)
#else
  #define _NES2_CHR_SIZE 0
  #define _NES2_CHR_SIZE_HI 0
#endif //NES2_CHR_SIZE

// Helper function: Computes ceil(log2(sz / 64)) Used for NES2 RAM sizes.
#define _NES2_ERROR -1
#define _NES2_LOGSIZE(sz) \
  (((sz) < 1L)       ? 0  : \
   ((sz) <= 128L)    ? 1  : \
   ((sz) <= 256L)    ? 2  : \
   ((sz) <= 512L)    ? 3  : \
   ((sz) <= 1024L)   ? 4  : \
   ((sz) <= 2048L)   ? 5  : \
   ((sz) <= 4096L)   ? 6  : \
   ((sz) <= 8192L)   ? 7  : \
   ((sz) <= 16384L)  ? 8  : \
   ((sz) <= 32768L)  ? 9  : \
   ((sz) <= 65536L)  ? 10 : \
   ((sz) <= 131072L) ? 11 : \
   ((sz) <= 262144L) ? 12 : \
   ((sz) <= 524288L) ? 13 : \
   ((sz) <= 1048576L)? 14 : _NES2_ERROR)

#define _NES2_MIRROR_HELPER(x) \
    (((x) == 'h' || (x) == 'H') ? 0 : \
     ((x) == 'v' || (x) == 'V') ? 1 : \
     ((x) == '4') ? 8 : \
     ((x) == '8') ? 9 : _NES2_ERROR)

#ifdef NES2_MIRROR
  #define _NES2_MIRROR_OUT (_NES2_MIRROR_HELPER(NES2_MIRROR))
#else
  #define _NES2_MIRROR_OUT (_NES2_MIRROR_HELPER('H'))
#endif //NES2_MIRROR

#ifdef NES2_WRAM
  #define _NES2_WRAM_SIZE (_NES2_LOGSIZE(NES2_WRAM))
#else
  #define _NES2_WRAM_SIZE (_NES2_LOGSIZE(0))
#endif //NES2_WRAM

#ifdef NES2_BRAM
  #define _NES2_BRAM_SIZE (_NES2_LOGSIZE(NES2_BRAM))
#else
  #define _NES2_BRAM_SIZE (_NES2_LOGSIZE(0))
#endif //NES2_BRAM

#ifdef NES2_CHRBRAM
  #define _NES2_CHRBRAM_SIZE (_NES2_LOGSIZE(NES2_CHRBRAM))
#else
  #define _NES2_CHRBRAM_SIZE (_NES2_LOGSIZE(0))
#endif //NES2_CHRBRAM

#ifdef NES2_CHRRAM
  #define _NES2_CHRRAM_SIZE (_NES2_LOGSIZE(NES2_CHRRAM))
#else
  // CHRRAM size depends on if the mapper has CHRBRAM or CHR already defined 
#define _NES2_CHRRAM_SIZE \
  ((((_NES2_CHR_SIZE) + _NES2_CHR_SIZE_HI + (_NES2_CHRBRAM_SIZE)) > 0) ? 0 : _NES2_LOGSIZE(32768L))
#endif

// Battery bit is set if we have either BRAM or CHRBRAM
#define _NES2_BATTERY_BIT \
  ((((_NES2_BRAM_SIZE) + (_NES2_CHRBRAM_SIZE)) > 0) ? 0x02 : 0x0)

#define _NES2_TV_HELPER(x) \
    (((x) == 'n' || (x) == 'N') ? 0 : \
     ((x) == 'p' || (x) == 'P') ? 1 : \
     ((x) == '1') ? 2 : \
     ((x) == '2') ? 3 : -1)

#ifdef NES2_TV
  #define _NES2_TV_SYSTEM _NES2_TV_HELPER(NES2_TV)
#else
  #define _NES2_TV_SYSTEM _NES2_TV_HELPER('N')
#endif //NES2_TV

#define _NES2_STRINGIFY(a) #a

#define NES2_HEADER(segment) \
  _Pragma( _NES2_STRINGIFY( rodata-name ## ( ## push, ## segment ## ) ## ) ); \
  _Static_assert (_NES2_WRAM_SIZE != _NES2_ERROR, "WRAM size must be 0 to 1048576"); \
  _Static_assert (_NES2_BRAM_SIZE != _NES2_ERROR, "BRAM size must be 0 to 1048576"); \
  _Static_assert (_NES2_CHRBRAM_SIZE != _NES2_ERROR, "CHRBRAM size must be 0 to 1048576"); \
  _Static_assert (_NES2_MIRROR_OUT != _NES2_ERROR, "Mirroring mode must be 'H', 'V', '4', or '8'"); \
  _Static_assert (_NES2_TV_SYSTEM != _NES2_ERROR, "TV system must be 'N', 'P', '1', or '2'"); \
  const unsigned char nes2header[16] = {\
    'N', 'E', 'S', 0x1A,\
    _NES2_PRG_SIZE, _NES2_CHR_SIZE,\
    _NES2_MAPPER6 | _NES2_MIRROR_OUT | _NES2_BATTERY_BIT, \
    _NES2_MAPPER7 | 0x08, \
    _NES2_MAPPER8, \
    (_NES2_CHR_SIZE_HI << 4) | _NES2_PRG_SIZE_HI, \
    (_NES2_BRAM_SIZE << 4) | _NES2_WRAM_SIZE, \
    (_NES2_CHRBRAM_SIZE << 4) | _NES2_CHRRAM_SIZE, \
    _NES2_TV_SYSTEM, 0, 0, 0 \
  };\
  _Pragma("rodata-name(pop)");

#endif //NES2HEADER_P_H


Linker script requirement

Your linker configuration file will need to have a segment for the header in the first ROM memory area. This segment can have any name, but should have 16 bytes of size.

MEMORY {
  HEADER: start = 0, size = $0010, type = ro, file = %O, fill=yes, fillval=$00;
  # Other memory area definitions appropriate for your board
}
SEGMENTS {
  INESHDR:  load = HEADER, type = ro, align = $10;
  # Other segment definitions appropriate for your board
}


Examples

A CNROM board with horizontal mirroring (V pad) for NTSC systems:

#include "nes2header.h"
#define NES2_MAPPER 3
#define NES2_PRG 32768
#define NES2_CHR 32768
#define NES2_MIRROR 'H'
#define NES2_TV 'N'
NES2_HEADER("INESHDR");

An SLROM board with 128 KiB PRG ROM, 128 KiB CHR ROM, 8 KiB battery-backed WRAM, and PAL-preferred but dual-compatible program:

#include "nes2header.inc"
#define NES2_MAPPER 1
#define NES2_PRG 131072
#define NES2_CHR 131072
#define NES2_BRAM 8192
#define NES2_TV 'P','N'
NES2_HEADER("INESHDR");

An MMC3 board with 512 KiB PRG ROM, 32 KiB CHR RAM, and NTSC-only program:

#include "nes2header.inc"
#define NES2_MAPPER 4
#define NES2_PRG 524288
#define NES2_CHRRAM 32768
#define NES2_TV 'N'
NES2_HEADER("INESHDR");