.nes file format: C++11/STL parsing library

File extension

nes

KS implementation details

License: WTFPL

This page hosts a formal specification of .nes file format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++11/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.nes", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    ines_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.header() // => get header

C++11/STL source code to parse .nes file format

ines.h

#pragma once

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <memory>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * \sa https://www.nesdev.org/wiki/INES Source
 */

class ines_t : public kaitai::kstruct {

public:
    class header_t;
    class playchoice10_t;

    ines_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = nullptr, ines_t* p__root = nullptr);

private:
    void _read();
    void _clean_up();

public:
    ~ines_t();

    class header_t : public kaitai::kstruct {

    public:
        class f6_t;
        class f7_t;
        class f9_t;
        class f10_t;

        header_t(kaitai::kstream* p__io, ines_t* p__parent = nullptr, ines_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

        /**
         * \sa https://www.nesdev.org/wiki/INES#Flags_6 Source
         */

        class f6_t : public kaitai::kstruct {

        public:

            enum mirroring_t {
                MIRRORING_HORIZONTAL = 0,
                MIRRORING_VERTICAL = 1
            };

            f6_t(kaitai::kstream* p__io, ines_t::header_t* p__parent = nullptr, ines_t* p__root = nullptr);

        private:
            void _read();
            void _clean_up();

        public:
            ~f6_t();

        private:
            uint64_t m_lower_mapper;
            bool m_four_screen;
            bool m_trainer;
            bool m_has_battery_ram;
            mirroring_t m_mirroring;
            ines_t* m__root;
            ines_t::header_t* m__parent;

        public:

            /**
             * Lower nibble of mapper number
             */
            uint64_t lower_mapper() const { return m_lower_mapper; }

            /**
             * Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM
             */
            bool four_screen() const { return m_four_screen; }

            /**
             * 512-byte trainer at $7000-$71FF (stored before PRG data)
             */
            bool trainer() const { return m_trainer; }

            /**
             * If on the cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory
             */
            bool has_battery_ram() const { return m_has_battery_ram; }

            /**
             * if 0, horizontal arrangement. if 1, vertical arrangement
             */
            mirroring_t mirroring() const { return m_mirroring; }
            ines_t* _root() const { return m__root; }
            ines_t::header_t* _parent() const { return m__parent; }
        };

        /**
         * \sa https://www.nesdev.org/wiki/INES#Flags_7 Source
         */

        class f7_t : public kaitai::kstruct {

        public:

            f7_t(kaitai::kstream* p__io, ines_t::header_t* p__parent = nullptr, ines_t* p__root = nullptr);

        private:
            void _read();
            void _clean_up();

        public:
            ~f7_t();

        private:
            uint64_t m_upper_mapper;
            uint64_t m_format;
            bool m_playchoice10;
            bool m_vs_unisystem;
            ines_t* m__root;
            ines_t::header_t* m__parent;

        public:

            /**
             * Upper nibble of mapper number
             */
            uint64_t upper_mapper() const { return m_upper_mapper; }

            /**
             * If equal to 2, flags 8-15 are in NES 2.0 format
             */
            uint64_t format() const { return m_format; }

            /**
             * Determines if it made for a Nintendo PlayChoice-10 or not
             */
            bool playchoice10() const { return m_playchoice10; }

            /**
             * Determines if it is made for a Nintendo VS Unisystem or not
             */
            bool vs_unisystem() const { return m_vs_unisystem; }
            ines_t* _root() const { return m__root; }
            ines_t::header_t* _parent() const { return m__parent; }
        };

        /**
         * \sa https://www.nesdev.org/wiki/INES#Flags_9 Source
         */

        class f9_t : public kaitai::kstruct {

        public:

            enum tv_system_t {
                TV_SYSTEM_NTSC = 0,
                TV_SYSTEM_PAL = 1
            };

            f9_t(kaitai::kstream* p__io, ines_t::header_t* p__parent = nullptr, ines_t* p__root = nullptr);

        private:
            void _read();
            void _clean_up();

        public:
            ~f9_t();

        private:
            uint64_t m_reserved;
            tv_system_t m_tv_system;
            ines_t* m__root;
            ines_t::header_t* m__parent;

        public:
            uint64_t reserved() const { return m_reserved; }

            /**
             * if 0, NTSC. If 1, PAL.
             */
            tv_system_t tv_system() const { return m_tv_system; }
            ines_t* _root() const { return m__root; }
            ines_t::header_t* _parent() const { return m__parent; }
        };

        /**
         * \sa https://www.nesdev.org/wiki/INES#Flags_10 Source
         */

        class f10_t : public kaitai::kstruct {

        public:

            enum tv_system_t {
                TV_SYSTEM_NTSC = 0,
                TV_SYSTEM_DUAL1 = 1,
                TV_SYSTEM_PAL = 2,
                TV_SYSTEM_DUAL2 = 3
            };

            f10_t(kaitai::kstream* p__io, ines_t::header_t* p__parent = nullptr, ines_t* p__root = nullptr);

        private:
            void _read();
            void _clean_up();

        public:
            ~f10_t();

        private:
            uint64_t m_reserved1;
            bool m_bus_conflict;
            bool m_prg_ram;
            uint64_t m_reserved2;
            tv_system_t m_tv_system;
            ines_t* m__root;
            ines_t::header_t* m__parent;

        public:
            uint64_t reserved1() const { return m_reserved1; }

            /**
             * If 0, no bus conflicts. If 1, bus conflicts.
             */
            bool bus_conflict() const { return m_bus_conflict; }

            /**
             * If 0, PRG ram is present. If 1, not present.
             */
            bool prg_ram() const { return m_prg_ram; }
            uint64_t reserved2() const { return m_reserved2; }

            /**
             * if 0, NTSC. If 2, PAL. If 1 or 3, dual compatible.
             */
            tv_system_t tv_system() const { return m_tv_system; }
            ines_t* _root() const { return m__root; }
            ines_t::header_t* _parent() const { return m__parent; }
        };

    private:
        bool f_mapper;
        int32_t m_mapper;

    public:

        /**
         * \sa https://www.nesdev.org/wiki/Mapper Source
         */
        int32_t mapper();

    private:
        std::string m_magic;
        uint8_t m_len_prg_rom;
        uint8_t m_len_chr_rom;
        std::unique_ptr<f6_t> m_f6;
        std::unique_ptr<f7_t> m_f7;
        uint8_t m_len_prg_ram;
        std::unique_ptr<f9_t> m_f9;
        std::unique_ptr<f10_t> m_f10;
        std::string m_reserved;
        ines_t* m__root;
        ines_t* m__parent;
        std::string m__raw_f6;
        std::unique_ptr<kaitai::kstream> m__io__raw_f6;
        std::string m__raw_f7;
        std::unique_ptr<kaitai::kstream> m__io__raw_f7;
        std::string m__raw_f9;
        std::unique_ptr<kaitai::kstream> m__io__raw_f9;
        std::string m__raw_f10;
        std::unique_ptr<kaitai::kstream> m__io__raw_f10;

    public:
        std::string magic() const { return m_magic; }

        /**
         * Size of PRG ROM in 16 KB units
         */
        uint8_t len_prg_rom() const { return m_len_prg_rom; }

        /**
         * Size of CHR ROM in 8 KB units (Value 0 means the board uses CHR RAM)
         */
        uint8_t len_chr_rom() const { return m_len_chr_rom; }
        f6_t* f6() const { return m_f6.get(); }
        f7_t* f7() const { return m_f7.get(); }

        /**
         * Size of PRG RAM in 8 KB units (Value 0 infers 8 KB for compatibility; see PRG RAM circuit on nesdev.com)
         */
        uint8_t len_prg_ram() const { return m_len_prg_ram; }
        f9_t* f9() const { return m_f9.get(); }

        /**
         * this one is unofficial
         */
        f10_t* f10() const { return m_f10.get(); }
        std::string reserved() const { return m_reserved; }
        ines_t* _root() const { return m__root; }
        ines_t* _parent() const { return m__parent; }
        std::string _raw_f6() const { return m__raw_f6; }
        kaitai::kstream* _io__raw_f6() const { return m__io__raw_f6.get(); }
        std::string _raw_f7() const { return m__raw_f7; }
        kaitai::kstream* _io__raw_f7() const { return m__io__raw_f7.get(); }
        std::string _raw_f9() const { return m__raw_f9; }
        kaitai::kstream* _io__raw_f9() const { return m__io__raw_f9.get(); }
        std::string _raw_f10() const { return m__raw_f10; }
        kaitai::kstream* _io__raw_f10() const { return m__io__raw_f10.get(); }
    };

    /**
     * \sa https://www.nesdev.org/wiki/PC10_ROM-Images Source
     */

    class playchoice10_t : public kaitai::kstruct {

    public:
        class prom_t;

        playchoice10_t(kaitai::kstream* p__io, ines_t* p__parent = nullptr, ines_t* p__root = nullptr);

    private:
        void _read();
        void _clean_up();

    public:
        ~playchoice10_t();

        class prom_t : public kaitai::kstruct {

        public:

            prom_t(kaitai::kstream* p__io, ines_t::playchoice10_t* p__parent = nullptr, ines_t* p__root = nullptr);

        private:
            void _read();
            void _clean_up();

        public:
            ~prom_t();

        private:
            std::string m_data;
            std::string m_counter_out;
            ines_t* m__root;
            ines_t::playchoice10_t* m__parent;

        public:
            std::string data() const { return m_data; }
            std::string counter_out() const { return m_counter_out; }
            ines_t* _root() const { return m__root; }
            ines_t::playchoice10_t* _parent() const { return m__parent; }
        };

    private:
        std::string m_inst_rom;
        std::unique_ptr<prom_t> m_prom;
        ines_t* m__root;
        ines_t* m__parent;

    public:
        std::string inst_rom() const { return m_inst_rom; }
        prom_t* prom() const { return m_prom.get(); }
        ines_t* _root() const { return m__root; }
        ines_t* _parent() const { return m__parent; }
    };

private:
    std::unique_ptr<header_t> m_header;
    std::string m_trainer;
    bool n_trainer;

public:
    bool _is_null_trainer() { trainer(); return n_trainer; };

private:
    std::string m_prg_rom;
    std::string m_chr_rom;
    std::unique_ptr<playchoice10_t> m_playchoice10;
    bool n_playchoice10;

public:
    bool _is_null_playchoice10() { playchoice10(); return n_playchoice10; };

private:
    std::string m_title;
    bool n_title;

public:
    bool _is_null_title() { title(); return n_title; };

private:
    ines_t* m__root;
    kaitai::kstruct* m__parent;
    std::string m__raw_header;
    std::unique_ptr<kaitai::kstream> m__io__raw_header;

public:
    header_t* header() const { return m_header.get(); }
    std::string trainer() const { return m_trainer; }
    std::string prg_rom() const { return m_prg_rom; }
    std::string chr_rom() const { return m_chr_rom; }
    playchoice10_t* playchoice10() const { return m_playchoice10.get(); }
    std::string title() const { return m_title; }
    ines_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
    std::string _raw_header() const { return m__raw_header; }
    kaitai::kstream* _io__raw_header() const { return m__io__raw_header.get(); }
};

ines.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "ines.h"
#include "kaitai/exceptions.h"

ines_t::ines_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_header = nullptr;
    m__io__raw_header = nullptr;
    m_playchoice10 = nullptr;
    _read();
}

void ines_t::_read() {
    m__raw_header = m__io->read_bytes(16);
    m__io__raw_header = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_header));
    m_header = std::unique_ptr<header_t>(new header_t(m__io__raw_header.get(), this, m__root));
    n_trainer = true;
    if (header()->f6()->trainer()) {
        n_trainer = false;
        m_trainer = m__io->read_bytes(512);
    }
    m_prg_rom = m__io->read_bytes((header()->len_prg_rom() * 16384));
    m_chr_rom = m__io->read_bytes((header()->len_chr_rom() * 8192));
    n_playchoice10 = true;
    if (header()->f7()->playchoice10()) {
        n_playchoice10 = false;
        m_playchoice10 = std::unique_ptr<playchoice10_t>(new playchoice10_t(m__io, this, m__root));
    }
    n_title = true;
    if (!(_io()->is_eof())) {
        n_title = false;
        m_title = kaitai::kstream::bytes_to_str(m__io->read_bytes_full(), std::string("ASCII"));
    }
}

ines_t::~ines_t() {
    _clean_up();
}

void ines_t::_clean_up() {
    if (!n_trainer) {
    }
    if (!n_playchoice10) {
    }
    if (!n_title) {
    }
}

ines_t::header_t::header_t(kaitai::kstream* p__io, ines_t* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_f6 = nullptr;
    m__io__raw_f6 = nullptr;
    m_f7 = nullptr;
    m__io__raw_f7 = nullptr;
    m_f9 = nullptr;
    m__io__raw_f9 = nullptr;
    m_f10 = nullptr;
    m__io__raw_f10 = nullptr;
    f_mapper = false;
    _read();
}

void ines_t::header_t::_read() {
    m_magic = m__io->read_bytes(4);
    if (!(magic() == std::string("\x4E\x45\x53\x1A", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x4E\x45\x53\x1A", 4), magic(), _io(), std::string("/types/header/seq/0"));
    }
    m_len_prg_rom = m__io->read_u1();
    m_len_chr_rom = m__io->read_u1();
    m__raw_f6 = m__io->read_bytes(1);
    m__io__raw_f6 = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_f6));
    m_f6 = std::unique_ptr<f6_t>(new f6_t(m__io__raw_f6.get(), this, m__root));
    m__raw_f7 = m__io->read_bytes(1);
    m__io__raw_f7 = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_f7));
    m_f7 = std::unique_ptr<f7_t>(new f7_t(m__io__raw_f7.get(), this, m__root));
    m_len_prg_ram = m__io->read_u1();
    m__raw_f9 = m__io->read_bytes(1);
    m__io__raw_f9 = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_f9));
    m_f9 = std::unique_ptr<f9_t>(new f9_t(m__io__raw_f9.get(), this, m__root));
    m__raw_f10 = m__io->read_bytes(1);
    m__io__raw_f10 = std::unique_ptr<kaitai::kstream>(new kaitai::kstream(m__raw_f10));
    m_f10 = std::unique_ptr<f10_t>(new f10_t(m__io__raw_f10.get(), this, m__root));
    m_reserved = m__io->read_bytes(5);
    if (!(reserved() == std::string("\x00\x00\x00\x00\x00", 5))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x00\x00\x00\x00\x00", 5), reserved(), _io(), std::string("/types/header/seq/8"));
    }
}

ines_t::header_t::~header_t() {
    _clean_up();
}

void ines_t::header_t::_clean_up() {
}

ines_t::header_t::f6_t::f6_t(kaitai::kstream* p__io, ines_t::header_t* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ines_t::header_t::f6_t::_read() {
    m_lower_mapper = m__io->read_bits_int_be(4);
    m_four_screen = m__io->read_bits_int_be(1);
    m_trainer = m__io->read_bits_int_be(1);
    m_has_battery_ram = m__io->read_bits_int_be(1);
    m_mirroring = static_cast<ines_t::header_t::f6_t::mirroring_t>(m__io->read_bits_int_be(1));
}

ines_t::header_t::f6_t::~f6_t() {
    _clean_up();
}

void ines_t::header_t::f6_t::_clean_up() {
}

ines_t::header_t::f7_t::f7_t(kaitai::kstream* p__io, ines_t::header_t* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ines_t::header_t::f7_t::_read() {
    m_upper_mapper = m__io->read_bits_int_be(4);
    m_format = m__io->read_bits_int_be(2);
    m_playchoice10 = m__io->read_bits_int_be(1);
    m_vs_unisystem = m__io->read_bits_int_be(1);
}

ines_t::header_t::f7_t::~f7_t() {
    _clean_up();
}

void ines_t::header_t::f7_t::_clean_up() {
}

ines_t::header_t::f9_t::f9_t(kaitai::kstream* p__io, ines_t::header_t* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ines_t::header_t::f9_t::_read() {
    m_reserved = m__io->read_bits_int_be(7);
    m_tv_system = static_cast<ines_t::header_t::f9_t::tv_system_t>(m__io->read_bits_int_be(1));
}

ines_t::header_t::f9_t::~f9_t() {
    _clean_up();
}

void ines_t::header_t::f9_t::_clean_up() {
}

ines_t::header_t::f10_t::f10_t(kaitai::kstream* p__io, ines_t::header_t* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ines_t::header_t::f10_t::_read() {
    m_reserved1 = m__io->read_bits_int_be(2);
    m_bus_conflict = m__io->read_bits_int_be(1);
    m_prg_ram = m__io->read_bits_int_be(1);
    m_reserved2 = m__io->read_bits_int_be(2);
    m_tv_system = static_cast<ines_t::header_t::f10_t::tv_system_t>(m__io->read_bits_int_be(2));
}

ines_t::header_t::f10_t::~f10_t() {
    _clean_up();
}

void ines_t::header_t::f10_t::_clean_up() {
}

int32_t ines_t::header_t::mapper() {
    if (f_mapper)
        return m_mapper;
    m_mapper = (f6()->lower_mapper() | (f7()->upper_mapper() << 4));
    f_mapper = true;
    return m_mapper;
}

ines_t::playchoice10_t::playchoice10_t(kaitai::kstream* p__io, ines_t* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_prom = nullptr;
    _read();
}

void ines_t::playchoice10_t::_read() {
    m_inst_rom = m__io->read_bytes(8192);
    m_prom = std::unique_ptr<prom_t>(new prom_t(m__io, this, m__root));
}

ines_t::playchoice10_t::~playchoice10_t() {
    _clean_up();
}

void ines_t::playchoice10_t::_clean_up() {
}

ines_t::playchoice10_t::prom_t::prom_t(kaitai::kstream* p__io, ines_t::playchoice10_t* p__parent, ines_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    _read();
}

void ines_t::playchoice10_t::prom_t::_read() {
    m_data = m__io->read_bytes(16);
    m_counter_out = m__io->read_bytes(16);
}

ines_t::playchoice10_t::prom_t::~prom_t() {
    _clean_up();
}

void ines_t::playchoice10_t::prom_t::_clean_up() {
}