Skip to content

Commit

Permalink
Merge pull request #47 from Kautenja/clone_restore_finish
Browse files Browse the repository at this point in the history
Clone restore finish
  • Loading branch information
Kautenja authored Jan 6, 2019
2 parents 44dc71b + 7d0ea46 commit d2de6bf
Show file tree
Hide file tree
Showing 32 changed files with 565 additions and 204 deletions.
15 changes: 12 additions & 3 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
Copyright (c) 2018 Christian Kauten
MIT License

This project is provided for educational purposes only. It is not affiliated
with and has not been approved by Nintendo.
Copyright (c) 2019 Christian Kauten

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Expand Down
7 changes: 7 additions & 0 deletions nes_py/cpp/cartridge.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Program: nes-py
// File: cartridge.cpp
// Description: This class houses the logic and data for an NES cartridge
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#include "cartridge.hpp"
#include "log.hpp"
#include <fstream>
Expand Down
7 changes: 7 additions & 0 deletions nes_py/cpp/controller.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Program: nes-py
// File: controller.cpp
// Description: This class houses the logic and data for an NES controller
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#include "controller.hpp"

Controller::Controller() {
Expand Down
7 changes: 7 additions & 0 deletions nes_py/cpp/cpu.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Program: nes-py
// File: cpu.cpp
// Description: This class houses the logic and data for the NES CPU
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#include "cpu.hpp"
#include "cpu_opcodes.hpp"
#include "log.hpp"
Expand Down
86 changes: 26 additions & 60 deletions nes_py/cpp/emulator.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Program: nes-py
// File: emulator.cpp
// Description: This class houses the logic and data for an NES emulator
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#include "emulator.hpp"
#include "log.hpp"

Expand Down Expand Up @@ -28,86 +35,45 @@ Emulator::Emulator(std::string path) : rom_path(path), cpu(), ppu() {
}
// set the interrupt callback for the PPU
ppu.setInterruptCallback([&](){ cpu.interrupt(bus, CPU::NMI); });
}

Emulator::Emulator(Emulator* emulator) {
rom_path = emulator->rom_path;
controller1 = emulator->controller1;
controller2 = emulator->controller2;
cpu = emulator->cpu;
ppu = emulator->ppu;
bus = emulator->bus;
picture_bus = emulator->picture_bus;
cartridge = emulator->cartridge;
// mapper = emulator->mapper;
// bus.setMapper(mapper);
// picture_bus.setMapper(mapper);
// raise an error if IO callback setup fails
if (
!bus.set_read_callback(PPUSTATUS, [&](void) {return ppu.getStatus();}) ||
!bus.set_read_callback(PPUDATA, [&](void) {return ppu.getData(picture_bus);}) ||
!bus.set_read_callback(JOY1, [&](void) {return controller1.read();}) ||
!bus.set_read_callback(JOY2, [&](void) {return controller2.read();}) ||
!bus.set_read_callback(OAMDATA, [&](void) {return ppu.getOAMData();})
) {
LOG(Error) << "Critical error: Failed to set I/O callbacks" << std::endl;
}
// raise an error if IO callback setup fails
if (
!bus.set_write_callback(PPUCTRL, [&](uint8_t b) {ppu.control(b);}) ||
!bus.set_write_callback(PPUMASK, [&](uint8_t b) {ppu.setMask(b);}) ||
!bus.set_write_callback(OAMADDR, [&](uint8_t b) {ppu.setOAMAddress(b);}) ||
!bus.set_write_callback(PPUADDR, [&](uint8_t b) {ppu.setDataAddress(b);}) ||
!bus.set_write_callback(PPUSCROL, [&](uint8_t b) {ppu.setScroll(b);}) ||
!bus.set_write_callback(PPUDATA, [&](uint8_t b) {ppu.setData(picture_bus, b);}) ||
!bus.set_write_callback(OAMDMA, [&](uint8_t b) {DMA(b);}) ||
!bus.set_write_callback(JOY1, [&](uint8_t b) {controller1.strobe(b); controller2.strobe(b);}) ||
!bus.set_write_callback(OAMDATA, [&](uint8_t b) {ppu.setOAMData(b);})
) {
LOG(Error) << "Critical error: Failed to set I/O callbacks" << std::endl;
}
// set the interrupt callback for the PPU
ppu.setInterruptCallback([&](){ cpu.interrupt(bus, CPU::NMI); });
}

void Emulator::reset() {
// load the ROM from disk and return if the operation fails
if (!cartridge.loadFromFile(rom_path))
return;
// create the mapper based on the mapper ID in the iNES header of the ROM
mapper = Mapper::createMapper(
static_cast<Mapper::Type>(cartridge.getMapper()),
mapper = Mapper::create(
cartridge,
[&](){ picture_bus.update_mirroring(); }
);
// if the mapper is a nullptr, the mapper ID is not supported at this time
if (!mapper) {
LOG(Error) << "Creating Mapper failed. Probably unsupported." << std::endl;
return;
}
// if setting the mapper on the buses fails, return
if (!bus.set_mapper(mapper) || !picture_bus.set_mapper(mapper))
return;
// reset the CPU and PPU
cpu.reset(bus);
ppu.reset();
bus.assign_mapper(mapper);
picture_bus.assign_mapper(mapper);
}

void Emulator::DMA(uint8_t page) {
// skip the DMA cycles on the CPU
cpu.skipDMACycles();
// get the pointer to the next page from the bus
auto page_pointer = bus.get_page_pointer(page);
// do the DMA page change on the PPU
ppu.doDMA(page_pointer);
ppu.doDMA(bus.get_page_pointer(page));
}

void Emulator::step(unsigned char action) {
// write the controller state to player 1
controller1.write_buttons(action);
// approximate a frame
for (int i = 0; i < 29781; i++) {
for (int i = 0; i < STEPS_PER_FRAME; i++) {
ppu.step(picture_bus);
cpu.step(bus);
}
}

void Emulator::backup() {
backup_bus = bus;
backup_picture_bus = picture_bus;
backup_cpu = cpu;
backup_ppu = ppu;
}

void Emulator::restore() {
bus = backup_bus;
picture_bus = backup_picture_bus;
cpu = backup_cpu;
ppu = backup_ppu;
}
8 changes: 8 additions & 0 deletions nes_py/cpp/include/cartridge.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// Program: nes-py
// File: cartridge.hpp
// Description: This class houses the logic and data for an NES cartridge
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#ifndef CARTRIDGE_H
#define CARTRIDGE_H

#include <vector>
#include <string>
#include <cstdint>
Expand Down
8 changes: 8 additions & 0 deletions nes_py/cpp/include/controller.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// Program: nes-py
// File: controller.hpp
// Description: This class houses the logic and data for an NES controller
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#ifndef CONTROLLER_H
#define CONTROLLER_H

#include <stdint.h>
#include <vector>

Expand Down
8 changes: 8 additions & 0 deletions nes_py/cpp/include/cpu.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// Program: nes-py
// File: cpu.hpp
// Description: This class houses the logic and data for the NES CPU
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#ifndef CPU_H
#define CPU_H

#include "main_bus.hpp"

/// An MOS6502 for an NES emulation
Expand Down
7 changes: 7 additions & 0 deletions nes_py/cpp/include/cpu_opcodes.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// Program: nes-py
// File: cpu_opcodes.hpp
// Description: This file defines relevant CPU opcodes
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#ifndef CPUOPCODES_H_INCLUDED
#define CPUOPCODES_H_INCLUDED

Expand Down
61 changes: 37 additions & 24 deletions nes_py/cpp/include/emulator.hpp
Original file line number Diff line number Diff line change
@@ -1,63 +1,70 @@
// Program: nes-py
// File: emulator.hpp
// Description: This class houses the logic and data for an NES emulator
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#ifndef EMULATOR_H
#define EMULATOR_H
#include <string>
#include <stdint.h>

#include "cartridge.hpp"
#include "controller.hpp"
#include "cpu.hpp"
#include "ppu.hpp"
#include "main_bus.hpp"
#include "mapper.hpp"
#include "picture_bus.hpp"
#include <stdint.h>
#include <string>

/// The width of the NES screen in pixels
const int NESVideoWidth = ScanlineVisibleDots;
const int NES_VIDEO_WIDTH = ScanlineVisibleDots;
/// The height of the NES screen in pixels
const int NESVideoHeight = VisibleScanlines;
const int NES_VIDEO_HEIGHT = VisibleScanlines;
/// The number of cycles in approximately 1 frame
const int STEPS_PER_FRAME = 29781;

/// An NES Emulator and OpenAI Gym interface
class Emulator {

private:
/// the path to the ROM for this environment
std::string rom_path;
/// the virtual cartridge with ROM and mapper data
Cartridge cartridge;
/// a pointer to the mapper on the cartridge
Mapper* mapper;
/// the 2 controllers on the emulator
Controller controller1, controller2;

/// the main data bus of the emulator
MainBus bus;

/// the picture bus from the PPU of the emulator
PictureBus picture_bus;

/// The emulator's CPU
CPU cpu;

/// the emulators' PPU
PPU ppu;

/// the virtual cartridge with ROM and mapper data
Cartridge cartridge;

/// a pointer to the mapper on the cartridge
Mapper* mapper;

/// the 2 controllers on the emulator
Controller controller1, controller2;
/// the main data bus of the emulator
MainBus backup_bus;
/// the picture bus from the PPU of the emulator
PictureBus backup_picture_bus;
/// The emulator's CPU
CPU backup_cpu;
/// the emulators' PPU
PPU backup_ppu;

/// Skip DMA cycle and perform a DMA copy.
void DMA(uint8_t page);

public:
/// Initialize a new emulator with a path to a ROM file.
///
/// @param rom_path the path to the ROM for the emulator to run
///
Emulator(std::string rom_path);

/// Create a new emulator as a copy of another emulator state
///
/// @param emulator the emulator to copy the state from
/// @param path the path to the ROM for the emulator to run
///
Emulator(Emulator* emulator);
Emulator(std::string path);

/// Return a 32-bit pointer to the screen buffer's first address.
///
Expand All @@ -72,7 +79,7 @@ class Emulator {
uint8_t* get_memory_buffer() { return bus.get_memory_buffer(); };

/// Load the ROM into the NES.
void reset();
void reset() { cpu.reset(bus); ppu.reset(); };

/// Perform a discrete "step" of the NES by rendering 1 frame.
///
Expand All @@ -91,6 +98,12 @@ class Emulator {
///
void step(unsigned char action);

/// Create a backup state on the emulator
void backup();

/// Restore the backup state on the emulator
void restore();

};

#endif // EMULATOR_H
8 changes: 8 additions & 0 deletions nes_py/cpp/include/log.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// Program: nes-py
// File: log.hpp
// Description: Logging utilities for the project
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#ifndef LOG_H
#define LOG_H

#include <iostream>
#include <string>
#include <fstream>
Expand Down
12 changes: 11 additions & 1 deletion nes_py/cpp/include/main_bus.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// Program: nes-py
// File: main_bus.hpp
// Description: This class houses the main bus data for the NES
//
// Copyright (c) 2019 Christian Kauten. All rights reserved.
//

#ifndef MAIN_BUS_H
#define MAIN_BUS_H

#include <vector>
#include <map>
#include <functional>
Expand Down Expand Up @@ -64,7 +72,9 @@ class MainBus {
///
/// @param mapper the new mapper pointer for the bus to use
///
bool set_mapper(Mapper* mapper);
bool assign_mapper(Mapper* mapper);

void set_mapper(Mapper *mapper) { m_mapper = mapper; };

/// Set a callback for when writes occur.
bool set_write_callback(IORegisters reg, std::function<void(uint8_t)> callback);
Expand Down
Loading

0 comments on commit d2de6bf

Please sign in to comment.