bsnes107r1

This commit is contained in:
DerKoun 2019-05-26 20:37:46 +02:00
parent ee164c449b
commit 7d436cad7a
1851 changed files with 188328 additions and 0 deletions

BIN
firmware/cx4.data.rom Normal file

Binary file not shown.

BIN
firmware/sgb1.boot.rom Normal file

Binary file not shown.

BIN
firmware/sgb2.boot.rom Normal file

Binary file not shown.

99
higan/GNUmakefile Normal file
View file

@ -0,0 +1,99 @@
target := bsnes
binary := application
build := performance
openmp := true
flags += -I. -I..
nall.path := ../nall
include $(nall.path)/GNUmakefile
ifeq ($(platform),windows)
ifeq ($(binary),application)
link += -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
link += -Wl,-enable-auto-import
link += -Wl,-enable-runtime-pseudo-reloc
else ifeq ($(binary),library)
link += -shared
endif
else ifeq ($(platform),macos)
ifeq ($(binary),application)
else ifeq ($(binary),library)
flags += -fPIC
link += -dynamiclib
endif
else ifneq ($(filter $(platform),linux bsd),)
ifeq ($(binary),application)
flags += -march=native
link += -Wl,-export-dynamic
link += -lX11 -lXext
else ifeq ($(binary),library)
flags += -fPIC
link += -shared
endif
else
$(error "unsupported platform")
endif
objects := libco emulator
obj/libco.o: ../libco/libco.c
obj/emulator.o: emulator/emulator.cpp
ifeq ($(target),higan)
cores := fc sfc ms md pce msx gb gba ws ngp
endif
ifeq ($(target),bsnes)
cores := sfc gb
endif
ifneq ($(filter $(cores),fc),)
include fc/GNUmakefile
endif
ifneq ($(filter $(cores),sfc),)
include sfc/GNUmakefile
endif
ifneq ($(filter $(cores),ms),)
include ms/GNUmakefile
endif
ifneq ($(filter $(cores),md),)
include md/GNUmakefile
endif
ifneq ($(filter $(cores),pce),)
include pce/GNUmakefile
endif
ifneq ($(filter $(cores),msx),)
include msx/GNUmakefile
endif
ifneq ($(filter $(cores),gb),)
include gb/GNUmakefile
endif
ifneq ($(filter $(cores),gba),)
include gba/GNUmakefile
endif
ifneq ($(filter $(cores),ws),)
include ws/GNUmakefile
endif
ifneq ($(filter $(cores),ngp),)
include ngp/GNUmakefile
endif
include processor/GNUmakefile
flags += $(foreach c,$(call strupper,$(cores)),-DCORE_$c)
ui := target-$(target)
include $(ui)/GNUmakefile
-include obj/*.d
clean:
$(call delete,obj/*)
$(call delete,out/*)

View file

@ -0,0 +1,71 @@
namespace Emulator {
#include "stream.cpp"
Audio audio;
Audio::~Audio() {
reset(nullptr);
}
auto Audio::reset(Interface* interface) -> void {
this->interface = interface;
streams.reset();
channels = 0;
}
auto Audio::setFrequency(double frequency) -> void {
this->frequency = frequency;
for(auto& stream : streams) {
stream->setFrequency(stream->inputFrequency, frequency);
}
}
auto Audio::setVolume(double volume) -> void {
this->volume = volume;
}
auto Audio::setBalance(double balance) -> void {
this->balance = balance;
}
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
this->channels = max(this->channels, channels);
shared_pointer<Stream> stream = new Stream;
stream->reset(channels, frequency, this->frequency);
streams.append(stream);
return stream;
}
auto Audio::process() -> void {
while(streams) {
for(auto& stream : streams) {
if(!stream->pending()) return;
}
double samples[channels];
for(auto& sample : samples) sample = 0.0;
for(auto& stream : streams) {
double buffer[channels];
uint length = stream->read(buffer), offset = 0;
for(auto& sample : samples) {
sample += buffer[offset];
if(++offset >= length) offset = 0;
}
}
for(auto c : range(channels)) {
samples[c] = max(-1.0, min(+1.0, samples[c] * volume));
}
if(channels == 2) {
if(balance < 0.0) samples[1] *= 1.0 + balance;
if(balance > 0.0) samples[0] *= 1.0 - balance;
}
platform->audioFrame(samples, channels);
}
}
}

View file

@ -0,0 +1,83 @@
#pragma once
#include <nall/dsp/iir/dc-removal.hpp>
#include <nall/dsp/iir/one-pole.hpp>
#include <nall/dsp/iir/biquad.hpp>
#include <nall/dsp/resampler/cubic.hpp>
namespace Emulator {
struct Interface;
struct Audio;
struct Filter;
struct Stream;
struct Audio {
~Audio();
auto reset(Interface* interface) -> void;
auto setFrequency(double frequency) -> void;
auto setVolume(double volume) -> void;
auto setBalance(double balance) -> void;
auto createStream(uint channels, double frequency) -> shared_pointer<Stream>;
private:
auto process() -> void;
Interface* interface = nullptr;
vector<shared_pointer<Stream>> streams;
uint channels = 0;
double frequency = 48000.0;
double volume = 1.0;
double balance = 0.0;
friend class Stream;
};
struct Filter {
enum class Mode : uint { DCRemoval, OnePole, Biquad } mode;
enum class Type : uint { None, LowPass, HighPass } type;
enum class Order : uint { None, First, Second } order;
DSP::IIR::DCRemoval dcRemoval;
DSP::IIR::OnePole onePole;
DSP::IIR::Biquad biquad;
};
struct Stream {
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void;
auto addDCRemovalFilter() -> void;
auto addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
auto addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
auto pending() const -> bool;
auto read(double samples[]) -> uint;
auto write(const double samples[]) -> void;
template<typename... P> auto sample(P&&... p) -> void {
double samples[sizeof...(P)] = {forward<P>(p)...};
write(samples);
}
private:
struct Channel {
vector<Filter> filters;
vector<DSP::IIR::Biquad> nyquist;
DSP::Resampler::Cubic resampler;
};
vector<Channel> channels;
double inputFrequency;
double outputFrequency;
friend class Audio;
};
extern Audio audio;
}

View file

@ -0,0 +1,106 @@
auto Stream::reset(uint channelCount, double inputFrequency, double outputFrequency) -> void {
channels.reset();
channels.resize(channelCount);
for(auto& channel : channels) {
channel.filters.reset();
}
setFrequency(inputFrequency, outputFrequency);
}
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
this->inputFrequency = inputFrequency;
if(outputFrequency) this->outputFrequency = outputFrequency();
for(auto& channel : channels) {
channel.nyquist.reset();
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
}
if(this->inputFrequency >= this->outputFrequency * 2) {
//add a low-pass filter to prevent aliasing during resampling
double cutoffFrequency = min(25000.0, this->outputFrequency / 2.0 - 2000.0);
for(auto& channel : channels) {
uint passes = 3;
for(uint pass : range(passes)) {
DSP::IIR::Biquad filter;
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, this->inputFrequency, q);
channel.nyquist.append(filter);
}
}
}
}
auto Stream::addDCRemovalFilter() -> void {
return; //todo: test to ensure this is desirable before enabling
for(auto& channel : channels) {
Filter filter{Filter::Mode::DCRemoval, Filter::Type::None, Filter::Order::None};
channel.filters.append(filter);
}
}
auto Stream::addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
for(auto& channel : channels) {
for(uint pass : range(passes)) {
if(order == Filter::Order::First) {
Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency);
channel.filters.append(filter);
}
if(order == Filter::Order::Second) {
Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
channel.filters.append(filter);
}
}
}
}
auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
for(auto& channel : channels) {
for(uint pass : range(passes)) {
if(order == Filter::Order::First) {
Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First};
filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, inputFrequency);
channel.filters.append(filter);
}
if(order == Filter::Order::Second) {
Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second};
double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
channel.filters.append(filter);
}
}
}
}
auto Stream::pending() const -> bool {
return channels && channels[0].resampler.pending();
}
auto Stream::read(double samples[]) -> uint {
for(uint c : range(channels.size())) samples[c] = channels[c].resampler.read();
return channels.size();
}
auto Stream::write(const double samples[]) -> void {
for(auto c : range(channels.size())) {
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
for(auto& filter : channels[c].filters) {
switch(filter.mode) {
case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break;
case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break;
case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break;
}
}
for(auto& filter : channels[c].nyquist) {
sample = filter.process(sample);
}
channels[c].resampler.write(sample);
}
audio.process();
}

48
higan/emulator/cheat.hpp Normal file
View file

@ -0,0 +1,48 @@
#pragma once
namespace Emulator {
struct Cheat {
struct Code {
uint addr;
uint data;
maybe<uint> comp;
};
explicit operator bool() const {
return codes.size() > 0;
}
auto reset() -> void {
codes.reset();
}
auto append(uint addr, uint data, maybe<uint> comp = {}) -> void {
codes.append({addr, data, comp});
}
auto assign(const vector<string>& list) -> void {
reset();
for(auto& entry : list) {
for(auto code : entry.split("+")) {
auto part = code.transform("=?", "//").split("/");
if(part.size() == 2) append(part[0].hex(), part[1].hex());
if(part.size() == 3) append(part[0].hex(), part[2].hex(), part[1].hex());
}
}
}
auto find(uint addr, uint comp) -> maybe<uint> {
for(auto& code : codes) {
if(code.addr == addr && (!code.comp || code.comp() == comp)) {
return code.data;
}
}
return nothing;
}
private:
vector<Code> codes;
};
}

View file

@ -0,0 +1,11 @@
#include <emulator/emulator.hpp>
#include <emulator/audio/audio.cpp>
#include <emulator/video/video.cpp>
#include <emulator/resource/resource.cpp>
namespace Emulator {
Platform* platform = nullptr;
}

View file

@ -0,0 +1,60 @@
#pragma once
#include <nall/platform.hpp>
#include <nall/adaptive-array.hpp>
#include <nall/any.hpp>
#include <nall/bit-field.hpp>
#include <nall/chrono.hpp>
#include <nall/dl.hpp>
#include <nall/endian.hpp>
#include <nall/image.hpp>
#include <nall/literals.hpp>
#include <nall/random.hpp>
#include <nall/serializer.hpp>
#include <nall/shared-pointer.hpp>
#include <nall/string.hpp>
#include <nall/traits.hpp>
#include <nall/unique-pointer.hpp>
#include <nall/vector.hpp>
#include <nall/vfs.hpp>
#include <nall/hash/crc32.hpp>
#include <nall/hash/sha256.hpp>
using namespace nall;
#include <libco/libco.h>
#include <emulator/types.hpp>
#include <emulator/memory/readable.hpp>
#include <emulator/memory/writable.hpp>
#include <emulator/audio/audio.hpp>
#include <emulator/video/video.hpp>
#include <emulator/resource/resource.hpp>
namespace Emulator {
static const string Name = "higan";
static const string Version = "107.1";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org/";
//incremented only when serialization format changes
static const string SerializerVersion = "107";
namespace Constants {
namespace Colorburst {
static constexpr double NTSC = 315.0 / 88.0 * 1'000'000.0;
static constexpr double PAL = 283.75 * 15'625.0 + 25.0;
}
}
//nall/vfs shorthand constants for open(), load()
namespace File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
}
#include "platform.hpp"
#include "interface.hpp"
#include "game.hpp"

110
higan/emulator/game.hpp Normal file
View file

@ -0,0 +1,110 @@
#pragma once
namespace Emulator {
struct Game {
struct Memory;
struct Oscillator;
inline auto load(string_view) -> void;
inline auto memory(Markup::Node) -> maybe<Memory>;
inline auto oscillator(natural = 0) -> maybe<Oscillator>;
struct Memory {
Memory() = default;
inline Memory(Markup::Node);
explicit operator bool() const { return (bool)type; }
inline auto name() const -> string;
string type;
natural size;
string content;
string manufacturer;
string architecture;
string identifier;
boolean nonVolatile;
};
struct Oscillator {
Oscillator() = default;
inline Oscillator(Markup::Node);
explicit operator bool() const { return frequency; }
natural frequency;
};
Markup::Node document;
string sha256;
string label;
string name;
string region;
string revision;
string board;
vector<Memory> memoryList;
vector<Oscillator> oscillatorList;
};
auto Game::load(string_view text) -> void {
document = BML::unserialize(text);
sha256 = document["game/sha256"].text();
label = document["game/label"].text();
name = document["game/name"].text();
region = document["game/region"].text();
revision = document["game/revision"].text();
board = document["game/board"].text();
for(auto node : document.find("game/board/memory")) {
memoryList.append(Memory{node});
}
for(auto node : document.find("game/board/oscillator")) {
oscillatorList.append(Oscillator{node});
}
}
auto Game::memory(Markup::Node node) -> maybe<Memory> {
if(!node) return nothing;
for(auto& memory : memoryList) {
auto type = node["type"].text();
auto size = node["size"].natural();
auto content = node["content"].text();
auto manufacturer = node["manufacturer"].text();
auto architecture = node["architecture"].text();
auto identifier = node["identifier"].text();
if(type && type != memory.type) continue;
if(size && size != memory.size) continue;
if(content && content != memory.content) continue;
if(manufacturer && manufacturer != memory.manufacturer) continue;
if(architecture && architecture != memory.architecture) continue;
if(identifier && identifier != memory.identifier) continue;
return memory;
}
return nothing;
}
auto Game::oscillator(natural index) -> maybe<Oscillator> {
if(index < oscillatorList.size()) return oscillatorList[index];
return nothing;
}
Game::Memory::Memory(Markup::Node node) {
type = node["type"].text();
size = node["size"].natural();
content = node["content"].text();
manufacturer = node["manufacturer"].text();
architecture = node["architecture"].text();
identifier = node["identifier"].text();
nonVolatile = !(bool)node["volatile"];
}
auto Game::Memory::name() const -> string {
if(architecture) return string{architecture, ".", content, ".", type}.downcase();
return string{content, ".", type}.downcase();
}
Game::Oscillator::Oscillator(Markup::Node node) {
frequency = node["frequency"].natural();
}
}

View file

@ -0,0 +1,101 @@
#pragma once
namespace Emulator {
struct Interface {
struct Information {
string manufacturer;
string name;
string extension;
bool resettable = false;
};
struct Display {
struct Type { enum : uint {
CRT,
LCD,
};};
uint id = 0;
string name;
uint type = 0;
uint colors = 0;
uint width = 0;
uint height = 0;
uint internalWidth = 0;
uint internalHeight = 0;
double aspectCorrection = 0;
};
struct Port {
uint id;
string name;
};
struct Device {
uint id;
string name;
};
struct Input {
struct Type { enum : uint {
Hat,
Button,
Trigger,
Control,
Axis,
Rumble,
};};
uint type;
string name;
};
//information
virtual auto information() -> Information { return {}; }
virtual auto display() -> Display { return {}; }
virtual auto color(uint32 color) -> uint64 { return 0; }
//game interface
virtual auto loaded() -> bool { return false; }
virtual auto hashes() -> vector<string> { return {}; }
virtual auto manifests() -> vector<string> { return {}; }
virtual auto titles() -> vector<string> { return {}; }
virtual auto load() -> bool { return false; }
virtual auto save() -> void {}
virtual auto unload() -> void {}
//system interface
virtual auto ports() -> vector<Port> { return {}; }
virtual auto devices(uint port) -> vector<Device> { return {}; }
virtual auto inputs(uint device) -> vector<Input> { return {}; }
virtual auto connected(uint port) -> uint { return 0; }
virtual auto connect(uint port, uint device) -> void {}
virtual auto power() -> void {}
virtual auto reset() -> void {}
virtual auto run() -> void {}
//time functions
virtual auto rtc() -> bool { return false; }
virtual auto synchronize(uint64 timestamp = 0) -> void {}
//state functions
virtual auto serialize() -> serializer { return {}; }
virtual auto unserialize(serializer&) -> bool { return false; }
//cheat functions
virtual auto cheats(const vector<string>& = {}) -> void {}
//configuration
virtual auto configuration() -> string { return {}; }
virtual auto configuration(string name) -> string { return {}; }
virtual auto configure(string configuration = "") -> bool { return false; }
virtual auto configure(string name, string value) -> bool { return false; }
//settings
virtual auto cap(const string& name) -> bool { return false; }
virtual auto get(const string& name) -> any { return {}; }
virtual auto set(const string& name, const any& value) -> bool { return false; }
};
}

View file

@ -0,0 +1,30 @@
#pragma once
namespace Emulator::Memory {
inline auto mirror(uint address, uint size) -> uint {
if(size == 0) return 0;
uint base = 0;
uint mask = 1 << 31;
while(address >= size) {
while(!(address & mask)) mask >>= 1;
address -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
return base + address;
}
inline auto reduce(uint address, uint mask) -> uint {
while(mask) {
uint bits = (mask & -mask) - 1;
address = address >> 1 & ~bits | address & bits;
mask = (mask & mask - 1) >> 1;
}
return address;
}
}

View file

@ -0,0 +1,63 @@
#pragma once
#include <emulator/memory/memory.hpp>
namespace Emulator::Memory {
template<typename T>
struct Readable {
~Readable() { reset(); }
inline auto reset() -> void {
delete[] self.data;
self.data = nullptr;
self.size = 0;
self.mask = 0;
}
inline auto allocate(uint size, T fill = ~0ull) -> void {
if(!size) return reset();
delete[] self.data;
self.size = size;
self.mask = bit::round(self.size) - 1;
self.data = new T[self.mask + 1];
memory::fill<T>(self.data, self.mask + 1, fill);
}
inline auto load(vfs::shared::file fp) -> void {
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
for(uint address = self.size; address <= self.mask; address++) {
self.data[address] = self.data[mirror(address, self.size)];
}
}
inline auto save(vfs::shared::file fp) -> void {
fp->write(self.data, self.size * sizeof(T));
}
explicit operator bool() const { return (bool)self.data; }
inline auto data() const -> const T* { return self.data; }
inline auto size() const -> uint { return self.size; }
inline auto mask() const -> uint { return self.mask; }
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
inline auto write(uint address, T data) const -> void {}
auto serialize(serializer& s) -> void {
const uint size = self.size;
s.integer(self.size);
s.integer(self.mask);
if(self.size != size) allocate(self.size);
s.array(self.data, self.size);
}
private:
struct {
T* data = nullptr;
uint size = 0;
uint mask = 0;
} self;
};
}

View file

@ -0,0 +1,65 @@
#pragma once
#include <emulator/memory/memory.hpp>
namespace Emulator::Memory {
template<typename T>
struct Writable {
~Writable() { reset(); }
inline auto reset() -> void {
delete[] self.data;
self.data = nullptr;
self.size = 0;
self.mask = 0;
}
inline auto allocate(uint size, T fill = ~0ull) -> void {
if(!size) return reset();
delete[] self.data;
self.size = size;
self.mask = bit::round(self.size) - 1;
self.data = new T[self.mask + 1];
memory::fill<T>(self.data, self.mask + 1, fill);
}
inline auto load(vfs::shared::file fp) -> void {
fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
for(uint address = self.size; address <= self.mask; address++) {
self.data[address] = self.data[mirror(address, self.size)];
}
}
inline auto save(vfs::shared::file fp) -> void {
fp->write(self.data, self.size * sizeof(T));
}
explicit operator bool() const { return (bool)self.data; }
inline auto data() -> T* { return self.data; }
inline auto data() const -> const T* { return self.data; }
inline auto size() const -> uint { return self.size; }
inline auto mask() const -> uint { return self.mask; }
inline auto operator[](uint address) -> T& { return self.data[address & self.mask]; }
inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
inline auto write(uint address, T data) -> void { self.data[address & self.mask] = data; }
auto serialize(serializer& s) -> void {
const uint size = self.size;
s.integer(self.size);
s.integer(self.mask);
if(self.size != size) allocate(self.size);
s.array(self.data, self.size);
}
private:
struct {
T* data = nullptr;
uint size = 0;
uint mask = 0;
} self;
};
}

View file

@ -0,0 +1,29 @@
#pragma once
namespace Emulator {
struct Platform {
struct Load {
Load() = default;
Load(uint pathID, string option = "") : valid(true), pathID(pathID), option(option) {}
explicit operator bool() const { return valid; }
bool valid = false;
uint pathID = 0;
string option;
};
virtual auto path(uint id) -> string { return ""; }
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
virtual auto load(uint id, string name, string type, vector<string> options = {}) -> Load { return {}; }
virtual auto videoFrame(const uint32* data, uint pitch, uint width, uint height) -> void {}
virtual auto audioFrame(const double* samples, uint channels) -> void {}
virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}
virtual auto dipSettings(Markup::Node node) -> uint { return 0; }
virtual auto notify(string text) -> void {}
};
extern Platform* platform;
}

96
higan/emulator/random.hpp Normal file
View file

@ -0,0 +1,96 @@
#pragma once
namespace Emulator {
struct Random {
enum class Entropy : uint { None, Low, High };
auto operator()() -> uint64 {
return random();
}
auto entropy(Entropy entropy) -> void {
_entropy = entropy;
seed();
}
auto seed(maybe<uint32> seed = nothing, maybe<uint32> sequence = nothing) -> void {
if(!seed) seed = (uint32)clock();
if(!sequence) sequence = 0;
_state = 0;
_increment = sequence() << 1 | 1;
step();
_state += seed();
step();
}
auto random() -> uint64 {
if(_entropy == Entropy::None) return 0;
return (uint64)step() << 32 | (uint64)step() << 0;
}
auto bias(uint64 bias) -> uint64 {
if(_entropy == Entropy::None) return bias;
return random();
}
auto bound(uint64 bound) -> uint64 {
uint64 threshold = -bound % bound;
while(true) {
uint64 result = random();
if(result >= threshold) return result % bound;
}
}
auto array(uint8* data, uint32 size) -> void {
if(_entropy == Entropy::None) {
memory::fill(data, size);
return;
}
if(_entropy == Entropy::High) {
for(uint32 address : range(size)) {
data[address] = random();
}
return;
}
//Entropy::Low
uint lobit = random() & 3;
uint hibit = (lobit + 8 + (random() & 3)) & 15;
uint lovalue = random() & 255;
uint hivalue = random() & 255;
if((random() & 3) == 0) lovalue = 0;
if((random() & 1) == 0) hivalue = ~lovalue;
for(uint32 address : range(size)) {
uint8 value = address.bit(lobit) ? lovalue : hivalue;
if(address.bit(hibit)) value = ~value;
if((random() & 511) == 0) value.bit(random() & 7) ^= 1;
if((random() & 2047) == 0) value.bit(random() & 7) ^= 1;
data[address] = value;
}
}
auto serialize(serializer& s) -> void {
s.integer((uint&)_entropy);
s.integer(_state);
s.integer(_increment);
}
private:
auto step() -> uint32 {
uint64 state = _state;
_state = state * 6364136223846793005ull + _increment;
uint32 xorshift = (state >> 18 ^ state) >> 27;
uint32 rotate = state >> 59;
return xorshift >> rotate | xorshift << (-rotate & 31);
}
Entropy _entropy = Entropy::High;
uint64 _state;
uint64 _increment;
};
}

View file

@ -0,0 +1,6 @@
all:
sourcery resource.bml resource.cpp resource.hpp
clean:
rm resource.cpp
rm resource.hpp

View file

@ -0,0 +1,5 @@
namespace name=Resource
namespace name=Sprite
binary name=CrosshairRed file=sprite/crosshair-red.png
binary name=CrosshairGreen file=sprite/crosshair-green.png
binary name=CrosshairBlue file=sprite/crosshair-blue.png

View file

@ -0,0 +1,45 @@
#include "resource.hpp"
namespace Resource {
namespace Sprite {
const unsigned char CrosshairRed[342] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
196,1,149,43,14,27,0,0,0,248,73,68,65,84,88,133,205,87,65,14,196,32,8,132,102,255,255,101,246,176,177,139,
148,81,80,27,229,212,70,102,6,212,0,50,229,77,26,107,156,37,139,2,228,241,209,39,11,113,71,156,68,139,106,128,
56,255,198,175,203,223,114,16,79,68,253,138,90,99,141,113,112,80,231,131,196,11,83,52,19,43,196,53,135,147,7,38,
150,104,244,212,32,86,235,228,236,20,6,200,207,191,117,215,70,12,242,94,139,133,166,236,173,236,67,252,111,139,67,157,
237,71,48,27,192,244,142,93,228,23,148,144,184,228,131,96,254,3,164,4,176,213,108,37,52,5,208,53,47,227,81,28,
49,153,102,163,88,96,149,68,150,193,21,223,59,128,68,43,69,13,103,4,199,246,8,34,151,240,209,249,38,112,251,47,
97,177,209,74,152,246,95,93,9,211,51,160,181,99,142,128,104,115,55,124,59,136,115,7,146,237,51,33,2,71,166,226,
94,23,13,77,214,104,44,103,174,163,143,86,189,244,187,224,232,151,81,21,132,39,210,33,91,246,54,132,193,44,226,219,
107,95,57,136,120,253,172,254,16,23,0,0,0,0,73,69,78,68,174,66,96,130,
};
const unsigned char CrosshairGreen[329] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
196,1,149,43,14,27,0,0,0,235,73,68,65,84,88,133,213,87,65,18,195,32,8,196,78,31,230,211,253,153,61,180,
52,18,145,1,193,97,178,39,141,44,139,24,69,11,216,209,133,177,98,117,166,37,92,162,77,176,170,118,223,26,163,78,
68,71,145,198,244,169,157,57,35,84,248,43,222,255,109,154,254,113,140,114,102,222,18,239,165,120,251,181,42,0,232,103,
114,217,85,226,163,27,124,232,163,87,142,115,153,82,137,71,98,233,247,21,44,228,194,169,217,171,252,159,22,95,234,164,
47,129,55,128,144,140,237,166,63,132,151,190,4,247,147,16,103,35,157,90,220,140,119,121,80,224,94,108,0,164,227,119,
182,221,229,13,182,82,193,225,176,42,56,59,188,105,9,52,5,3,109,58,243,205,202,203,255,9,17,251,91,202,169,227,
205,128,235,198,19,17,64,40,82,171,225,233,32,158,113,33,65,164,222,9,105,16,50,81,55,238,88,210,212,119,1,0,
238,241,241,126,143,125,62,216,173,151,209,35,222,134,235,96,98,252,229,226,3,112,72,179,236,202,138,114,18,0,0,0,
0,73,69,78,68,174,66,96,130,
};
const unsigned char CrosshairBlue[332] = {
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,32,8,6,0,0,0,115,122,122,
244,0,0,0,4,115,66,73,84,8,8,8,8,124,8,100,136,0,0,0,9,112,72,89,115,0,0,14,196,0,0,14,
196,1,149,43,14,27,0,0,0,238,73,68,65,84,88,133,213,87,91,18,195,32,8,196,78,15,232,81,189,161,253,9,
25,52,98,121,57,76,246,43,137,44,11,24,69,11,232,209,55,99,69,235,76,74,184,69,107,229,245,91,27,220,137,124,
75,140,58,21,165,34,181,246,199,251,100,167,174,200,32,124,137,119,124,134,177,252,116,108,224,44,120,44,190,156,56,102,
163,204,228,182,107,173,80,31,93,225,67,30,189,112,124,85,41,145,120,36,88,191,159,96,33,23,78,101,47,242,127,90,
156,213,73,159,2,111,0,33,21,179,150,63,132,151,62,5,243,78,136,217,236,118,173,85,198,86,30,20,152,154,13,192,
118,251,125,216,90,121,212,118,215,112,86,224,26,142,133,247,152,2,73,195,64,155,190,248,166,229,229,255,132,8,243,146,
242,234,120,43,224,58,241,68,4,16,138,212,110,120,58,136,119,28,72,16,169,103,194,33,136,63,68,209,184,103,74,83,
239,5,0,215,26,167,231,123,124,103,130,53,221,140,94,113,55,100,131,9,242,151,139,31,79,50,234,237,105,206,30,22,
0,0,0,0,73,69,78,68,174,66,96,130,
};
}
}

View file

@ -0,0 +1,7 @@
namespace Resource {
namespace Sprite {
extern const unsigned char CrosshairRed[342];
extern const unsigned char CrosshairGreen[329];
extern const unsigned char CrosshairBlue[332];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

View file

@ -0,0 +1,91 @@
#pragma once
namespace Emulator {
struct Scheduler {
enum class Mode : uint {
Run,
SynchronizeMaster,
SynchronizeSlave,
};
enum class Event : uint {
Step,
Frame,
Synchronize,
};
inline auto synchronizing() const -> bool { return _mode == Mode::SynchronizeSlave; }
auto reset() -> void {
_host = co_active();
_threads.reset();
}
auto primary(Thread& thread) -> void {
_master = _resume = thread.handle();
}
auto append(Thread& thread) -> bool {
if(_threads.find(&thread)) return false;
thread._clock += _threads.size(); //this bias prioritizes threads appended earlier first
return _threads.append(&thread), true;
}
auto remove(Thread& thread) -> bool {
if(auto offset = _threads.find(&thread)) return _threads.remove(*offset), true;
return false;
}
auto enter(Mode mode = Mode::Run) -> Event {
_mode = mode;
_host = co_active();
co_switch(_resume);
return _event;
}
inline auto resume(Thread& thread) -> void {
if(_mode != Mode::SynchronizeSlave) co_switch(thread.handle());
}
auto exit(Event event) -> void {
uintmax minimum = -1;
for(auto thread : _threads) {
if(thread->_clock < minimum) minimum = thread->_clock;
}
for(auto thread : _threads) {
thread->_clock -= minimum;
}
_event = event;
_resume = co_active();
co_switch(_host);
}
inline auto synchronize(Thread& thread) -> void {
if(thread.handle() == _master) {
while(enter(Mode::SynchronizeMaster) != Event::Synchronize);
} else {
_resume = thread.handle();
while(enter(Mode::SynchronizeSlave) != Event::Synchronize);
}
}
inline auto synchronize() -> void {
if(co_active() == _master) {
if(_mode == Mode::SynchronizeMaster) return exit(Event::Synchronize);
} else {
if(_mode == Mode::SynchronizeSlave) return exit(Event::Synchronize);
}
}
private:
cothread_t _host = nullptr; //program thread (used to exit scheduler)
cothread_t _resume = nullptr; //resume thread (used to enter scheduler)
cothread_t _master = nullptr; //primary thread (used to synchronize components)
Mode _mode = Mode::Run;
Event _event = Event::Step;
vector<Thread*> _threads;
};
}

61
higan/emulator/thread.hpp Normal file
View file

@ -0,0 +1,61 @@
#pragma once
namespace Emulator {
struct Thread {
enum : uintmax { Second = (uintmax)-1 >> 1 };
virtual ~Thread() {
if(_handle) co_delete(_handle);
}
inline auto active() const { return co_active() == _handle; }
inline auto handle() const { return _handle; }
inline auto frequency() const { return _frequency; }
inline auto scalar() const { return _scalar; }
inline auto clock() const { return _clock; }
auto setHandle(cothread_t handle) -> void {
_handle = handle;
}
auto setFrequency(double frequency) -> void {
_frequency = frequency + 0.5;
_scalar = Second / _frequency;
}
auto setScalar(uintmax scalar) -> void {
_scalar = scalar;
}
auto setClock(uintmax clock) -> void {
_clock = clock;
}
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
if(_handle) co_delete(_handle);
_handle = co_create(64 * 1024 * sizeof(void*), entrypoint);
setFrequency(frequency);
setClock(0);
}
inline auto step(uint clocks) -> void {
_clock += _scalar * clocks;
}
auto serialize(serializer& s) -> void {
s.integer(_frequency);
s.integer(_scalar);
s.integer(_clock);
}
protected:
cothread_t _handle = nullptr;
uintmax _frequency = 0;
uintmax _scalar = 0;
uintmax _clock = 0;
friend class Scheduler;
};
}

129
higan/emulator/types.hpp Normal file
View file

@ -0,0 +1,129 @@
using int1 = nall::Integer< 1>;
using int2 = nall::Integer< 2>;
using int3 = nall::Integer< 3>;
using int4 = nall::Integer< 4>;
using int5 = nall::Integer< 5>;
using int6 = nall::Integer< 6>;
using int7 = nall::Integer< 7>;
using int8 = nall::Integer< 8>;
using int9 = nall::Integer< 9>;
using int10 = nall::Integer<10>;
using int11 = nall::Integer<11>;
using int12 = nall::Integer<12>;
using int13 = nall::Integer<13>;
using int14 = nall::Integer<14>;
using int15 = nall::Integer<15>;
using int16 = nall::Integer<16>;
using int17 = nall::Integer<17>;
using int18 = nall::Integer<18>;
using int19 = nall::Integer<19>;
using int20 = nall::Integer<20>;
using int21 = nall::Integer<21>;
using int22 = nall::Integer<22>;
using int23 = nall::Integer<23>;
using int24 = nall::Integer<24>;
using int25 = nall::Integer<25>;
using int26 = nall::Integer<26>;
using int27 = nall::Integer<27>;
using int28 = nall::Integer<28>;
using int29 = nall::Integer<29>;
using int30 = nall::Integer<30>;
using int31 = nall::Integer<31>;
using int32 = nall::Integer<32>;
using int33 = nall::Integer<33>;
using int34 = nall::Integer<34>;
using int35 = nall::Integer<35>;
using int36 = nall::Integer<36>;
using int37 = nall::Integer<37>;
using int38 = nall::Integer<38>;
using int39 = nall::Integer<39>;
using int40 = nall::Integer<40>;
using int41 = nall::Integer<41>;
using int42 = nall::Integer<42>;
using int43 = nall::Integer<43>;
using int44 = nall::Integer<44>;
using int45 = nall::Integer<45>;
using int46 = nall::Integer<46>;
using int47 = nall::Integer<47>;
using int48 = nall::Integer<48>;
using int49 = nall::Integer<49>;
using int50 = nall::Integer<50>;
using int51 = nall::Integer<51>;
using int52 = nall::Integer<52>;
using int53 = nall::Integer<53>;
using int54 = nall::Integer<54>;
using int55 = nall::Integer<55>;
using int56 = nall::Integer<56>;
using int57 = nall::Integer<57>;
using int58 = nall::Integer<58>;
using int59 = nall::Integer<59>;
using int60 = nall::Integer<60>;
using int61 = nall::Integer<61>;
using int62 = nall::Integer<62>;
using int63 = nall::Integer<63>;
using int64 = nall::Integer<64>;
using uint1 = nall::Natural< 1>;
using uint2 = nall::Natural< 2>;
using uint3 = nall::Natural< 3>;
using uint4 = nall::Natural< 4>;
using uint5 = nall::Natural< 5>;
using uint6 = nall::Natural< 6>;
using uint7 = nall::Natural< 7>;
using uint8 = nall::Natural< 8>;
using uint9 = nall::Natural< 9>;
using uint10 = nall::Natural<10>;
using uint11 = nall::Natural<11>;
using uint12 = nall::Natural<12>;
using uint13 = nall::Natural<13>;
using uint14 = nall::Natural<14>;
using uint15 = nall::Natural<15>;
using uint16 = nall::Natural<16>;
using uint17 = nall::Natural<17>;
using uint18 = nall::Natural<18>;
using uint19 = nall::Natural<19>;
using uint20 = nall::Natural<20>;
using uint21 = nall::Natural<21>;
using uint22 = nall::Natural<22>;
using uint23 = nall::Natural<23>;
using uint24 = nall::Natural<24>;
using uint25 = nall::Natural<25>;
using uint26 = nall::Natural<26>;
using uint27 = nall::Natural<27>;
using uint28 = nall::Natural<28>;
using uint29 = nall::Natural<29>;
using uint30 = nall::Natural<30>;
using uint31 = nall::Natural<31>;
using uint32 = nall::Natural<32>;
using uint33 = nall::Natural<33>;
using uint34 = nall::Natural<34>;
using uint35 = nall::Natural<35>;
using uint36 = nall::Natural<36>;
using uint37 = nall::Natural<37>;
using uint38 = nall::Natural<38>;
using uint39 = nall::Natural<39>;
using uint40 = nall::Natural<40>;
using uint41 = nall::Natural<41>;
using uint42 = nall::Natural<42>;
using uint43 = nall::Natural<43>;
using uint44 = nall::Natural<44>;
using uint45 = nall::Natural<45>;
using uint46 = nall::Natural<46>;
using uint47 = nall::Natural<47>;
using uint48 = nall::Natural<48>;
using uint49 = nall::Natural<49>;
using uint50 = nall::Natural<50>;
using uint51 = nall::Natural<51>;
using uint52 = nall::Natural<52>;
using uint53 = nall::Natural<53>;
using uint54 = nall::Natural<54>;
using uint55 = nall::Natural<55>;
using uint56 = nall::Natural<56>;
using uint57 = nall::Natural<57>;
using uint58 = nall::Natural<58>;
using uint59 = nall::Natural<59>;
using uint60 = nall::Natural<60>;
using uint61 = nall::Natural<61>;
using uint62 = nall::Natural<62>;
using uint63 = nall::Natural<63>;
using uint64 = nall::Natural<64>;

View file

@ -0,0 +1,20 @@
Sprite::Sprite(uint width, uint height) : width(width), height(height) {
pixels = new uint32[width * height]();
}
Sprite::~Sprite() {
delete[] pixels;
}
auto Sprite::setPixels(const nall::image& image) -> void {
memory::copy(this->pixels, width * height * sizeof(uint32), image.data(), image.size());
}
auto Sprite::setVisible(bool visible) -> void {
this->visible = visible;
}
auto Sprite::setPosition(int x, int y) -> void {
this->x = x;
this->y = y;
}

View file

@ -0,0 +1,191 @@
namespace Emulator {
#include "sprite.cpp"
Video video;
Video::~Video() {
reset(nullptr);
}
auto Video::reset(Interface* interface) -> void {
this->interface = interface;
sprites.reset();
delete buffer;
buffer = nullptr;
delete rotate;
rotate = nullptr;
delete palette;
palette = nullptr;
width = 0;
height = 0;
effects.colorBleed = false;
effects.interframeBlending = false;
effects.rotateLeft = false;
}
auto Video::setPalette() -> void {
if(!interface) return;
delete palette;
colors = interface->display().colors;
palette = new uint32[colors];
for(auto index : range(colors)) {
uint64 color = interface->color(index);
uint16 b = color.bits( 0,15);
uint16 g = color.bits(16,31);
uint16 r = color.bits(32,47);
uint16 a = 0xffff;
if(saturation != 1.0) {
uint16 grayscale = uclamp<16>((r + g + b) / 3);
double inverse = max(0.0, 1.0 - saturation);
r = uclamp<16>(r * saturation + grayscale * inverse);
g = uclamp<16>(g * saturation + grayscale * inverse);
b = uclamp<16>(b * saturation + grayscale * inverse);
}
if(gamma != 1.0) {
double reciprocal = 1.0 / 32767.0;
r = r > 32767 ? r : uint16(32767 * pow(r * reciprocal, gamma));
g = g > 32767 ? g : uint16(32767 * pow(g * reciprocal, gamma));
b = b > 32767 ? b : uint16(32767 * pow(b * reciprocal, gamma));
}
if(luminance != 1.0) {
r = uclamp<16>(r * luminance);
g = uclamp<16>(g * luminance);
b = uclamp<16>(b * luminance);
}
switch(depth) {
case 24: palette[index] = r >> 8 << 16 | g >> 8 << 8 | b >> 8 << 0; break;
case 30: palette[index] = r >> 6 << 20 | g >> 6 << 10 | b >> 6 << 0; break;
}
}
}
auto Video::setDepth(uint depth) -> void {
this->depth = depth;
}
auto Video::setSaturation(double saturation) -> void {
this->saturation = saturation;
}
auto Video::setGamma(double gamma) -> void {
this->gamma = gamma;
}
auto Video::setLuminance(double luminance) -> void {
this->luminance = luminance;
}
auto Video::setEffect(Effect effect, const any& value) -> void {
if(effect == Effect::ColorBleed && value.is<bool>()) {
effects.colorBleed = value.get<bool>();
}
if(effect == Effect::InterframeBlending && value.is<bool>()) {
effects.interframeBlending = value.get<bool>();
}
if(effect == Effect::RotateLeft && value.is<bool>()) {
effects.rotateLeft = value.get<bool>();
}
}
auto Video::createSprite(uint width, uint height) -> shared_pointer<Sprite> {
shared_pointer<Sprite> sprite = new Sprite{width, height};
sprites.append(sprite);
return sprite;
}
auto Video::removeSprite(shared_pointer<Sprite> sprite) -> bool {
for(uint n : range(sprites.size())) {
if(sprite == sprites[n]) {
sprites.remove(n);
return true;
}
}
return false;
}
auto Video::refresh(uint32* input, uint pitch, uint width, uint height) -> void {
if(this->width != width || this->height != height) {
delete buffer;
delete rotate;
buffer = new uint32[width * height]();
rotate = new uint32[height * width]();
this->width = width;
this->height = height;
}
auto output = buffer;
pitch >>= 2; //bytes to words
for(uint y : range(height)) {
auto source = input + y * pitch;
auto target = output + y * width;
if(!effects.interframeBlending) {
for(uint x : range(width)) {
auto color = palette[*source++];
*target++ = color;
}
} else {
uint32 mask = depth == 30 ? 0x40100401 : 0x01010101;
for(uint x : range(width)) {
auto a = *target;
auto b = palette[*source++];
*target++ = (a + b - ((a ^ b) & mask)) >> 1;
}
}
}
if(effects.colorBleed) {
uint32 mask = depth == 30 ? 0x40100401 : 0x01010101;
for(uint y : range(height)) {
auto target = output + y * width;
for(uint x : range(width)) {
auto a = target[x];
auto b = target[x + (x != width - 1)];
target[x] = (a + b - ((a ^ b) & mask)) >> 1;
}
}
}
if(effects.rotateLeft) {
for(uint y : range(height)) {
auto source = buffer + y * width;
for(uint x : range(width)) {
auto target = rotate + (width - 1 - x) * height + y;
*target = *source++;
}
}
output = rotate;
swap(width, height);
}
for(auto& sprite : sprites) {
if(!sprite->visible) continue;
uint32 opaqueAlpha = depth == 30 ? 0xc0000000 : 0xff000000;
for(int y : range(sprite->height)) {
for(int x : range(sprite->width)) {
int pixelY = sprite->y + y;
if(pixelY < 0 || pixelY >= height) continue;
int pixelX = sprite->x + x;
if(pixelX < 0 || pixelX >= width) continue;
auto pixel = sprite->pixels[y * sprite->width + x];
if(pixel) output[pixelY * width + pixelX] = opaqueAlpha | pixel;
}
}
}
platform->videoFrame(output, width * sizeof(uint32), width, height);
}
}

View file

@ -0,0 +1,80 @@
#pragma once
namespace Emulator {
struct Interface;
struct Video;
struct Sprite;
struct Video {
enum class Effect : uint {
ColorBleed,
InterframeBlending,
RotateLeft,
};
~Video();
auto reset(Interface* interface) -> void;
auto setPalette() -> void;
auto setDepth(uint depth) -> void;
auto setSaturation(double saturation) -> void;
auto setGamma(double gamma) -> void;
auto setLuminance(double luminance) -> void;
auto setEffect(Effect effect, const any& value) -> void;
auto createSprite(uint width, uint height) -> shared_pointer<Sprite>;
auto removeSprite(shared_pointer<Sprite> sprite) -> bool;
auto refresh(uint32* input, uint pitch, uint width, uint height) -> void;
private:
Interface* interface = nullptr;
vector<shared_pointer<Sprite>> sprites;
uint32* buffer = nullptr;
uint32* rotate = nullptr;
uint32* palette = nullptr;
uint width = 0;
uint height = 0;
uint colors = 0;
uint depth = 24;
double saturation = 1.0;
double gamma = 1.0;
double luminance = 1.0;
struct Effects {
bool colorBleed = false;
bool interframeBlending = false;
bool rotateLeft = false;
} effects;
friend class Sprite;
};
struct Sprite {
Sprite(uint width, uint height);
~Sprite();
auto setPixels(const nall::image& image) -> void;
auto setVisible(bool visible) -> void;
auto setPosition(int x, int y) -> void;
private:
const uint width;
const uint height;
uint32* pixels = nullptr;
bool visible = false;
int x = 0;
int y = 0;
friend class Video;
};
extern Video video;
}

13
higan/fc/GNUmakefile Normal file
View file

@ -0,0 +1,13 @@
processors += mos6502
objects += fc-interface fc-system fc-controller
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
obj/fc-interface.o: fc/interface/interface.cpp
obj/fc-system.o: fc/system/system.cpp
obj/fc-controller.o: fc/controller/controller.cpp
obj/fc-memory.o: fc/memory/memory.cpp
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp
obj/fc-cpu.o: fc/cpu/cpu.cpp
obj/fc-apu.o: fc/apu/apu.cpp
obj/fc-ppu.o: fc/ppu/ppu.cpp

319
higan/fc/apu/apu.cpp Normal file
View file

@ -0,0 +1,319 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "envelope.cpp"
#include "sweep.cpp"
#include "pulse.cpp"
#include "triangle.cpp"
#include "noise.cpp"
#include "dmc.cpp"
#include "serialization.cpp"
APU apu;
APU::APU() {
for(uint amp : range(32)) {
if(amp == 0) {
pulseDAC[amp] = 0;
} else {
pulseDAC[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
}
}
for(uint dmc_amp : range(128)) {
for(uint triangle_amp : range(16)) {
for(uint noise_amp : range(16)) {
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp] = 0;
} else {
dmcTriangleNoiseDAC[dmc_amp][triangle_amp][noise_amp]
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
}
}
}
}
}
auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main();
}
auto APU::main() -> void {
uint pulse_output, triangle_output, noise_output, dmc_output;
pulse_output = pulse[0].clock();
pulse_output += pulse[1].clock();
triangle_output = triangle.clock();
noise_output = noise.clock();
dmc_output = dmc.clock();
clockFrameCounterDivider();
int output = 0;
output += pulseDAC[pulse_output];
output += dmcTriangleNoiseDAC[dmc_output][triangle_output][noise_output];
output += cartridgeSample;
stream->sample(sclamp<16>(output) / 32768.0);
tick();
}
auto APU::tick() -> void {
Thread::step(rate());
synchronize(cpu);
}
auto APU::setIRQ() -> void {
cpu.apuLine(frame.irqPending || dmc.irqPending);
}
auto APU::setSample(int16 sample) -> void {
cartridgeSample = sample;
}
auto APU::power(bool reset) -> void {
create(APU::Enter, system.frequency());
stream = Emulator::audio.createStream(1, frequency() / rate());
stream->addHighPassFilter( 90.0, Emulator::Filter::Order::First);
stream->addHighPassFilter( 440.0, Emulator::Filter::Order::First);
stream->addLowPassFilter (14000.0, Emulator::Filter::Order::First);
stream->addDCRemovalFilter();
pulse[0].power();
pulse[1].power();
triangle.power();
noise.power();
dmc.power();
frame.irqPending = 0;
frame.mode = 0;
frame.counter = 0;
frame.divider = 1;
enabledChannels = 0;
cartridgeSample = 0;
setIRQ();
}
auto APU::readIO(uint16 addr) -> uint8 {
switch(addr) {
case 0x4015: {
uint8 result = 0x00;
result |= pulse[0].lengthCounter ? 0x01 : 0;
result |= pulse[1].lengthCounter ? 0x02 : 0;
result |= triangle.lengthCounter ? 0x04 : 0;
result |= noise.lengthCounter ? 0x08 : 0;
result |= dmc.lengthCounter ? 0x10 : 0;
result |= frame.irqPending ? 0x40 : 0;
result |= dmc.irqPending ? 0x80 : 0;
frame.irqPending = false;
setIRQ();
return result;
}
}
return cpu.mdr();
}
auto APU::writeIO(uint16 addr, uint8 data) -> void {
const uint n = (addr >> 2) & 1; //pulse#
switch(addr) {
case 0x4000: case 0x4004: {
pulse[n].duty = data >> 6;
pulse[n].envelope.loopMode = data & 0x20;
pulse[n].envelope.useSpeedAsVolume = data & 0x10;
pulse[n].envelope.speed = data & 0x0f;
return;
}
case 0x4001: case 0x4005: {
pulse[n].sweep.enable = data & 0x80;
pulse[n].sweep.period = (data & 0x70) >> 4;
pulse[n].sweep.decrement = data & 0x08;
pulse[n].sweep.shift = data & 0x07;
pulse[n].sweep.reload = true;
return;
}
case 0x4002: case 0x4006: {
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x0700) | (data << 0);
return;
}
case 0x4003: case 0x4007: {
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
pulse[n].sweep.pulsePeriod = (pulse[n].sweep.pulsePeriod & 0x00ff) | (data << 8);
pulse[n].dutyCounter = 0;
pulse[n].envelope.reloadDecay = true;
if(enabledChannels & (1 << n)) {
pulse[n].lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
}
return;
}
case 0x4008: {
triangle.haltLengthCounter = data & 0x80;
triangle.linearLength = data & 0x7f;
return;
}
case 0x400a: {
triangle.period = (triangle.period & 0x0700) | (data << 0);
return;
}
case 0x400b: {
triangle.period = (triangle.period & 0x00ff) | (data << 8);
triangle.reloadLinear = true;
if(enabledChannels & (1 << 2)) {
triangle.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
}
return;
}
case 0x400c: {
noise.envelope.loopMode = data & 0x20;
noise.envelope.useSpeedAsVolume = data & 0x10;
noise.envelope.speed = data & 0x0f;
return;
}
case 0x400e: {
noise.shortMode = data & 0x80;
noise.period = data & 0x0f;
return;
}
case 0x400f: {
noise.envelope.reloadDecay = true;
if(enabledChannels & (1 << 3)) {
noise.lengthCounter = lengthCounterTable[(data >> 3) & 0x1f];
}
return;
}
case 0x4010: {
dmc.irqEnable = data & 0x80;
dmc.loopMode = data & 0x40;
dmc.period = data & 0x0f;
dmc.irqPending = dmc.irqPending && dmc.irqEnable && !dmc.loopMode;
setIRQ();
return;
}
case 0x4011: {
dmc.dacLatch = data & 0x7f;
return;
}
case 0x4012: {
dmc.addrLatch = data;
return;
}
case 0x4013: {
dmc.lengthLatch = data;
return;
}
case 0x4015: {
if((data & 0x01) == 0) pulse[0].lengthCounter = 0;
if((data & 0x02) == 0) pulse[1].lengthCounter = 0;
if((data & 0x04) == 0) triangle.lengthCounter = 0;
if((data & 0x08) == 0) noise.lengthCounter = 0;
(data & 0x10) ? dmc.start() : dmc.stop();
dmc.irqPending = false;
setIRQ();
enabledChannels = data & 0x1f;
return;
}
case 0x4017: {
frame.mode = data >> 6;
frame.counter = 0;
if(frame.mode & 2) clockFrameCounter();
if(frame.mode & 1) {
frame.irqPending = false;
setIRQ();
}
frame.divider = FrameCounter::NtscPeriod;
return;
}
}
}
auto APU::clockFrameCounter() -> void {
frame.counter++;
if(frame.counter & 1) {
pulse[0].clockLength();
pulse[0].sweep.clock(0);
pulse[1].clockLength();
pulse[1].sweep.clock(1);
triangle.clockLength();
noise.clockLength();
}
pulse[0].envelope.clock();
pulse[1].envelope.clock();
triangle.clockLinearLength();
noise.envelope.clock();
if(frame.counter == 0) {
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
if(frame.mode == 0) {
frame.irqPending = true;
setIRQ();
}
}
}
auto APU::clockFrameCounterDivider() -> void {
frame.divider -= 2;
if(frame.divider <= 0) {
clockFrameCounter();
frame.divider += FrameCounter::NtscPeriod;
}
}
const uint8 APU::lengthCounterTable[32] = {
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
};
const uint16 APU::noisePeriodTableNTSC[16] = {
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
};
const uint16 APU::noisePeriodTablePAL[16] = {
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
};
const uint16 APU::dmcPeriodTableNTSC[16] = {
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
};
const uint16 APU::dmcPeriodTablePAL[16] = {
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
};
}

182
higan/fc/apu/apu.hpp Normal file
View file

@ -0,0 +1,182 @@
struct APU : Thread {
shared_pointer<Emulator::Stream> stream;
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//apu.cpp
APU();
static auto Enter() -> void;
auto main() -> void;
auto tick() -> void;
auto setIRQ() -> void;
auto setSample(int16 sample) -> void;
auto power(bool reset) -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Envelope {
auto volume() const -> uint;
auto clock() -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
uint4 speed;
bool useSpeedAsVolume;
bool loopMode;
bool reloadDecay;
uint8 decayCounter;
uint4 decayVolume;
};
struct Sweep {
auto checkPeriod() -> bool;
auto clock(uint channel) -> void;
auto power() -> void;
auto serialize(serializer&) -> void;
uint8 shift;
bool decrement;
uint3 period;
uint8 counter;
bool enable;
bool reload;
uint11 pulsePeriod;
};
struct Pulse {
auto clockLength() -> void;
auto checkPeriod() -> bool;
auto clock() -> uint8;
auto power() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
Envelope envelope;
Sweep sweep;
uint2 duty;
uint3 dutyCounter;
uint11 period;
uint periodCounter;
} pulse[2];
struct Triangle {
auto clockLength() -> void;
auto clockLinearLength() -> void;
auto clock() -> uint8;
auto power() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
uint8 linearLength;
bool haltLengthCounter;
uint11 period;
uint periodCounter;
uint5 stepCounter;
uint8 linearLengthCounter;
bool reloadLinear;
} triangle;
struct Noise {
auto clockLength() -> void;
auto clock() -> uint8;
auto power() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
Envelope envelope;
uint4 period;
uint periodCounter;
bool shortMode;
uint15 lfsr;
} noise;
struct DMC {
auto start() -> void;
auto stop() -> void;
auto clock() -> uint8;
auto power() -> void;
auto serialize(serializer&) -> void;
uint lengthCounter;
bool irqPending;
uint4 period;
uint periodCounter;
bool irqEnable;
bool loopMode;
uint8 dacLatch;
uint8 addrLatch;
uint8 lengthLatch;
uint15 readAddr;
uint dmaDelayCounter;
uint3 bitCounter;
bool dmaBufferValid;
uint8 dmaBuffer;
bool sampleValid;
uint8 sample;
} dmc;
struct FrameCounter {
auto serialize(serializer&) -> void;
enum : uint { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
bool irqPending;
uint2 mode;
uint2 counter;
int divider;
};
auto clockFrameCounter() -> void;
auto clockFrameCounterDivider() -> void;
FrameCounter frame;
uint8 enabledChannels;
int16 cartridgeSample;
int16 pulseDAC[32];
int16 dmcTriangleNoiseDAC[128][16][16];
static const uint8 lengthCounterTable[32];
static const uint16 dmcPeriodTableNTSC[16];
static const uint16 dmcPeriodTablePAL[16];
static const uint16 noisePeriodTableNTSC[16];
static const uint16 noisePeriodTablePAL[16];
};
extern APU apu;

89
higan/fc/apu/dmc.cpp Normal file
View file

@ -0,0 +1,89 @@
auto APU::DMC::start() -> void {
if(lengthCounter == 0) {
readAddr = 0x4000 + (addrLatch << 6);
lengthCounter = (lengthLatch << 4) + 1;
}
}
auto APU::DMC::stop() -> void {
lengthCounter = 0;
dmaDelayCounter = 0;
cpu.rdyLine(1);
cpu.rdyAddr(false);
}
auto APU::DMC::clock() -> uint8 {
uint8 result = dacLatch;
if(dmaDelayCounter > 0) {
dmaDelayCounter--;
if(dmaDelayCounter == 1) {
cpu.rdyAddr(true, 0x8000 | readAddr);
} else if(dmaDelayCounter == 0) {
cpu.rdyLine(1);
cpu.rdyAddr(false);
dmaBuffer = cpu.mdr();
dmaBufferValid = true;
lengthCounter--;
readAddr++;
if(lengthCounter == 0) {
if(loopMode) {
start();
} else if(irqEnable) {
irqPending = true;
apu.setIRQ();
}
}
}
}
if(--periodCounter == 0) {
if(sampleValid) {
int delta = (((sample >> bitCounter) & 1) << 2) - 2;
uint data = dacLatch + delta;
if((data & 0x80) == 0) dacLatch = data;
}
if(++bitCounter == 0) {
if(dmaBufferValid) {
sampleValid = true;
sample = dmaBuffer;
dmaBufferValid = false;
} else {
sampleValid = false;
}
}
periodCounter = Region::PAL() ? dmcPeriodTablePAL[period] : dmcPeriodTableNTSC[period];
}
if(lengthCounter > 0 && !dmaBufferValid && dmaDelayCounter == 0) {
cpu.rdyLine(0);
dmaDelayCounter = 4;
}
return result;
}
auto APU::DMC::power() -> void {
lengthCounter = 0;
irqPending = 0;
period = 0;
periodCounter = Region::PAL() ? dmcPeriodTablePAL[0] : dmcPeriodTableNTSC[0];
irqEnable = 0;
loopMode = 0;
dacLatch = 0;
addrLatch = 0;
lengthLatch = 0;
readAddr = 0;
dmaDelayCounter = 0;
bitCounter = 0;
dmaBufferValid = 0;
dmaBuffer = 0;
sampleValid = 0;
sample = 0;
}

26
higan/fc/apu/envelope.cpp Normal file
View file

@ -0,0 +1,26 @@
auto APU::Envelope::volume() const -> uint {
return useSpeedAsVolume ? speed : decayVolume;
}
auto APU::Envelope::clock() -> void {
if(reloadDecay) {
reloadDecay = false;
decayVolume = 0x0f;
decayCounter = speed + 1;
return;
}
if(--decayCounter == 0) {
decayCounter = speed + 1;
if(decayVolume || loopMode) decayVolume--;
}
}
auto APU::Envelope::power() -> void {
speed = 0;
useSpeedAsVolume = 0;
loopMode = 0;
reloadDecay = 0;
decayCounter = 0;
decayVolume = 0;
}

42
higan/fc/apu/noise.cpp Normal file
View file

@ -0,0 +1,42 @@
auto APU::Noise::clockLength() -> void {
if(envelope.loopMode == 0) {
if(lengthCounter > 0) lengthCounter--;
}
}
auto APU::Noise::clock() -> uint8 {
if(lengthCounter == 0) return 0;
uint8 result = (lfsr & 1) ? envelope.volume() : 0;
if(--periodCounter == 0) {
uint feedback;
if(shortMode) {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 6) & 1);
} else {
feedback = ((lfsr >> 0) & 1) ^ ((lfsr >> 1) & 1);
}
lfsr = (lfsr >> 1) | (feedback << 14);
periodCounter = Region::PAL() ? apu.noisePeriodTablePAL[period] : apu.noisePeriodTableNTSC[period];
}
return result;
}
auto APU::Noise::power() -> void {
lengthCounter = 0;
envelope.speed = 0;
envelope.useSpeedAsVolume = 0;
envelope.loopMode = 0;
envelope.reloadDecay = 0;
envelope.decayCounter = 0;
envelope.decayVolume = 0;
period = 0;
periodCounter = 1;
shortMode = 0;
lfsr = 1;
}

38
higan/fc/apu/pulse.cpp Normal file
View file

@ -0,0 +1,38 @@
auto APU::Pulse::clockLength() -> void {
if(envelope.loopMode == 0) {
if(lengthCounter) lengthCounter--;
}
}
auto APU::Pulse::clock() -> uint8 {
if(!sweep.checkPeriod()) return 0;
if(lengthCounter == 0) return 0;
static const uint dutyTable[4][8] = {
{0, 0, 0, 0, 0, 0, 0, 1}, //12.5%
{0, 0, 0, 0, 0, 0, 1, 1}, //25.0%
{0, 0, 0, 0, 1, 1, 1, 1}, //50.0%
{1, 1, 1, 1, 1, 1, 0, 0}, //25.0% (negated)
};
uint8 result = dutyTable[duty][dutyCounter] ? envelope.volume() : 0;
if(sweep.pulsePeriod < 0x008) result = 0;
if(--periodCounter == 0) {
periodCounter = (sweep.pulsePeriod + 1) * 2;
dutyCounter--;
}
return result;
}
auto APU::Pulse::power() -> void {
envelope.power();
sweep.power();
lengthCounter = 0;
duty = 0;
dutyCounter = 0;
period = 0;
periodCounter = 1;
}

View file

@ -0,0 +1,104 @@
auto APU::serialize(serializer& s) -> void {
Thread::serialize(s);
pulse[0].serialize(s);
pulse[1].serialize(s);
triangle.serialize(s);
dmc.serialize(s);
frame.serialize(s);
s.integer(enabledChannels);
s.integer(cartridgeSample);
}
auto APU::Envelope::serialize(serializer& s) -> void {
s.integer(speed);
s.integer(useSpeedAsVolume);
s.integer(loopMode);
s.integer(reloadDecay);
s.integer(decayCounter);
s.integer(decayVolume);
}
auto APU::Sweep::serialize(serializer& s) -> void {
s.integer(shift);
s.integer(decrement);
s.integer(period);
s.integer(counter);
s.integer(enable);
s.integer(reload);
s.integer(pulsePeriod);
}
auto APU::Pulse::serialize(serializer& s) -> void {
s.integer(lengthCounter);
envelope.serialize(s);
sweep.serialize(s);
s.integer(duty);
s.integer(dutyCounter);
s.integer(period);
s.integer(periodCounter);
}
auto APU::Triangle::serialize(serializer& s) -> void {
s.integer(lengthCounter);
s.integer(linearLength);
s.integer(haltLengthCounter);
s.integer(period);
s.integer(periodCounter);
s.integer(stepCounter);
s.integer(linearLengthCounter);
s.integer(reloadLinear);
}
auto APU::Noise::serialize(serializer& s) -> void {
s.integer(lengthCounter);
envelope.serialize(s);
s.integer(period);
s.integer(periodCounter);
s.integer(shortMode);
s.integer(lfsr);
}
auto APU::DMC::serialize(serializer& s) -> void {
s.integer(lengthCounter);
s.integer(irqPending);
s.integer(period);
s.integer(periodCounter);
s.integer(irqEnable);
s.integer(loopMode);
s.integer(dacLatch);
s.integer(addrLatch);
s.integer(lengthLatch);
s.integer(readAddr);
s.integer(dmaDelayCounter);
s.integer(bitCounter);
s.integer(dmaBufferValid);
s.integer(dmaBuffer);
s.integer(sampleValid);
s.integer(sample);
}
auto APU::FrameCounter::serialize(serializer& s) -> void {
s.integer(irqPending);
s.integer(mode);
s.integer(counter);
s.integer(divider);
}

40
higan/fc/apu/sweep.cpp Normal file
View file

@ -0,0 +1,40 @@
auto APU::Sweep::checkPeriod() -> bool {
if(pulsePeriod > 0x7ff) return false;
if(decrement == 0) {
if((pulsePeriod + (pulsePeriod >> shift)) & 0x800) return false;
}
return true;
}
auto APU::Sweep::clock(uint channel) -> void {
if(--counter == 0) {
counter = period + 1;
if(enable && shift && pulsePeriod > 8) {
int delta = pulsePeriod >> shift;
if(decrement) {
pulsePeriod -= delta;
if(channel == 0) pulsePeriod--;
} else if((pulsePeriod + delta) < 0x800) {
pulsePeriod += delta;
}
}
}
if(reload) {
reload = false;
counter = period + 1;
}
}
auto APU::Sweep::power() -> void {
shift = 0;
decrement = 0;
period = 0;
counter = 1;
enable = 0;
reload = 0;
pulsePeriod = 0;
}

40
higan/fc/apu/triangle.cpp Normal file
View file

@ -0,0 +1,40 @@
auto APU::Triangle::clockLength() -> void {
if(haltLengthCounter == 0) {
if(lengthCounter > 0) lengthCounter--;
}
}
auto APU::Triangle::clockLinearLength() -> void {
if(reloadLinear) {
linearLengthCounter = linearLength;
} else if(linearLengthCounter) {
linearLengthCounter--;
}
if(haltLengthCounter == 0) reloadLinear = false;
}
auto APU::Triangle::clock() -> uint8 {
uint8 result = stepCounter & 0x0f;
if((stepCounter & 0x10) == 0) result ^= 0x0f;
if(lengthCounter == 0 || linearLengthCounter == 0) return result;
if(--periodCounter == 0) {
stepCounter++;
periodCounter = period + 1;
}
return result;
}
auto APU::Triangle::power() -> void {
lengthCounter = 0;
linearLength = 0;
haltLengthCounter = 0;
period = 0;
periodCounter = 1;
stepCounter = 0;
linearLengthCounter = 0;
reloadLinear = 0;
}

View file

@ -0,0 +1,110 @@
//BANDAI-FCG
struct BandaiFCG : Board {
BandaiFCG(Markup::Node& document) : Board(document) {
}
auto main() -> void {
if(irqCounterEnable) {
if(--irqCounter == 0xffff) {
cpu.irqLine(1);
irqCounterEnable = false;
}
}
tick();
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
case 2: return 0x0000 | (addr & 0x03ff);
case 3: return 0x0400 | (addr & 0x03ff);
}
unreachable;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) {
bool region = addr & 0x4000;
uint bank = (region == 0 ? prgBank : (uint8)0x0f);
return prgrom.read((bank << 14) | (addr & 0x3fff));
}
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr >= 0x6000) {
switch(addr & 15) {
case 0x00: case 0x01: case 0x02: case 0x03:
case 0x04: case 0x05: case 0x06: case 0x07:
chrBank[addr & 7] = data;
break;
case 0x08:
prgBank = data & 0x0f;
break;
case 0x09:
mirror = data & 0x03;
break;
case 0x0a:
cpu.irqLine(0);
irqCounterEnable = data & 0x01;
irqCounter = irqLatch;
break;
case 0x0b:
irqLatch = (irqLatch & 0xff00) | (data << 0);
break;
case 0x0c:
irqLatch = (irqLatch & 0x00ff) | (data << 8);
break;
case 0x0d:
//todo: serial EEPROM support
break;
}
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
addr = (chrBank[addr >> 10] << 10) | (addr & 0x03ff);
return Board::writeCHR(addr, data);
}
auto power() -> void {
reset();
}
auto reset() -> void {
for(auto& n : chrBank) n = 0;
prgBank = 0;
mirror = 0;
irqCounterEnable = 0;
irqCounter = 0;
irqLatch = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.array(chrBank);
s.integer(prgBank);
s.integer(mirror);
s.integer(irqCounterEnable);
s.integer(irqCounter);
s.integer(irqLatch);
}
uint8 chrBank[8];
uint8 prgBank;
uint2 mirror;
bool irqCounterEnable;
uint16 irqCounter;
uint16 irqLatch;
};

View file

@ -0,0 +1,223 @@
#include "bandai-fcg.cpp"
#include "konami-vrc1.cpp"
#include "konami-vrc2.cpp"
#include "konami-vrc3.cpp"
#include "konami-vrc4.cpp"
#include "konami-vrc6.cpp"
#include "konami-vrc7.cpp"
#include "nes-axrom.cpp"
#include "nes-bnrom.cpp"
#include "nes-cnrom.cpp"
#include "nes-exrom.cpp"
#include "nes-fxrom.cpp"
#include "nes-gxrom.cpp"
#include "nes-hkrom.cpp"
#include "nes-nrom.cpp"
#include "nes-pxrom.cpp"
#include "nes-sxrom.cpp"
#include "nes-txrom.cpp"
#include "nes-uxrom.cpp"
#include "sunsoft-5b.cpp"
Board::Board(Markup::Node& document) {
cartridge.board = this;
information.type = document["game/board"].text();
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Program)"]}) {
if(prgrom.size = memory.size) prgrom.data = new uint8_t[prgrom.size]();
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read, File::Required)) {
fp->read(prgrom.data, min(prgrom.size, fp->size()));
}
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
if(prgram.size = memory.size) prgram.data = new uint8_t[prgram.size](), prgram.writable = true;
if(memory.nonVolatile) {
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read)) {
fp->read(prgram.data, min(prgram.size, fp->size()));
}
}
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Character)"]}) {
if(chrrom.size = memory.size) chrrom.data = new uint8_t[chrrom.size]();
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read, File::Required)) {
fp->read(chrrom.data, min(chrrom.size, fp->size()));
}
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Character)"]}) {
if(chrram.size = memory.size) chrram.data = new uint8_t[chrram.size](), chrram.writable = true;
if(memory.nonVolatile) {
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read)) {
fp->read(chrram.data, min(chrram.size, fp->size()));
}
}
}
}
auto Board::save() -> void {
auto document = BML::unserialize(cartridge.manifest());
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
if(memory.nonVolatile) {
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Write)) {
fp->write(prgram.data, prgram.size);
}
}
}
if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=RAM,content=Character)"]}) {
if(memory.nonVolatile) {
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Write)) {
fp->write(chrram.data, chrram.size);
}
}
}
}
auto Board::Memory::read(uint addr) const -> uint8 {
return data[mirror(addr, size)];
}
auto Board::Memory::write(uint addr, uint8 byte) -> void {
if(writable) data[mirror(addr, size)] = byte;
}
auto Board::mirror(uint addr, uint size) -> uint {
uint base = 0;
if(size) {
uint mask = 1 << 23;
while(addr >= size) {
while(!(addr & mask)) mask >>= 1;
addr -= mask;
if(size > mask) {
size -= mask;
base += mask;
}
mask >>= 1;
}
base += addr;
}
return base;
}
auto Board::main() -> void {
cartridge.step(cartridge.rate() * 4095);
tick();
}
auto Board::tick() -> void {
cartridge.step(cartridge.rate());
cartridge.synchronize(cpu);
}
auto Board::readCHR(uint addr) -> uint8 {
if(chrram.size) return chrram.data[mirror(addr, chrram.size)];
if(chrrom.size) return chrrom.data[mirror(addr, chrrom.size)];
return 0u;
}
auto Board::writeCHR(uint addr, uint8 data) -> void {
if(chrram.size) chrram.data[mirror(addr, chrram.size)] = data;
}
auto Board::power() -> void {
}
auto Board::serialize(serializer& s) -> void {
if(prgram.size) s.array(prgram.data, prgram.size);
if(chrram.size) s.array(chrram.data, chrram.size);
}
auto Board::load(string manifest) -> Board* {
auto document = BML::unserialize(manifest);
cartridge.information.title = document["game/label"].text();
string type = document["game/board"].text();
if(type == "BANDAI-FCG" ) return new BandaiFCG(document);
if(type == "KONAMI-VRC-1") return new KonamiVRC1(document);
if(type == "KONAMI-VRC-2") return new KonamiVRC2(document);
if(type == "KONAMI-VRC-3") return new KonamiVRC3(document);
if(type == "KONAMI-VRC-4") return new KonamiVRC4(document);
if(type == "KONAMI-VRC-6") return new KonamiVRC6(document);
if(type == "KONAMI-VRC-7") return new KonamiVRC7(document);
if(type == "NES-AMROM" ) return new NES_AxROM(document);
if(type == "NES-ANROM" ) return new NES_AxROM(document);
if(type == "NES-AN1ROM" ) return new NES_AxROM(document);
if(type == "NES-AOROM" ) return new NES_AxROM(document);
if(type == "NES-BNROM" ) return new NES_BNROM(document);
if(type == "NES-CNROM" ) return new NES_CNROM(document);
if(type == "NES-EKROM" ) return new NES_ExROM(document);
if(type == "NES-ELROM" ) return new NES_ExROM(document);
if(type == "NES-ETROM" ) return new NES_ExROM(document);
if(type == "NES-EWROM" ) return new NES_ExROM(document);
if(type == "NES-FJROM" ) return new NES_FxROM(document);
if(type == "NES-FKROM" ) return new NES_FxROM(document);
if(type == "NES-GNROM" ) return new NES_GxROM(document);
if(type == "NES-MHROM" ) return new NES_GxROM(document);
if(type == "NES-HKROM" ) return new NES_HKROM(document);
if(type == "NES-NROM" ) return new NES_NROM(document);
if(type == "NES-NROM-128") return new NES_NROM(document);
if(type == "NES-NROM-256") return new NES_NROM(document);
if(type == "NES-PEEOROM" ) return new NES_PxROM(document);
if(type == "NES-PNROM" ) return new NES_PxROM(document);
if(type == "NES-SAROM" ) return new NES_SxROM(document);
if(type == "NES-SBROM" ) return new NES_SxROM(document);
if(type == "NES-SCROM" ) return new NES_SxROM(document);
if(type == "NES-SC1ROM" ) return new NES_SxROM(document);
if(type == "NES-SEROM" ) return new NES_SxROM(document);
if(type == "NES-SFROM" ) return new NES_SxROM(document);
if(type == "NES-SFEXPROM") return new NES_SxROM(document);
if(type == "NES-SGROM" ) return new NES_SxROM(document);
if(type == "NES-SHROM" ) return new NES_SxROM(document);
if(type == "NES-SH1ROM" ) return new NES_SxROM(document);
if(type == "NES-SIROM" ) return new NES_SxROM(document);
if(type == "NES-SJROM" ) return new NES_SxROM(document);
if(type == "NES-SKROM" ) return new NES_SxROM(document);
if(type == "NES-SLROM" ) return new NES_SxROM(document);
if(type == "NES-SL1ROM" ) return new NES_SxROM(document);
if(type == "NES-SL2ROM" ) return new NES_SxROM(document);
if(type == "NES-SL3ROM" ) return new NES_SxROM(document);
if(type == "NES-SLRROM" ) return new NES_SxROM(document);
if(type == "NES-SMROM" ) return new NES_SxROM(document);
if(type == "NES-SNROM" ) return new NES_SxROM(document);
if(type == "NES-SOROM" ) return new NES_SxROM(document);
if(type == "NES-SUROM" ) return new NES_SxROM(document);
if(type == "NES-SXROM" ) return new NES_SxROM(document);
if(type == "NES-TBROM" ) return new NES_TxROM(document);
if(type == "NES-TEROM" ) return new NES_TxROM(document);
if(type == "NES-TFROM" ) return new NES_TxROM(document);
if(type == "NES-TGROM" ) return new NES_TxROM(document);
if(type == "NES-TKROM" ) return new NES_TxROM(document);
if(type == "NES-TKSROM" ) return new NES_TxROM(document);
if(type == "NES-TLROM" ) return new NES_TxROM(document);
if(type == "NES-TL1ROM" ) return new NES_TxROM(document);
if(type == "NES-TL2ROM" ) return new NES_TxROM(document);
if(type == "NES-TLSROM" ) return new NES_TxROM(document);
if(type == "NES-TNROM" ) return new NES_TxROM(document);
if(type == "NES-TQROM" ) return new NES_TxROM(document);
if(type == "NES-TR1ROM" ) return new NES_TxROM(document);
if(type == "NES-TSROM" ) return new NES_TxROM(document);
if(type == "NES-TVROM" ) return new NES_TxROM(document);
if(type == "NES-UNROM" ) return new NES_UxROM(document);
if(type == "NES-UOROM" ) return new NES_UxROM(document);
if(type == "SUNSOFT-5B" ) return new Sunsoft5B(document);
return nullptr;
}

View file

@ -0,0 +1,48 @@
struct Board {
struct Memory {
inline Memory(uint8_t* data, uint size) : data(data), size(size) {}
inline Memory() : data(nullptr), size(0u), writable(false) {}
inline ~Memory() { if(data) delete[] data; }
inline auto read(uint addr) const -> uint8;
inline auto write(uint addr, uint8 data) -> void;
string name;
uint8_t* data = nullptr;
uint size = 0;
bool writable = false;
};
virtual ~Board() = default;
static auto mirror(uint addr, uint size) -> uint;
Board(Markup::Node& document);
auto save() -> void;
virtual auto main() -> void;
virtual auto tick() -> void;
virtual auto readPRG(uint addr) -> uint8 = 0;
virtual auto writePRG(uint addr, uint8 data) -> void = 0;
virtual auto readCHR(uint addr) -> uint8;
virtual auto writeCHR(uint addr, uint8 data) -> void;
virtual inline auto scanline(uint y) -> void {}
virtual auto power() -> void;
virtual auto serialize(serializer&) -> void;
static auto load(string manifest) -> Board*;
struct Information {
string type;
} information;
Memory prgrom;
Memory prgram;
Memory chrrom;
Memory chrram;
};

View file

@ -0,0 +1,34 @@
struct KonamiVRC1 : Board {
KonamiVRC1(Markup::Node& document) : Board(document), vrc1(*this) {
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read(vrc1.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) return vrc1.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc1.addrCIRAM(addr));
return Board::readCHR(vrc1.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc1.addrCIRAM(addr), data);
return Board::writeCHR(vrc1.addrCHR(addr), data);
}
auto power() -> void {
vrc1.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc1.serialize(s);
}
VRC1 vrc1;
};

View file

@ -0,0 +1,51 @@
struct KonamiVRC2 : Board {
KonamiVRC2(Markup::Node& document) : Board(document), vrc2(*this) {
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return vrc2.readRAM(addr);
return prgrom.read(vrc2.addrPRG(addr));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return vrc2.writeRAM(addr, data);
bool a0 = (addr & settings.pinout.a0);
bool a1 = (addr & settings.pinout.a1);
addr &= 0xfff0;
addr |= (a0 << 0) | (a1 << 1);
return vrc2.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc2.addrCIRAM(addr));
return Board::readCHR(vrc2.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc2.addrCIRAM(addr), data);
return Board::writeCHR(vrc2.addrCHR(addr), data);
}
auto power() -> void {
vrc2.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc2.serialize(s);
}
struct Settings {
struct Pinout {
uint a0;
uint a1;
} pinout;
} settings;
VRC2 vrc2;
};

View file

@ -0,0 +1,51 @@
struct KonamiVRC3 : Board {
KonamiVRC3(Markup::Node& document) : Board(document), vrc3(*this) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto main() -> void {
vrc3.main();
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xe000) == 0x6000) return prgram.read(addr & 0x1fff);
if(addr & 0x8000) return prgrom.read(vrc3.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) return prgram.write(addr & 0x1fff, data);
if(addr & 0x8000) return vrc3.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
return chrram.read(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
return chrram.write(addr, data);
}
auto power() -> void {
vrc3.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc3.serialize(s);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
VRC3 vrc3;
};

View file

@ -0,0 +1,55 @@
struct KonamiVRC4 : Board {
KonamiVRC4(Markup::Node& document) : Board(document), vrc4(*this) {
settings.pinout.a0 = 1 << document["board/chip/pinout/a0"].natural();
settings.pinout.a1 = 1 << document["board/chip/pinout/a1"].natural();
}
auto main() -> void {
return vrc4.main();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc4.addrPRG(addr));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
bool a0 = (addr & settings.pinout.a0);
bool a1 = (addr & settings.pinout.a1);
addr &= 0xfff0;
addr |= (a1 << 1) | (a0 << 0);
return vrc4.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc4.addrCIRAM(addr));
return Board::readCHR(vrc4.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc4.addrCIRAM(addr), data);
return Board::writeCHR(vrc4.addrCHR(addr), data);
}
auto power() -> void {
vrc4.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc4.serialize(s);
}
struct Settings {
struct Pinout {
uint a0;
uint a1;
} pinout;
} settings;
VRC4 vrc4;
};

View file

@ -0,0 +1,39 @@
struct KonamiVRC6 : Board {
KonamiVRC6(Markup::Node& document) : Board(document), vrc6(*this) {
}
auto readPRG(uint addr) -> uint8{
if((addr & 0xe000) == 0x6000) return vrc6.readRAM(addr);
if(addr & 0x8000) return prgrom.read(vrc6.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) return vrc6.writeRAM(addr, data);
if(addr & 0x8000) {
addr = (addr & 0xf003);
if(prgram.size) addr = (addr & ~3) | ((addr & 2) >> 1) | ((addr & 1) << 1);
return vrc6.writeIO(addr, data);
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc6.addrCIRAM(addr));
return Board::readCHR(vrc6.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc6.addrCIRAM(addr), data);
return Board::writeCHR(vrc6.addrCHR(addr), data);
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc6.serialize(s);
}
auto main() -> void { vrc6.main(); }
auto power() -> void { vrc6.power(); }
VRC6 vrc6;
};

View file

@ -0,0 +1,41 @@
struct KonamiVRC7 : Board {
KonamiVRC7(Markup::Node& document) : Board(document), vrc7(*this) {
}
auto main() -> void {
return vrc7.main();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
return prgrom.read(vrc7.addrPRG(addr));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
return vrc7.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(vrc7.addrCIRAM(addr));
return chrram.read(vrc7.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(vrc7.addrCIRAM(addr), data);
return chrram.write(vrc7.addrCHR(addr), data);
}
auto power() -> void {
vrc7.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
vrc7.serialize(s);
}
VRC7 vrc7;
};

View file

@ -0,0 +1,46 @@
//NES-AMROM
//NES-ANROM
//NES-AN1ROM
//NES-AOROM
struct NES_AxROM : Board {
NES_AxROM(Markup::Node& document) : Board(document) {
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) {
prgBank = data & 0x0f;
mirrorSelect = data & 0x10;
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM((mirrorSelect << 10) | (addr & 0x03ff));
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM((mirrorSelect << 10) | (addr & 0x03ff), data);
return Board::writeCHR(addr, data);
}
auto power() -> void {
prgBank = 0x0f;
mirrorSelect = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(mirrorSelect);
}
uint4 prgBank;
bool mirrorSelect;
};

View file

@ -0,0 +1,47 @@
//NES-BN-ROM-01
struct NES_BNROM : Board {
NES_BNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) prgBank = data & 0x03;
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr);
}
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr, data);
}
return Board::writeCHR(addr, data);
}
auto power() -> void {
prgBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prgBank;
};

View file

@ -0,0 +1,49 @@
//NES-CNROM
struct NES_CNROM : Board {
NES_CNROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read(addr & 0x7fff);
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) chrBank = data & 0x03;
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
Board::writeCHR(addr, data);
}
auto power() -> void {
chrBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(chrBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 chrBank;
};

View file

@ -0,0 +1,47 @@
struct NES_ExROM : Board {
NES_ExROM(Markup::Node& document) : Board(document), mmc5(*this) {
revision = Revision::ELROM;
}
auto main() -> void {
mmc5.main();
}
auto readPRG(uint addr) -> uint8 {
return mmc5.readPRG(addr);
}
auto writePRG(uint addr, uint8 data) -> void {
mmc5.writePRG(addr, data);
}
auto readCHR(uint addr) -> uint8 {
return mmc5.readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
mmc5.writeCHR(addr, data);
}
auto scanline(uint y) -> void {
mmc5.scanline(y);
}
auto power() -> void {
mmc5.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc5.serialize(s);
}
enum class Revision : uint {
EKROM,
ELROM,
ETROM,
EWROM,
} revision;
MMC5 mmc5;
};

View file

@ -0,0 +1,86 @@
//MMC4
struct NES_FxROM : Board {
NES_FxROM(Markup::Node& document) : Board(document) {
revision = Revision::FKROM;
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
uint bank = addr < 0xc000 ? prgBank : (uint4)0x0f;
return prgrom.read((bank * 0x4000) | (addr & 0x3fff));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prgBank = data & 0x0f; break;
case 0xb000: chrBank[0][0] = data & 0x1f; break;
case 0xc000: chrBank[0][1] = data & 0x1f; break;
case 0xd000: chrBank[1][0] = data & 0x1f; break;
case 0xe000: chrBank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
}
auto power() -> void {
prgBank = 0;
chrBank[0][0] = 0;
chrBank[0][1] = 0;
chrBank[1][0] = 0;
chrBank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(chrBank[0][0]);
s.integer(chrBank[0][1]);
s.integer(chrBank[1][0]);
s.integer(chrBank[1][1]);
s.integer(mirror);
s.array(latch);
}
enum Revision : uint {
FJROM,
FKROM,
} revision;
uint4 prgBank;
uint5 chrBank[2][2];
bool mirror;
bool latch[2];
};

View file

@ -0,0 +1,56 @@
//NES-GNROM
//NES-MHROM
struct NES_GxROM : Board {
NES_GxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read((prgBank << 15) | (addr & 0x7fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) {
prgBank = (data & 0x30) >> 4;
chrBank = (data & 0x03) >> 0;
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
addr = (chrBank * 0x2000) + (addr & 0x1fff);
Board::writeCHR(addr, data);
}
auto power() -> void {
prgBank = 0;
chrBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(chrBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint2 prgBank;
uint2 chrBank;
};

View file

@ -0,0 +1,42 @@
struct NES_HKROM : Board {
NES_HKROM(Markup::Node& document) : Board(document), mmc6(*this) {
}
auto main() -> void {
mmc6.main();
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xf000) == 0x7000) return mmc6.readRAM(addr);
if(addr & 0x8000) return prgrom.read(mmc6.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xf000) == 0x7000) return mmc6.writeRAM(addr, data);
if(addr & 0x8000) return mmc6.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
mmc6.irqTest(addr);
if(addr & 0x2000) return ppu.readCIRAM(mmc6.addrCIRAM(addr));
return Board::readCHR(mmc6.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
mmc6.irqTest(addr);
if(addr & 0x2000) return ppu.writeCIRAM(mmc6.addrCIRAM(addr), data);
return Board::writeCHR(mmc6.addrCHR(addr), data);
}
auto power() -> void {
mmc6.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc6.serialize(s);
}
MMC6 mmc6;
};

View file

@ -0,0 +1,44 @@
//NES-NROM-128
//NES-NROM-256
struct NES_NROM : Board {
NES_NROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if(addr & 0x8000) return prgrom.read(addr);
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr & 0x07ff);
}
if(chrram.size) return chrram.read(addr);
return chrrom.read(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr & 0x07ff, data);
}
if(chrram.size) return chrram.write(addr, data);
}
auto power() -> void {
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
};

View file

@ -0,0 +1,92 @@
//MMC2
struct NES_PxROM : Board {
NES_PxROM(Markup::Node& document) : Board(document) {
revision = Revision::PNROM;
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
if(addr < 0x8000) return prgram.read(addr);
uint bank = 0;
switch((addr / 0x2000) & 3) {
case 0: bank = prgBank; break;
case 1: bank = 0x0d; break;
case 2: bank = 0x0e; break;
case 3: bank = 0x0f; break;
}
return prgrom.read((bank * 0x2000) | (addr & 0x1fff));
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr < 0x6000) return;
if(addr < 0x8000) return prgram.write(addr, data);
switch(addr & 0xf000) {
case 0xa000: prgBank = data & 0x0f; break;
case 0xb000: chrBank[0][0] = data & 0x1f; break;
case 0xc000: chrBank[0][1] = data & 0x1f; break;
case 0xd000: chrBank[1][0] = data & 0x1f; break;
case 0xe000: chrBank[1][1] = data & 0x1f; break;
case 0xf000: mirror = data & 0x01; break;
}
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::readCHR((bank * 0x1000) | (addr & 0x0fff));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
bool region = addr & 0x1000;
uint bank = chrBank[region][latch[region]];
if((addr & 0x0ff8) == 0x0fd8) latch[region] = 0;
if((addr & 0x0ff8) == 0x0fe8) latch[region] = 1;
return Board::writeCHR((bank * 0x1000) | (addr & 0x0fff), data);
}
auto power() -> void {
prgBank = 0;
chrBank[0][0] = 0;
chrBank[0][1] = 0;
chrBank[1][0] = 0;
chrBank[1][1] = 0;
mirror = 0;
latch[0] = 0;
latch[1] = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
s.integer(chrBank[0][0]);
s.integer(chrBank[0][1]);
s.integer(chrBank[1][0]);
s.integer(chrBank[1][1]);
s.integer(mirror);
s.array(latch);
}
enum Revision : uint {
PEEOROM,
PNROM,
} revision;
uint4 prgBank;
uint5 chrBank[2][2];
bool mirror;
bool latch[2];
};

View file

@ -0,0 +1,95 @@
struct NES_SxROM : Board {
NES_SxROM(Markup::Node& document) : Board(document), mmc1(*this) {
revision = Revision::SXROM;
}
auto main() -> void {
return mmc1.main();
}
auto addrRAM(uint addr) -> uint {
uint bank = 0;
if(revision == Revision::SOROM) bank = (mmc1.chrBank[0] & 0x08) >> 3;
if(revision == Revision::SUROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
if(revision == Revision::SXROM) bank = (mmc1.chrBank[0] & 0x0c) >> 2;
return (bank << 13) | (addr & 0x1fff);
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chrBank[0] & 0x10) return cpu.mdr();
}
if(mmc1.ramDisable) return 0x00;
return prgram.read(addrRAM(addr));
}
if(addr & 0x8000) {
addr = mmc1.addrPRG(addr);
if(revision == Revision::SXROM) {
addr |= ((mmc1.chrBank[0] & 0x10) >> 4) << 18;
}
return prgrom.read(addr);
}
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) {
if(revision == Revision::SNROM) {
if(mmc1.chrBank[0] & 0x10) return;
}
if(mmc1.ramDisable) return;
return prgram.write(addrRAM(addr), data);
}
if(addr & 0x8000) return mmc1.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(mmc1.addrCIRAM(addr));
return Board::readCHR(mmc1.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(mmc1.addrCIRAM(addr), data);
return Board::writeCHR(mmc1.addrCHR(addr), data);
}
auto power() -> void {
mmc1.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc1.serialize(s);
}
enum class Revision : uint {
SAROM,
SBROM,
SCROM,
SC1ROM,
SEROM,
SFROM,
SGROM,
SHROM,
SH1ROM,
SIROM,
SJROM,
SKROM,
SLROM,
SL1ROM,
SL2ROM,
SL3ROM,
SLRROM,
SMROM,
SNROM,
SOROM,
SUROM,
SXROM,
} revision;
MMC1 mmc1;
};

View file

@ -0,0 +1,61 @@
struct NES_TxROM : Board {
NES_TxROM(Markup::Node& document) : Board(document), mmc3(*this) {
revision = Revision::TLROM;
}
auto main() -> void {
mmc3.main();
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xe000) == 0x6000) return mmc3.readRAM(addr);
if(addr & 0x8000) return prgrom.read(mmc3.addrPRG(addr));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) return mmc3.writeRAM(addr, data);
if(addr & 0x8000) return mmc3.writeIO(addr, data);
}
auto readCHR(uint addr) -> uint8 {
mmc3.irqTest(addr);
if(addr & 0x2000) return ppu.readCIRAM(mmc3.addrCIRAM(addr));
return Board::readCHR(mmc3.addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
mmc3.irqTest(addr);
if(addr & 0x2000) return ppu.writeCIRAM(mmc3.addrCIRAM(addr), data);
return Board::writeCHR(mmc3.addrCHR(addr), data);
}
auto power() -> void {
mmc3.power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
mmc3.serialize(s);
}
enum class Revision : uint {
TBROM,
TEROM,
TFROM,
TGROM,
TKROM,
TKSROM,
TLROM,
TL1ROM,
TL2ROM,
TLSROM,
TNROM,
TQROM,
TR1ROM,
TSROM,
TVROM,
} revision;
MMC3 mmc3;
};

View file

@ -0,0 +1,50 @@
//NES-UNROM
//NES-UOROM
struct NES_UxROM : Board {
NES_UxROM(Markup::Node& document) : Board(document) {
settings.mirror = document["board/mirror/mode"].text() == "vertical" ? 1 : 0;
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xc000) == 0x8000) return prgrom.read((prgBank << 14) | (addr & 0x3fff));
if((addr & 0xc000) == 0xc000) return prgrom.read(( 0x0f << 14) | (addr & 0x3fff));
return cpu.mdr();
}
auto writePRG(uint addr, uint8 data) -> void {
if(addr & 0x8000) prgBank = data & 0x0f;
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.readCIRAM(addr);
}
return Board::readCHR(addr);
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
if(settings.mirror == 0) addr = ((addr & 0x0800) >> 1) | (addr & 0x03ff);
return ppu.writeCIRAM(addr, data);
}
return Board::writeCHR(addr, data);
}
auto power() -> void {
prgBank = 0;
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(prgBank);
}
struct Settings {
bool mirror; //0 = horizontal, 1 = vertical
} settings;
uint4 prgBank;
};

View file

@ -0,0 +1,217 @@
//SUNSOFT-5B
struct Sunsoft5B : Board {
Sunsoft5B(Markup::Node& document) : Board(document) {
}
struct Pulse {
auto clock() -> void {
if(--counter == 0) {
counter = frequency << 4;
duty ^= 1;
}
output = duty ? volume : (uint4)0;
if(disable) output = 0;
}
auto power() -> void {
disable = 1;
frequency = 1;
volume = 0;
counter = 0;
duty = 0;
output = 0;
}
auto serialize(serializer& s) -> void {
s.integer(disable);
s.integer(frequency);
s.integer(volume);
s.integer(counter);
s.integer(duty);
s.integer(output);
}
bool disable;
uint12 frequency;
uint4 volume;
uint16 counter; //12-bit countdown + 4-bit phase
uint1 duty;
uint4 output;
} pulse[3];
auto main() -> void {
if(irqCounterEnable) {
if(--irqCounter == 0xffff) {
cpu.irqLine(irqEnable);
}
}
pulse[0].clock();
pulse[1].clock();
pulse[2].clock();
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
apu.setSample(-output);
tick();
}
auto readPRG(uint addr) -> uint8 {
if(addr < 0x6000) return cpu.mdr();
uint8 bank = 0x3f; //((addr & 0xe000) == 0xe000
if((addr & 0xe000) == 0x6000) bank = prgBank[0];
if((addr & 0xe000) == 0x8000) bank = prgBank[1];
if((addr & 0xe000) == 0xa000) bank = prgBank[2];
if((addr & 0xe000) == 0xc000) bank = prgBank[3];
bool ramEnable = bank & 0x80;
bool ramSelect = bank & 0x40;
bank &= 0x3f;
if(ramSelect) {
if(!ramEnable) return cpu.mdr();
return prgram.data[addr & 0x1fff];
}
addr = (bank << 13) | (addr & 0x1fff);
return prgrom.read(addr);
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xe000) == 0x6000) {
prgram.data[addr & 0x1fff] = data;
}
if(addr == 0x8000) {
mmuPort = data & 0x0f;
}
if(addr == 0xa000) {
switch(mmuPort) {
case 0: chrBank[0] = data; break;
case 1: chrBank[1] = data; break;
case 2: chrBank[2] = data; break;
case 3: chrBank[3] = data; break;
case 4: chrBank[4] = data; break;
case 5: chrBank[5] = data; break;
case 6: chrBank[6] = data; break;
case 7: chrBank[7] = data; break;
case 8: prgBank[0] = data; break;
case 9: prgBank[1] = data; break;
case 10: prgBank[2] = data; break;
case 11: prgBank[3] = data; break;
case 12: mirror = data & 3; break;
case 13:
irqEnable = data & 0x80;
irqCounterEnable = data & 0x01;
if(irqEnable == 0) cpu.irqLine(0);
break;
case 14: irqCounter = (irqCounter & 0xff00) | (data << 0); break;
case 15: irqCounter = (irqCounter & 0x00ff) | (data << 8); break;
}
}
if(addr == 0xc000) {
apuPort = data & 0x0f;
}
if(addr == 0xe000) {
switch(apuPort) {
case 0: pulse[0].frequency = (pulse[0].frequency & 0xff00) | (data << 0); break;
case 1: pulse[0].frequency = (pulse[0].frequency & 0x00ff) | (data << 8); break;
case 2: pulse[1].frequency = (pulse[1].frequency & 0xff00) | (data << 0); break;
case 3: pulse[1].frequency = (pulse[1].frequency & 0x00ff) | (data << 8); break;
case 4: pulse[2].frequency = (pulse[2].frequency & 0xff00) | (data << 0); break;
case 5: pulse[2].frequency = (pulse[2].frequency & 0x00ff) | (data << 8); break;
case 7:
pulse[0].disable = data & 0x01;
pulse[1].disable = data & 0x02;
pulse[2].disable = data & 0x04;
break;
case 8: pulse[0].volume = data & 0x0f; break;
case 9: pulse[1].volume = data & 0x0f; break;
case 10: pulse[2].volume = data & 0x0f; break;
}
}
}
auto addrCHR(uint addr) -> uint {
uint8 bank = (addr >> 10) & 7;
return (chrBank[bank] << 10) | (addr & 0x03ff);
}
auto addrCIRAM(uint addr) -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal
case 2: return 0x0000 | (addr & 0x03ff); //first
case 3: return 0x0400 | (addr & 0x03ff); //second
}
unreachable;
}
auto readCHR(uint addr) -> uint8 {
if(addr & 0x2000) return ppu.readCIRAM(addrCIRAM(addr));
return Board::readCHR(addrCHR(addr));
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) return ppu.writeCIRAM(addrCIRAM(addr), data);
return Board::writeCHR(addrCHR(addr), data);
}
auto power() -> void {
for(int n : range(16)) {
double volume = 1.0 / pow(2, 1.0 / 2 * (15 - n));
dac[n] = volume * 8192.0;
}
mmuPort = 0;
apuPort = 0;
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
irqEnable = 0;
irqCounterEnable = 0;
irqCounter = 0;
pulse[0].power();
pulse[1].power();
pulse[2].power();
}
auto serialize(serializer& s) -> void {
Board::serialize(s);
s.integer(mmuPort);
s.integer(apuPort);
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(irqEnable);
s.integer(irqCounterEnable);
s.integer(irqCounter);
pulse[0].serialize(s);
pulse[1].serialize(s);
pulse[2].serialize(s);
}
uint4 mmuPort;
uint4 apuPort;
uint8 prgBank[4];
uint8 chrBank[8];
uint2 mirror;
bool irqEnable;
bool irqCounterEnable;
uint16 irqCounter;
int16 dac[16];
};

View file

@ -0,0 +1,74 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "chip/chip.cpp"
#include "board/board.cpp"
#include "serialization.cpp"
Cartridge cartridge;
auto Cartridge::Enter() -> void {
while(true) scheduler.synchronize(), cartridge.main();
}
auto Cartridge::main() -> void {
board->main();
}
auto Cartridge::load() -> bool {
if(auto loaded = platform->load(ID::Famicom, "Famicom", "fc", {"NTSC-J", "NTSC-U", "PAL"})) {
information.pathID = loaded.pathID;
information.region = loaded.option;
} else return false;
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else {
return false;
}
Board::load(information.manifest); //this call will set Cartridge::board if successful
if(!board) return false;
Hash::SHA256 sha;
sha.input(board->prgrom.data, board->prgrom.size);
sha.input(board->chrrom.data, board->chrrom.size);
information.sha256 = sha.digest();
return true;
}
auto Cartridge::save() -> void {
board->save();
}
auto Cartridge::unload() -> void {
delete board;
board = nullptr;
}
auto Cartridge::power() -> void {
create(Cartridge::Enter, system.frequency());
board->power();
}
auto Cartridge::readPRG(uint addr) -> uint8 {
return board->readPRG(addr);
}
auto Cartridge::writePRG(uint addr, uint8 data) -> void {
return board->writePRG(addr, data);
}
auto Cartridge::readCHR(uint addr) -> uint8 {
return board->readCHR(addr);
}
auto Cartridge::writeCHR(uint addr, uint8 data) -> void {
return board->writeCHR(addr, data);
}
auto Cartridge::scanline(uint y) -> void {
return board->scanline(y);
}
}

View file

@ -0,0 +1,48 @@
#include "chip/chip.hpp"
#include "board/board.hpp"
struct Cartridge : Thread {
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cartridge.cpp
static auto Enter() -> void;
auto main() -> void;
auto pathID() const -> uint { return information.pathID; }
auto region() const -> string { return information.region; }
auto hash() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
auto power() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Information {
uint pathID = 0;
string region;
string sha256;
string manifest;
string title;
} information;
//privileged:
Board* board = nullptr;
auto readPRG(uint addr) -> uint8;
auto writePRG(uint addr, uint8 data) -> void;
auto readCHR(uint addr) -> uint8;
auto writeCHR(uint addr, uint8 data) -> void;
//scanline() is for debugging purposes only:
//boards must detect scanline edges on their own
auto scanline(uint y) -> void;
};
extern Cartridge cartridge;

View file

@ -0,0 +1,17 @@
#include "mmc1.cpp"
#include "mmc3.cpp"
#include "mmc5.cpp"
#include "mmc6.cpp"
#include "vrc1.cpp"
#include "vrc2.cpp"
#include "vrc3.cpp"
#include "vrc4.cpp"
#include "vrc6.cpp"
#include "vrc7.cpp"
Chip::Chip(Board& board) : board(board) {
}
auto Chip::tick() -> void {
board.tick();
}

View file

@ -0,0 +1,8 @@
struct Board;
struct Chip {
Chip(Board& board);
auto tick() -> void;
Board& board;
};

View file

@ -0,0 +1,126 @@
struct MMC1 : Chip {
MMC1(Board& board) : Chip(board) {
revision = Revision::MMC1B2;
}
auto main() -> void {
if(writedelay) writedelay--;
tick();
}
auto addrPRG(uint addr) -> uint {
bool region = addr & 0x4000;
uint bank = (prgBank & ~1) + region;
if(prgSize) {
bank = (region == 0 ? 0x0 : 0xf);
if(region != prgMode) bank = prgBank;
}
return (bank << 14) | (addr & 0x3fff);
}
auto addrCHR(uint addr) -> uint {
bool region = addr & 0x1000;
uint bank = chrBank[region];
if(chrMode == 0) bank = (chrBank[0] & ~1) | region;
return (bank << 12) | (addr & 0x0fff);
}
auto addrCIRAM(uint addr) -> uint {
switch(mirror) {
case 0: return 0x0000 | (addr & 0x03ff);
case 1: return 0x0400 | (addr & 0x03ff);
case 2: return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
case 3: return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
}
unreachable;
}
auto writeIO(uint addr, uint8 data) -> void {
if(writedelay) return;
writedelay = 2;
if(data & 0x80) {
shiftaddr = 0;
prgSize = 1;
prgMode = 1;
} else {
shiftdata = ((data & 1) << 4) | (shiftdata >> 1);
if(++shiftaddr == 5) {
shiftaddr = 0;
switch((addr >> 13) & 3) {
case 0:
chrMode = (shiftdata & 0x10);
prgSize = (shiftdata & 0x08);
prgMode = (shiftdata & 0x04);
mirror = (shiftdata & 0x03);
break;
case 1:
chrBank[0] = (shiftdata & 0x1f);
break;
case 2:
chrBank[1] = (shiftdata & 0x1f);
break;
case 3:
ramDisable = (shiftdata & 0x10);
prgBank = (shiftdata & 0x0f);
break;
}
}
}
}
auto power() -> void {
writedelay = 0;
shiftaddr = 0;
shiftdata = 0;
chrMode = 0;
prgSize = 1;
prgMode = 1;
mirror = 0;
chrBank[0] = 0;
chrBank[1] = 1;
ramDisable = 0;
prgBank = 0;
}
auto serialize(serializer& s) -> void {
s.integer(writedelay);
s.integer(shiftaddr);
s.integer(shiftdata);
s.integer(chrMode);
s.integer(prgSize);
s.integer(prgMode);
s.integer(mirror);
s.array(chrBank);
s.integer(ramDisable);
s.integer(prgBank);
}
enum class Revision : uint {
MMC1,
MMC1A,
MMC1B1,
MMC1B2,
MMC1B3,
MMC1C,
} revision;
uint writedelay;
uint shiftaddr;
uint shiftdata;
bool chrMode;
bool prgSize; //0 = 32K, 1 = 16K
bool prgMode;
uint2 mirror; //0 = first, 1 = second, 2 = vertical, 3 = horizontal
uint5 chrBank[2];
bool ramDisable;
uint4 prgBank;
};

View file

@ -0,0 +1,181 @@
struct MMC3 : Chip {
MMC3(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqDelay) irqDelay--;
cpu.irqLine(irqLine);
tick();
}
auto irqTest(uint addr) -> void {
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
if(irqDelay == 0) {
if(irqCounter == 0) {
irqCounter = irqLatch;
} else if(--irqCounter == 0) {
if(irqEnable) irqLine = 1;
}
}
irqDelay = 6;
}
chrAbus = addr;
}
auto addrPRG(uint addr) const -> uint {
switch((addr >> 13) & 3) {
case 0:
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 1:
return (prgBank[1] << 13) | (addr & 0x1fff);
case 2:
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
unreachable;
}
auto addrCHR(uint addr) const -> uint {
if(chrMode == 0) {
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
}
return 0;
}
auto addrCIRAM(uint addr) const -> uint {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
unreachable;
}
auto readRAM(uint addr) -> uint8 {
if(ramEnable) return board.prgram.data[addr & 0x1fff];
return 0x00;
}
auto writeRAM(uint addr, uint8 data) -> void {
if(ramEnable && !ramWriteProtect) board.prgram.data[addr & 0x1fff] = data;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xe001) {
case 0x8000:
chrMode = data & 0x80;
prgMode = data & 0x40;
bankSelect = data & 0x07;
break;
case 0x8001:
switch(bankSelect) {
case 0: chrBank[0] = data & ~1; break;
case 1: chrBank[1] = data & ~1; break;
case 2: chrBank[2] = data; break;
case 3: chrBank[3] = data; break;
case 4: chrBank[4] = data; break;
case 5: chrBank[5] = data; break;
case 6: prgBank[0] = data & 0x3f; break;
case 7: prgBank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
ramEnable = data & 0x80;
ramWriteProtect = data & 0x40;
break;
case 0xc000:
irqLatch = data;
break;
case 0xc001:
irqCounter = 0;
break;
case 0xe000:
irqEnable = false;
irqLine = 0;
break;
case 0xe001:
irqEnable = true;
break;
}
}
auto power() -> void {
chrMode = 0;
prgMode = 0;
bankSelect = 0;
prgBank[0] = 0;
prgBank[1] = 0;
chrBank[0] = 0;
chrBank[1] = 0;
chrBank[2] = 0;
chrBank[3] = 0;
chrBank[4] = 0;
chrBank[5] = 0;
mirror = 0;
ramEnable = 1;
ramWriteProtect = 0;
irqLatch = 0;
irqCounter = 0;
irqEnable = false;
irqDelay = 0;
irqLine = 0;
chrAbus = 0;
}
auto serialize(serializer& s) -> void {
s.integer(chrMode);
s.integer(prgMode);
s.integer(bankSelect);
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(ramEnable);
s.integer(ramWriteProtect);
s.integer(irqLatch);
s.integer(irqCounter);
s.integer(irqEnable);
s.integer(irqDelay);
s.integer(irqLine);
s.integer(chrAbus);
}
bool chrMode;
bool prgMode;
uint3 bankSelect;
uint8 prgBank[2];
uint8 chrBank[6];
bool mirror;
bool ramEnable;
bool ramWriteProtect;
uint8 irqLatch;
uint8 irqCounter;
bool irqEnable;
uint irqDelay;
bool irqLine;
uint16 chrAbus;
};

View file

@ -0,0 +1,493 @@
struct MMC5 : Chip {
MMC5(Board& board) : Chip(board) {
revision = Revision::MMC5;
}
auto main() -> void {
//scanline() resets this; if no scanlines detected, enter video blanking period
if(++cpuCycleCounter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
cpu.irqLine(irqEnable && irqPending);
tick();
}
auto scanline(uint y) -> void {
//used for testing only, to verify MMC5 scanline detection is accurate:
//if(y != vcounter && y <= 240) print(y, " vs ", vcounter, "\n");
}
auto accessPRG(bool write, uint addr, uint8 data = 0x00) -> uint8 {
uint bank;
if((addr & 0xe000) == 0x6000) {
bank = (ramSelect << 2) | ramBank;
addr &= 0x1fff;
} else if(prgMode == 0) {
bank = prgBank[3] & ~3;
addr &= 0x7fff;
} else if(prgMode == 1) {
if((addr & 0xc000) == 0x8000) bank = (prgBank[1] & ~1);
if((addr & 0xe000) == 0xc000) bank = (prgBank[3] & ~1);
addr &= 0x3fff;
} else if(prgMode == 2) {
if((addr & 0xe000) == 0x8000) bank = (prgBank[1] & ~1) | 0;
if((addr & 0xe000) == 0xa000) bank = (prgBank[1] & ~1) | 1;
if((addr & 0xe000) == 0xc000) bank = (prgBank[2]);
if((addr & 0xe000) == 0xe000) bank = (prgBank[3]);
addr &= 0x1fff;
} else if(prgMode == 3) {
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
if((addr & 0xe000) == 0xe000) bank = prgBank[3];
addr &= 0x1fff;
}
bool rom = bank & 0x80;
bank &= 0x7f;
if(write == false) {
if(rom) {
return board.prgrom.read((bank << 13) | addr);
} else {
return board.prgram.read((bank << 13) | addr);
}
} else {
if(rom) {
board.prgrom.write((bank << 13) | addr, data);
} else {
if(prgramWriteProtect[0] == 2 && prgramWriteProtect[1] == 1) {
board.prgram.write((bank << 13) | addr, data);
}
}
return 0x00;
}
}
auto readPRG(uint addr) -> uint8 {
if((addr & 0xfc00) == 0x5c00) {
if(exramMode >= 2) return exram[addr & 0x03ff];
return cpu.mdr();
}
if(addr >= 0x6000) {
return accessPRG(0, addr);
}
switch(addr) {
case 0x5204: {
uint8 result = (irqPending << 7) | (inFrame << 6);
irqPending = false;
return result;
}
case 0x5205: return (multiplier * multiplicand) >> 0;
case 0x5206: return (multiplier * multiplicand) >> 8;
}
return 0x00;
}
auto writePRG(uint addr, uint8 data) -> void {
if((addr & 0xfc00) == 0x5c00) {
//writes 0x00 *during* Vblank (not during screen rendering ...)
if(exramMode == 0 || exramMode == 1) exram[addr & 0x03ff] = inFrame ? data : (uint8)0x00;
if(exramMode == 2) exram[addr & 0x03ff] = data;
return;
}
if(addr >= 0x6000) {
accessPRG(1, addr, data);
return;
}
switch(addr) {
case 0x2000:
sprite8x16 = data & 0x20;
break;
case 0x2001:
//if BG+sprites are disabled; enter video blanking period
if((data & 0x18) == 0) blank();
break;
case 0x5100: prgMode = data & 3; break;
case 0x5101: chrMode = data & 3; break;
case 0x5102: prgramWriteProtect[0] = data & 3; break;
case 0x5103: prgramWriteProtect[1] = data & 3; break;
case 0x5104:
exramMode = data & 3;
break;
case 0x5105:
nametableMode[0] = (data & 0x03) >> 0;
nametableMode[1] = (data & 0x0c) >> 2;
nametableMode[2] = (data & 0x30) >> 4;
nametableMode[3] = (data & 0xc0) >> 6;
break;
case 0x5106:
fillmodeTile = data;
break;
case 0x5107:
fillmodeColor = data & 3;
fillmodeColor |= fillmodeColor << 2;
fillmodeColor |= fillmodeColor << 4;
break;
case 0x5113:
ramSelect = data & 0x04;
ramBank = data & 0x03;
break;
case 0x5114: prgBank[0] = data; break;
case 0x5115: prgBank[1] = data; break;
case 0x5116: prgBank[2] = data; break;
case 0x5117: prgBank[3] = data | 0x80; break;
case 0x5120: chrSpriteBank[0] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5121: chrSpriteBank[1] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5122: chrSpriteBank[2] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5123: chrSpriteBank[3] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5124: chrSpriteBank[4] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5125: chrSpriteBank[5] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5126: chrSpriteBank[6] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5127: chrSpriteBank[7] = (chrBankHi << 8) | data; chrActive = 0; break;
case 0x5128: chrBGBank[0] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x5129: chrBGBank[1] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x512a: chrBGBank[2] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x512b: chrBGBank[3] = (chrBankHi << 8) | data; chrActive = 1; break;
case 0x5130:
chrBankHi = data & 3;
break;
case 0x5200:
vsEnable = data & 0x80;
vsSide = data & 0x40;
vsTile = data & 0x1f;
break;
case 0x5201:
vsScroll = data;
break;
case 0x5202:
vsBank = data;
break;
case 0x5203:
irqLine = data;
break;
case 0x5204:
irqEnable = data & 0x80;
break;
case 0x5205:
multiplicand = data;
break;
case 0x5206:
multiplier = data;
break;
}
}
auto chrSpriteAddr(uint addr) -> uint {
if(chrMode == 0) {
auto bank = chrSpriteBank[7];
return (bank * 0x2000) + (addr & 0x1fff);
}
if(chrMode == 1) {
auto bank = chrSpriteBank[(addr / 0x1000) * 4 + 3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chrMode == 2) {
auto bank = chrSpriteBank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chrMode == 3) {
auto bank = chrSpriteBank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
unreachable;
}
auto chrBGAddr(uint addr) -> uint {
addr &= 0x0fff;
if(chrMode == 0) {
auto bank = chrBGBank[3];
return (bank * 0x2000) + (addr & 0x0fff);
}
if(chrMode == 1) {
auto bank = chrBGBank[3];
return (bank * 0x1000) + (addr & 0x0fff);
}
if(chrMode == 2) {
auto bank = chrBGBank[(addr / 0x0800) * 2 + 1];
return (bank * 0x0800) + (addr & 0x07ff);
}
if(chrMode == 3) {
auto bank = chrBGBank[(addr / 0x0400)];
return (bank * 0x0400) + (addr & 0x03ff);
}
unreachable;
}
auto chrVSAddr(uint addr) -> uint {
return (vsBank * 0x1000) + (addr & 0x0ff8) + (vsVpos & 7);
}
auto blank() -> void {
inFrame = false;
}
auto scanline() -> void {
hcounter = 0;
if(inFrame == false) {
inFrame = true;
irqPending = false;
vcounter = 0;
} else {
if(vcounter == irqLine) irqPending = true;
vcounter++;
}
cpuCycleCounter = 0;
}
auto readCIRAM(uint addr) -> uint8 {
if(vsFetch && (hcounter & 2) == 0) return exram[vsVpos / 8 * 32 + vsHpos / 8];
if(vsFetch && (hcounter & 2) != 0) return exram[vsVpos / 32 * 8 + vsHpos / 32 + 0x03c0];
switch(nametableMode[(addr >> 10) & 3]) {
case 0: return ppu.readCIRAM(0x0000 | (addr & 0x03ff));
case 1: return ppu.readCIRAM(0x0400 | (addr & 0x03ff));
case 2: return exramMode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
case 3: return (hcounter & 2) == 0 ? fillmodeTile : fillmodeColor;
}
unreachable;
}
auto readCHR(uint addr) -> uint8 {
chrAccess[0] = chrAccess[1];
chrAccess[1] = chrAccess[2];
chrAccess[2] = chrAccess[3];
chrAccess[3] = addr;
//detect two unused nametable fetches at end of each scanline
if((chrAccess[0] & 0x2000) == 0
&& (chrAccess[1] & 0x2000)
&& (chrAccess[2] & 0x2000)
&& (chrAccess[3] & 0x2000)) scanline();
if(inFrame == false) {
vsFetch = false;
if(addr & 0x2000) return readCIRAM(addr);
return board.chrrom.read(chrActive ? chrBGAddr(addr) : chrSpriteAddr(addr));
}
bool bgFetch = (hcounter < 256 || hcounter >= 320);
uint8 result = 0x00;
if((hcounter & 7) == 0) {
vsHpos = hcounter >= 320 ? hcounter - 320 : hcounter + 16;
vsVpos = vcounter + vsScroll;
vsFetch = vsEnable && bgFetch && exramMode < 2
&& (vsSide ? vsHpos / 8 >= vsTile : vsHpos / 8 < vsTile);
if(vsVpos >= 240) vsVpos -= 240;
result = readCIRAM(addr);
exbank = (chrBankHi << 6) | (exram[addr & 0x03ff] & 0x3f);
exattr = exram[addr & 0x03ff] >> 6;
exattr |= exattr << 2;
exattr |= exattr << 4;
} else if((hcounter & 7) == 2) {
result = readCIRAM(addr);
if(bgFetch && exramMode == 1) result = exattr;
} else {
if(vsFetch) result = board.chrrom.read(chrVSAddr(addr));
else if(sprite8x16 ? bgFetch : chrActive) result = board.chrrom.read(chrBGAddr(addr));
else result = board.chrrom.read(chrSpriteAddr(addr));
if(bgFetch && exramMode == 1) result = board.chrrom.read(exbank * 0x1000 + (addr & 0x0fff));
}
hcounter += 2;
return result;
}
auto writeCHR(uint addr, uint8 data) -> void {
if(addr & 0x2000) {
switch(nametableMode[(addr >> 10) & 3]) {
case 0: return ppu.writeCIRAM(0x0000 | (addr & 0x03ff), data);
case 1: return ppu.writeCIRAM(0x0400 | (addr & 0x03ff), data);
case 2: exram[addr & 0x03ff] = data; break;
}
}
}
auto power() -> void {
for(auto& n : exram) n = 0xff;
prgMode = 3;
chrMode = 0;
for(auto& n : prgramWriteProtect) n = 0;
exramMode = 0;
for(auto& n : nametableMode) n = 0;
fillmodeTile = 0;
fillmodeColor = 0;
ramSelect = 0;
ramBank = 0;
prgBank[0] = 0x00;
prgBank[1] = 0x00;
prgBank[2] = 0x00;
prgBank[3] = 0xff;
for(auto& n : chrSpriteBank) n = 0;
for(auto& n : chrBGBank) n = 0;
chrBankHi = 0;
vsEnable = 0;
vsSide = 0;
vsTile = 0;
vsScroll = 0;
vsBank = 0;
irqLine = 0;
irqEnable = 0;
multiplicand = 0;
multiplier = 0;
cpuCycleCounter = 0;
irqCounter = 0;
irqPending = 0;
inFrame = 0;
vcounter = 0;
hcounter = 0;
for(auto& n : chrAccess) n = 0;
chrActive = 0;
sprite8x16 = 0;
exbank = 0;
exattr = 0;
vsFetch = 0;
vsVpos = 0;
vsHpos = 0;
}
auto serialize(serializer& s) -> void {
s.array(exram);
s.integer(prgMode);
s.integer(chrMode);
for(auto& n : prgramWriteProtect) s.integer(n);
s.integer(exramMode);
for(auto& n : nametableMode) s.integer(n);
s.integer(fillmodeTile);
s.integer(fillmodeColor);
s.integer(ramSelect);
s.integer(ramBank);
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrSpriteBank) s.integer(n);
for(auto& n : chrBGBank) s.integer(n);
s.integer(chrBankHi);
s.integer(vsEnable);
s.integer(vsSide);
s.integer(vsTile);
s.integer(vsScroll);
s.integer(vsBank);
s.integer(irqLine);
s.integer(irqEnable);
s.integer(multiplicand);
s.integer(multiplier);
s.integer(cpuCycleCounter);
s.integer(irqCounter);
s.integer(irqPending);
s.integer(inFrame);
s.integer(vcounter);
s.integer(hcounter);
for(auto& n : chrAccess) s.integer(n);
s.integer(chrActive);
s.integer(sprite8x16);
s.integer(exbank);
s.integer(exattr);
s.integer(vsFetch);
s.integer(vsVpos);
s.integer(vsHpos);
}
enum class Revision : uint {
MMC5,
MMC5B,
} revision;
uint8 exram[1024];
//programmable registers
uint2 prgMode; //$5100
uint2 chrMode; //$5101
uint2 prgramWriteProtect[2]; //$5102,$5103
uint2 exramMode; //$5104
uint2 nametableMode[4]; //$5105
uint8 fillmodeTile; //$5106
uint8 fillmodeColor; //$5107
bool ramSelect; //$5113
uint2 ramBank; //$5113
uint8 prgBank[4]; //$5114-5117
uint10 chrSpriteBank[8]; //$5120-5127
uint10 chrBGBank[4]; //$5128-512b
uint2 chrBankHi; //$5130
bool vsEnable; //$5200
bool vsSide; //$5200
uint5 vsTile; //$5200
uint8 vsScroll; //$5201
uint8 vsBank; //$5202
uint8 irqLine; //$5203
bool irqEnable; //$5204
uint8 multiplicand; //$5205
uint8 multiplier; //$5206
//status registers
uint cpuCycleCounter;
uint irqCounter;
bool irqPending;
bool inFrame;
uint vcounter;
uint hcounter;
uint16 chrAccess[4];
bool chrActive;
bool sprite8x16;
uint8 exbank;
uint8 exattr;
bool vsFetch;
uint8 vsVpos;
uint8 vsHpos;
};

View file

@ -0,0 +1,192 @@
struct MMC6 : Chip {
MMC6(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqDelay) irqDelay--;
cpu.irqLine(irqLine);
tick();
}
auto irqTest(uint addr) -> void {
if(!(chrAbus & 0x1000) && (addr & 0x1000)) {
if(irqDelay == 0) {
if(irqCounter == 0) {
irqCounter = irqLatch;
} else if(--irqCounter == 0) {
if(irqEnable) irqLine = 1;
}
}
irqDelay = 6;
}
chrAbus = addr;
}
auto addrPRG(uint addr) const -> uint {
switch((addr >> 13) & 3) {
case 0:
if(prgMode == 1) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 1:
return (prgBank[1] << 13) | (addr & 0x1fff);
case 2:
if(prgMode == 0) return (0x3e << 13) | (addr & 0x1fff);
return (prgBank[0] << 13) | (addr & 0x1fff);
case 3:
return (0x3f << 13) | (addr & 0x1fff);
}
unreachable;
}
auto addrCHR(uint addr) const -> uint {
if(chrMode == 0) {
if(addr <= 0x07ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x0fff) return (chrBank[1] << 10) | (addr & 0x07ff);
if(addr <= 0x13ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x1bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x1fff) return (chrBank[5] << 10) | (addr & 0x03ff);
} else {
if(addr <= 0x03ff) return (chrBank[2] << 10) | (addr & 0x03ff);
if(addr <= 0x07ff) return (chrBank[3] << 10) | (addr & 0x03ff);
if(addr <= 0x0bff) return (chrBank[4] << 10) | (addr & 0x03ff);
if(addr <= 0x0fff) return (chrBank[5] << 10) | (addr & 0x03ff);
if(addr <= 0x17ff) return (chrBank[0] << 10) | (addr & 0x07ff);
if(addr <= 0x1fff) return (chrBank[1] << 10) | (addr & 0x07ff);
}
return 0;
}
auto addrCIRAM(uint addr) const -> uint {
if(mirror == 0) return ((addr & 0x0400) >> 0) | (addr & 0x03ff);
if(mirror == 1) return ((addr & 0x0800) >> 1) | (addr & 0x03ff);
unreachable;
}
auto readRAM(uint addr) -> uint8 {
if(ramEnable == false) return cpu.mdr();
if(ramReadable[0] == false && ramReadable[1] == false) return cpu.mdr();
bool region = addr & 0x0200;
if(ramReadable[region] == false) return 0x00;
return board.prgram.read((region * 0x0200) + (addr & 0x01ff));
}
auto writeRAM(uint addr, uint8 data) -> void {
if(ramEnable == false) return;
bool region = addr & 0x0200;
if(ramWritable[region] == false) return;
return board.prgram.write((region * 0x0200) + (addr & 0x01ff), data);
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xe001) {
case 0x8000:
chrMode = data & 0x80;
prgMode = data & 0x40;
ramEnable = data & 0x20;
bankSelect = data & 0x07;
if(ramEnable == false) {
for(auto &n : ramReadable) n = false;
for(auto &n : ramWritable) n = false;
}
break;
case 0x8001:
switch(bankSelect) {
case 0: chrBank[0] = data & ~1; break;
case 1: chrBank[1] = data & ~1; break;
case 2: chrBank[2] = data; break;
case 3: chrBank[3] = data; break;
case 4: chrBank[4] = data; break;
case 5: chrBank[5] = data; break;
case 6: prgBank[0] = data & 0x3f; break;
case 7: prgBank[1] = data & 0x3f; break;
}
break;
case 0xa000:
mirror = data & 0x01;
break;
case 0xa001:
if(ramEnable == false) break;
ramReadable[1] = data & 0x80;
ramWritable[1] = data & 0x40;
ramReadable[0] = data & 0x20;
ramWritable[0] = data & 0x10;
break;
case 0xc000:
irqLatch = data;
break;
case 0xc001:
irqCounter = 0;
break;
case 0xe000:
irqEnable = false;
irqLine = 0;
break;
case 0xe001:
irqEnable = true;
break;
}
}
auto power() -> void {
chrMode = 0;
prgMode = 0;
ramEnable = 0;
bankSelect = 0;
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
for(auto& n : ramReadable) n = 0;
for(auto& n : ramWritable) n = 0;
irqLatch = 0;
irqCounter = 0;
irqEnable = 0;
irqDelay = 0;
irqLine = 0;
chrAbus = 0;
}
auto serialize(serializer& s) -> void {
s.integer(chrMode);
s.integer(prgMode);
s.integer(ramEnable);
s.integer(bankSelect);
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrBank) s.integer(n);
s.integer(mirror);
for(auto& n : ramReadable) s.integer(n);
for(auto& n : ramWritable) s.integer(n);
s.integer(irqLatch);
s.integer(irqCounter);
s.integer(irqEnable);
s.integer(irqDelay);
s.integer(irqLine);
s.integer(chrAbus);
}
bool chrMode;
bool prgMode;
bool ramEnable;
uint3 bankSelect;
uint8 prgBank[2];
uint8 chrBank[6];
bool mirror;
bool ramReadable[2];
bool ramWritable[2];
uint8 irqLatch;
uint8 irqCounter;
bool irqEnable;
uint irqDelay;
bool irqLine;
uint16 chrAbus;
};

View file

@ -0,0 +1,75 @@
struct VRC1 : Chip {
VRC1(Board& board) : Chip(board) {
}
auto addrPRG(uint addr) const -> uint {
uint bank = 0x0f;
if((addr & 0xe000) == 0x8000) bank = prgBank[0];
if((addr & 0xe000) == 0xa000) bank = prgBank[1];
if((addr & 0xe000) == 0xc000) bank = prgBank[2];
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBankLo[(bool)(addr & 0x1000)];
bank |= chrBankHi[(bool)(addr & 0x1000)] << 4;
return (bank * 0x1000) + (addr & 0x0fff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
}
throw;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xf000) {
case 0x8000:
prgBank[0] = data & 0x0f;
break;
case 0x9000:
chrBankHi[1] = data & 0x04;
chrBankHi[0] = data & 0x02;
mirror = data & 0x01;
break;
case 0xa000:
prgBank[1] = data & 0x0f;
break;
case 0xc000:
prgBank[2] = data & 0x0f;
break;
case 0xe000:
chrBankLo[0] = data & 0x0f;
break;
case 0xf000:
chrBankLo[1] = data & 0x0f;
break;
}
}
auto power() -> void {
for(auto& n : prgBank) n = 0;
for(auto& n : chrBankLo) n = 0;
for(auto& n : chrBankHi) n = 0;
mirror = 0;
}
auto serialize(serializer& s) -> void {
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrBankLo) s.integer(n);
for(auto& n : chrBankHi) s.integer(n);
s.integer(mirror);
}
uint4 prgBank[3];
uint4 chrBankLo[2];
bool chrBankHi[2];
bool mirror;
};

View file

@ -0,0 +1,105 @@
struct VRC2 : Chip {
VRC2(Board& board) : Chip(board) {
}
auto addrPRG(uint addr) const -> uint {
uint bank;
switch(addr & 0xe000) {
case 0x8000: bank = prgBank[0]; break;
case 0xa000: bank = prgBank[1]; break;
case 0xc000: bank = 0x1e; break;
case 0xe000: bank = 0x1f; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
throw;
}
auto readRAM(uint addr) -> uint8 {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) return cpu.mdr() | latch;
return cpu.mdr();
}
return board.prgram.read(addr & 0x1fff);
}
auto writeRAM(uint addr, uint8 data) -> void {
if(board.prgram.size == 0) {
if((addr & 0xf000) == 0x6000) latch = data & 0x01;
return;
}
return board.prgram.write(addr & 0x1fff, data);
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prgBank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001: case 0x9002: case 0x9003:
mirror = data & 0x03;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prgBank[1] = data & 0x1f;
break;
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
}
}
auto power() -> void {
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
latch = 0;
}
auto serialize(serializer& s) -> void {
for(auto& n : prgBank) s.integer(n);
for(auto& n : chrBank) s.integer(n);
s.integer(mirror);
s.integer(latch);
}
uint5 prgBank[2];
uint8 chrBank[8];
uint2 mirror;
bool latch;
};

View file

@ -0,0 +1,89 @@
struct VRC3 : Chip {
VRC3(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) { //16-bit
if(++irqCounter.w == 0) {
irqLine = 1;
irqEnable = irqAcknowledge;
irqCounter.w = irqLatch;
}
}
if(irqMode == 1) { //8-bit
if(++irqCounter.l == 0) {
irqLine = 1;
irqEnable = irqAcknowledge;
irqCounter.l = irqLatch;
}
}
}
cpu.irqLine(irqLine);
tick();
}
auto addrPRG(uint addr) const -> uint {
uint bank = (addr < 0xc000 ? (uint)prgBank : 0x0f);
return (bank * 0x4000) + (addr & 0x3fff);
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr & 0xf000) {
case 0x8000: irqLatch = (irqLatch & 0xfff0) | ((data & 0x0f) << 0); break;
case 0x9000: irqLatch = (irqLatch & 0xff0f) | ((data & 0x0f) << 4); break;
case 0xa000: irqLatch = (irqLatch & 0xf0ff) | ((data & 0x0f) << 8); break;
case 0xb000: irqLatch = (irqLatch & 0x0fff) | ((data & 0x0f) << 12); break;
case 0xc000:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) irqCounter.w = irqLatch;
break;
case 0xd000:
irqLine = 0;
irqEnable = irqAcknowledge;
break;
case 0xf000:
prgBank = data & 0x0f;
break;
}
}
auto power() -> void {
prgBank = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqLatch = 0;
irqCounter.w = 0;
irqLine = 0;
}
auto serialize(serializer& s) -> void {
s.integer(prgBank);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqLatch);
s.integer(irqCounter.w);
s.integer(irqLine);
}
uint4 prgBank;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint16 irqLatch;
struct {
union {
uint16_t w;
struct { uint8_t order_lsb2(l, h); };
};
} irqCounter;
bool irqLine;
};

View file

@ -0,0 +1,173 @@
struct VRC4 : Chip {
VRC4(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) {
irqScalar -= 3;
if(irqScalar <= 0) {
irqScalar += 341;
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
if(irqMode == 1) {
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
cpu.irqLine(irqLine);
tick();
}
auto addrPRG(uint addr) const -> uint {
uint bank = 0, banks = board.prgrom.size / 0x2000;
switch(addr & 0xe000) {
case 0x8000: bank = prgMode == 0 ? (uint)prgBank[0] : banks - 2; break;
case 0xa000: bank = prgBank[1]; break;
case 0xc000: bank = prgMode == 0 ? banks - 2 : (uint)prgBank[0]; break;
case 0xe000: bank = banks - 1; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
throw;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prgBank[0] = data & 0x1f;
break;
case 0x9000: case 0x9001:
mirror = data & 0x03;
break;
case 0x9002: case 0x9003:
prgMode = data & 0x02;
break;
case 0xa000: case 0xa001: case 0xa002: case 0xa003:
prgBank[1] = data & 0x1f;
break;
case 0xb000: chrBank[0] = (chrBank[0] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb001: chrBank[0] = (chrBank[0] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xb002: chrBank[1] = (chrBank[1] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xb003: chrBank[1] = (chrBank[1] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc000: chrBank[2] = (chrBank[2] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc001: chrBank[2] = (chrBank[2] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xc002: chrBank[3] = (chrBank[3] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xc003: chrBank[3] = (chrBank[3] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd000: chrBank[4] = (chrBank[4] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd001: chrBank[4] = (chrBank[4] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xd002: chrBank[5] = (chrBank[5] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xd003: chrBank[5] = (chrBank[5] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe000: chrBank[6] = (chrBank[6] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe001: chrBank[6] = (chrBank[6] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xe002: chrBank[7] = (chrBank[7] & 0xf0) | ((data & 0x0f) << 0); break;
case 0xe003: chrBank[7] = (chrBank[7] & 0x0f) | ((data & 0x0f) << 4); break;
case 0xf000:
irqLatch = (irqLatch & 0xf0) | ((data & 0x0f) << 0);
break;
case 0xf001:
irqLatch = (irqLatch & 0x0f) | ((data & 0x0f) << 4);
break;
case 0xf002:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) {
irqCounter = irqLatch;
irqScalar = 341;
}
irqLine = 0;
break;
case 0xf003:
irqEnable = irqAcknowledge;
irqLine = 0;
break;
}
}
auto power() -> void {
prgMode = 0;
for(auto& n : prgBank) n = 0;
mirror = 0;
for(auto& n : chrBank) n = 0;
irqLatch = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqCounter = 0;
irqScalar = 0;
irqLine = 0;
}
auto serialize(serializer& s) -> void {
s.integer(prgMode);
for(auto& n : prgBank) s.integer(n);
s.integer(mirror);
for(auto& n : chrBank) s.integer(n);
s.integer(irqLatch);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqCounter);
s.integer(irqScalar);
s.integer(irqLine);
}
bool prgMode;
uint5 prgBank[2];
uint2 mirror;
uint8 chrBank[8];
uint8 irqLatch;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint8 irqCounter;
int irqScalar;
bool irqLine;
};

View file

@ -0,0 +1,312 @@
struct VRC6 : Chip {
VRC6(Board& board) : Chip(board) {
}
struct Pulse {
auto clock() -> void {
if(--divider == 0) {
divider = frequency + 1;
cycle++;
output = (mode == 1 || cycle > duty) ? volume : (uint4)0;
}
if(enable == false) output = 0;
}
auto serialize(serializer& s) -> void {
s.integer(mode);
s.integer(duty);
s.integer(volume);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(cycle);
s.integer(output);
}
bool mode;
uint3 duty;
uint4 volume;
bool enable;
uint12 frequency;
uint12 divider;
uint4 cycle;
uint4 output;
} pulse1, pulse2;
struct Sawtooth {
auto clock() -> void {
if(--divider == 0) {
divider = frequency + 1;
if(++phase == 0) {
accumulator += rate;
if(++stage == 7) {
stage = 0;
accumulator = 0;
}
}
}
output = accumulator >> 3;
if(enable == false) output = 0;
}
auto serialize(serializer& s) -> void {
s.integer(rate);
s.integer(enable);
s.integer(frequency);
s.integer(divider);
s.integer(phase);
s.integer(stage);
s.integer(accumulator);
s.integer(output);
}
uint6 rate;
bool enable;
uint12 frequency;
uint12 divider;
uint1 phase;
uint3 stage;
uint8 accumulator;
uint5 output;
} sawtooth;
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) {
irqScalar -= 3;
if(irqScalar <= 0) {
irqScalar += 341;
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
if(irqMode == 1) {
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
cpu.irqLine(irqLine);
pulse1.clock();
pulse2.clock();
sawtooth.clock();
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
apu.setSample(-output);
tick();
}
auto addrPRG(uint addr) const -> uint {
if((addr & 0xc000) == 0x8000) return (prgBank[0] << 14) | (addr & 0x3fff);
if((addr & 0xe000) == 0xc000) return (prgBank[1] << 13) | (addr & 0x1fff);
if((addr & 0xe000) == 0xe000) return ( 0xff << 13) | (addr & 0x1fff);
return 0x00;
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[(addr >> 10) & 7];
return (bank << 10) | (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
unreachable;
}
auto readRAM(uint addr) -> uint8 {
return board.prgram.data[addr & 0x1fff];
}
auto writeRAM(uint addr, uint8 data) -> void {
board.prgram.data[addr & 0x1fff] = data;
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: case 0x8001: case 0x8002: case 0x8003:
prgBank[0] = data;
break;
case 0x9000:
pulse1.mode = data & 0x80;
pulse1.duty = (data & 0x70) >> 4;
pulse1.volume = data & 0x0f;
break;
case 0x9001:
pulse1.frequency = (pulse1.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0x9002:
pulse1.frequency = (pulse1.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse1.enable = data & 0x80;
break;
case 0xa000:
pulse2.mode = data & 0x80;
pulse2.duty = (data & 0x70) >> 4;
pulse2.volume = data & 0x0f;
break;
case 0xa001:
pulse2.frequency = (pulse2.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xa002:
pulse2.frequency = (pulse2.frequency & 0x00ff) | ((data & 0x0f) << 8);
pulse2.enable = data & 0x80;
break;
case 0xb000:
sawtooth.rate = data & 0x3f;
break;
case 0xb001:
sawtooth.frequency = (sawtooth.frequency & 0x0f00) | ((data & 0xff) << 0);
break;
case 0xb002:
sawtooth.frequency = (sawtooth.frequency & 0x00ff) | ((data & 0x0f) << 8);
sawtooth.enable = data & 0x80;
break;
case 0xb003:
mirror = (data >> 2) & 3;
break;
case 0xc000: case 0xc001: case 0xc002: case 0xc003:
prgBank[1] = data;
break;
case 0xd000: case 0xd001: case 0xd002: case 0xd003:
chrBank[0 + (addr & 3)] = data;
break;
case 0xe000: case 0xe001: case 0xe002: case 0xe003:
chrBank[4 + (addr & 3)] = data;
break;
case 0xf000:
irqLatch = data;
break;
case 0xf001:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) {
irqCounter = irqLatch;
irqScalar = 341;
}
irqLine = 0;
break;
case 0xf002:
irqEnable = irqAcknowledge;
irqLine = 0;
break;
}
}
auto power() -> void {
prgBank[0] = 0;
prgBank[1] = 0;
chrBank[0] = 0;
chrBank[1] = 0;
chrBank[2] = 0;
chrBank[3] = 0;
chrBank[4] = 0;
chrBank[5] = 0;
chrBank[6] = 0;
chrBank[7] = 0;
mirror = 0;
irqLatch = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqCounter = 0;
irqScalar = 0;
irqLine = 0;
pulse1.mode = 0;
pulse1.duty = 0;
pulse1.volume = 0;
pulse1.enable = 0;
pulse1.frequency = 0;
pulse1.divider = 1;
pulse1.cycle = 0;
pulse1.output = 0;
pulse2.mode = 0;
pulse2.duty = 0;
pulse2.volume = 0;
pulse2.enable = 0;
pulse2.frequency = 0;
pulse2.divider = 1;
pulse2.cycle = 0;
pulse2.output = 0;
sawtooth.rate = 0;
sawtooth.enable = 0;
sawtooth.frequency = 0;
sawtooth.divider = 1;
sawtooth.phase = 0;
sawtooth.stage = 0;
sawtooth.accumulator = 0;
sawtooth.output = 0;
}
auto serialize(serializer& s) -> void {
pulse1.serialize(s);
pulse2.serialize(s);
sawtooth.serialize(s);
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(irqLatch);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqCounter);
s.integer(irqScalar);
s.integer(irqLine);
}
uint8 prgBank[2];
uint8 chrBank[8];
uint2 mirror;
uint8 irqLatch;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint8 irqCounter;
int irqScalar;
bool irqLine;
};

View file

@ -0,0 +1,144 @@
//Konami VRC7
//Yamaha YM2413 OPLL audio - not emulated
struct VRC7 : Chip {
VRC7(Board& board) : Chip(board) {
}
auto main() -> void {
if(irqEnable) {
if(irqMode == 0) {
irqScalar -= 3;
if(irqScalar <= 0) {
irqScalar += 341;
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
if(irqMode == 1) {
if(irqCounter == 0xff) {
irqCounter = irqLatch;
irqLine = 1;
} else {
irqCounter++;
}
}
}
cpu.irqLine(irqLine);
tick();
}
auto writeIO(uint addr, uint8 data) -> void {
switch(addr) {
case 0x8000: prgBank[0] = data; break;
case 0x8010: prgBank[1] = data; break;
case 0x9000: prgBank[2] = data; break;
case 0x9010: break; //APU addr port
case 0x9030: break; //APU data port
case 0xa000: chrBank[0] = data; break;
case 0xa010: chrBank[1] = data; break;
case 0xb000: chrBank[2] = data; break;
case 0xb010: chrBank[3] = data; break;
case 0xc000: chrBank[4] = data; break;
case 0xc010: chrBank[5] = data; break;
case 0xd000: chrBank[6] = data; break;
case 0xd010: chrBank[7] = data; break;
case 0xe000: mirror = data & 0x03; break;
case 0xe010:
irqLatch = data;
break;
case 0xf000:
irqMode = data & 0x04;
irqEnable = data & 0x02;
irqAcknowledge = data & 0x01;
if(irqEnable) {
irqCounter = irqLatch;
irqScalar = 341;
}
irqLine = 0;
break;
case 0xf010:
irqEnable = irqAcknowledge;
irqLine = 0;
break;
}
}
auto addrPRG(uint addr) const -> uint {
uint bank = 0;
switch(addr & 0xe000) {
case 0x8000: bank = prgBank[0]; break;
case 0xa000: bank = prgBank[1]; break;
case 0xc000: bank = prgBank[2]; break;
case 0xe000: bank = 0xff; break;
}
return (bank * 0x2000) + (addr & 0x1fff);
}
auto addrCHR(uint addr) const -> uint {
uint bank = chrBank[addr / 0x0400];
return (bank * 0x0400) + (addr & 0x03ff);
}
auto addrCIRAM(uint addr) const -> uint {
switch(mirror) {
case 0: return ((addr & 0x0400) >> 0) | (addr & 0x03ff); //vertical mirroring
case 1: return ((addr & 0x0800) >> 1) | (addr & 0x03ff); //horizontal mirroring
case 2: return 0x0000 | (addr & 0x03ff); //one-screen mirroring (first)
case 3: return 0x0400 | (addr & 0x03ff); //one-screen mirroring (second)
}
unreachable;
}
auto power() -> void {
for(auto& n : prgBank) n = 0;
for(auto& n : chrBank) n = 0;
mirror = 0;
irqLatch = 0;
irqMode = 0;
irqEnable = 0;
irqAcknowledge = 0;
irqCounter = 0;
irqScalar = 0;
irqLine = 0;
}
auto serialize(serializer& s) -> void {
s.array(prgBank);
s.array(chrBank);
s.integer(mirror);
s.integer(irqLatch);
s.integer(irqMode);
s.integer(irqEnable);
s.integer(irqAcknowledge);
s.integer(irqCounter);
s.integer(irqScalar);
s.integer(irqLine);
}
uint8 prgBank[3];
uint8 chrBank[8];
uint2 mirror;
uint8 irqLatch;
bool irqMode;
bool irqEnable;
bool irqAcknowledge;
uint8 irqCounter;
int irqScalar;
bool irqLine;
};

View file

@ -0,0 +1,4 @@
auto Cartridge::serialize(serializer& s) -> void {
Thread::serialize(s);
return board->serialize(s);
}

View file

@ -0,0 +1,58 @@
#include <fc/fc.hpp>
namespace Famicom {
ControllerPort controllerPort1;
ControllerPort controllerPort2;
#include "gamepad/gamepad.cpp"
Controller::Controller(uint port) : port(port) {
if(!handle()) create(Controller::Enter, 1);
}
Controller::~Controller() {
scheduler.remove(*this);
}
auto Controller::Enter() -> void {
while(true) {
scheduler.synchronize();
if(controllerPort1.device->active()) controllerPort1.device->main();
if(controllerPort2.device->active()) controllerPort2.device->main();
}
}
auto Controller::main() -> void {
step(1);
synchronize(cpu);
}
//
auto ControllerPort::connect(uint deviceID) -> void {
if(!system.loaded()) return;
delete device;
switch(deviceID) { default:
case ID::Device::None: device = new Controller(port); break;
case ID::Device::Gamepad: device = new Gamepad(port); break;
}
cpu.peripherals.reset();
if(auto device = controllerPort1.device) cpu.peripherals.append(device);
if(auto device = controllerPort2.device) cpu.peripherals.append(device);
}
auto ControllerPort::power(uint port) -> void {
this->port = port;
}
auto ControllerPort::unload() -> void {
delete device;
device = nullptr;
}
auto ControllerPort::serialize(serializer& s) -> void {
}
}

View file

@ -0,0 +1,45 @@
//Famicom controller port pinout:
// ____
// | \
// |(7) \
// |(2)(1)|
// |(3)(5)|
// |(4)(6)|
// |______|
//
// pin name port1 port2
// 1: +5v
// 2: clock $4016 read $4016.d0 write
// 3: latch $4016.d0 write $4016.d0 write
// 4: data0 $4016.d0 read $4017.d0 read
// 5: data3 $4016.d3 read $4017.d3 read
// 6: data4 $4016.d4 read $4017.d4 read
// 7: gnd
struct Controller : Thread {
Controller(uint port);
virtual ~Controller();
static auto Enter() -> void;
virtual auto main() -> void;
virtual auto data() -> uint3 { return 0; }
virtual auto latch(bool data) -> void {}
const uint port;
};
struct ControllerPort {
auto connect(uint deviceID) -> void;
auto power(uint port) -> void;
auto unload() -> void;
auto serialize(serializer&) -> void;
uint port;
Controller* device = nullptr;
};
extern ControllerPort controllerPort1;
extern ControllerPort controllerPort2;
#include "gamepad/gamepad.hpp"

View file

@ -0,0 +1,36 @@
Gamepad::Gamepad(uint port) : Controller(port) {
}
auto Gamepad::data() -> uint3 {
if(counter >= 8) return 1;
if(latched == 1) return platform->inputPoll(port, ID::Device::Gamepad, A);
switch(counter++) {
case 0: return a;
case 1: return b;
case 2: return select;
case 3: return start;
case 4: return up && !down;
case 5: return down && !up;
case 6: return left && !right;
case 7: return right && !left;
}
unreachable;
}
auto Gamepad::latch(bool data) -> void {
if(latched == data) return;
latched = data;
counter = 0;
if(latched == 0) {
a = platform->inputPoll(port, ID::Device::Gamepad, A);
b = platform->inputPoll(port, ID::Device::Gamepad, B);
select = platform->inputPoll(port, ID::Device::Gamepad, Select);
start = platform->inputPoll(port, ID::Device::Gamepad, Start);
up = platform->inputPoll(port, ID::Device::Gamepad, Up);
down = platform->inputPoll(port, ID::Device::Gamepad, Down);
left = platform->inputPoll(port, ID::Device::Gamepad, Left);
right = platform->inputPoll(port, ID::Device::Gamepad, Right);
}
}

View file

@ -0,0 +1,22 @@
struct Gamepad : Controller {
enum : uint {
Up, Down, Left, Right, B, A, Select, Start,
};
Gamepad(uint port);
auto data() -> uint3;
auto latch(bool data) -> void;
private:
bool latched = 0;
uint counter = 0;
bool a = 0;
bool b = 0;
bool select = 0;
bool start = 0;
bool up = 0;
bool down = 0;
bool left = 0;
bool right = 0;
};

44
higan/fc/cpu/cpu.cpp Normal file
View file

@ -0,0 +1,44 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "memory.cpp"
#include "timing.cpp"
#include "serialization.cpp"
CPU cpu;
auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main();
}
auto CPU::main() -> void {
if(io.interruptPending) return interrupt();
instruction();
}
auto CPU::step(uint clocks) -> void {
Thread::step(clocks);
synchronize(apu);
synchronize(ppu);
synchronize(cartridge);
for(auto peripheral : peripherals) synchronize(*peripheral);
}
auto CPU::power(bool reset) -> void {
MOS6502::BCD = 0;
MOS6502::power();
create(CPU::Enter, system.frequency());
if(!reset) for(auto& data : ram) data = 0xff;
ram[0x008] = 0xf7; //todo: what is this about?
ram[0x009] = 0xef;
ram[0x00a] = 0xdf;
ram[0x00f] = 0xbf;
r.pc.byte(0) = bus.read(0xfffc);
r.pc.byte(1) = bus.read(0xfffd);
io = {};
}
}

58
higan/fc/cpu/cpu.hpp Normal file
View file

@ -0,0 +1,58 @@
struct CPU : Processor::MOS6502, Thread {
inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }
//cpu.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power(bool reset) -> void;
//memory.cpp
auto readRAM(uint11 addr) -> uint8;
auto writeRAM(uint11 addr, uint8 data) -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
auto readDebugger(uint16 addr) -> uint8 override;
auto serialize(serializer&) -> void;
//timing.cpp
auto read(uint16 addr) -> uint8 override;
auto write(uint16 addr, uint8 data) -> void override;
auto lastCycle() -> void override;
auto nmi(uint16& vector) -> void override;
auto oamdma() -> void;
auto nmiLine(bool) -> void;
auto irqLine(bool) -> void;
auto apuLine(bool) -> void;
auto rdyLine(bool) -> void;
auto rdyAddr(bool valid, uint16 value = 0) -> void;
//protected:
vector<Thread*> peripherals;
uint8 ram[0x800];
struct IO {
bool interruptPending = 0;
bool nmiPending = 0;
bool nmiLine = 0;
bool irqLine = 0;
bool apuLine = 0;
bool rdyLine = 1;
bool rdyAddrValid = 0;
uint16 rdyAddrValue;
bool oamdmaPending = 0;
uint8 oamdmaPage;
} io;
};
extern CPU cpu;

49
higan/fc/cpu/memory.cpp Normal file
View file

@ -0,0 +1,49 @@
auto CPU::readRAM(uint11 addr) -> uint8 {
return ram[addr];
}
auto CPU::writeRAM(uint11 addr, uint8 data) -> void {
ram[addr] = data;
}
auto CPU::readIO(uint16 addr) -> uint8 {
switch(addr) {
case 0x4016: {
auto data = controllerPort1.device->data();
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
}
case 0x4017: {
auto data = controllerPort2.device->data();
return (mdr() & 0xc0) | data.bit(2) << 4 | data.bit(1) << 3 | data.bit(0) << 0;
}
}
return apu.readIO(addr);
}
auto CPU::writeIO(uint16 addr, uint8 data) -> void {
switch(addr) {
case 0x4014: {
io.oamdmaPage = data;
io.oamdmaPending = true;
return;
}
case 0x4016: {
controllerPort1.device->latch(data.bit(0));
controllerPort2.device->latch(data.bit(0));
return;
}
}
return apu.writeIO(addr, data);
}
auto CPU::readDebugger(uint16 addr) -> uint8 {
return bus.read(addr);
}

View file

@ -0,0 +1,19 @@
auto CPU::serialize(serializer& s) -> void {
MOS6502::serialize(s);
Thread::serialize(s);
s.array(ram);
s.integer(io.interruptPending);
s.integer(io.nmiPending);
s.integer(io.nmiLine);
s.integer(io.irqLine);
s.integer(io.apuLine);
s.integer(io.rdyLine);
s.integer(io.rdyAddrValid);
s.integer(io.rdyAddrValue);
s.integer(io.oamdmaPending);
s.integer(io.oamdmaPage);
}

64
higan/fc/cpu/timing.cpp Normal file
View file

@ -0,0 +1,64 @@
auto CPU::read(uint16 addr) -> uint8 {
if(io.oamdmaPending) {
io.oamdmaPending = false;
read(addr);
oamdma();
}
while(io.rdyLine == 0) {
r.mdr = bus.read(io.rdyAddrValid ? io.rdyAddrValue : addr);
step(rate());
}
r.mdr = bus.read(addr);
step(rate());
return r.mdr;
}
auto CPU::write(uint16 addr, uint8 data) -> void {
bus.write(addr, r.mdr = data);
step(rate());
}
auto CPU::lastCycle() -> void {
io.interruptPending = ((io.irqLine | io.apuLine) & ~r.p.i) | io.nmiPending;
}
auto CPU::nmi(uint16& vector) -> void {
if(io.nmiPending) {
io.nmiPending = false;
vector = 0xfffa;
}
}
auto CPU::oamdma() -> void {
for(uint n : range(256)) {
uint8 data = read(io.oamdmaPage << 8 | n);
write(0x2004, data);
}
}
auto CPU::nmiLine(bool line) -> void {
//edge-sensitive (0->1)
if(!io.nmiLine && line) io.nmiPending = true;
io.nmiLine = line;
}
auto CPU::irqLine(bool line) -> void {
//level-sensitive
io.irqLine = line;
}
auto CPU::apuLine(bool line) -> void {
//level-sensitive
io.apuLine = line;
}
auto CPU::rdyLine(bool line) -> void {
io.rdyLine = line;
}
auto CPU::rdyAddr(bool valid, uint16 value) -> void {
io.rdyAddrValid = valid;
io.rdyAddrValue = value;
}

47
higan/fc/fc.hpp Normal file
View file

@ -0,0 +1,47 @@
#pragma once
//license: GPLv3
//started: 2011-09-05
#include <emulator/emulator.hpp>
#include <emulator/thread.hpp>
#include <emulator/scheduler.hpp>
#include <emulator/cheat.hpp>
#include <processor/mos6502/mos6502.hpp>
namespace Famicom {
#define platform Emulator::platform
namespace File = Emulator::File;
using Scheduler = Emulator::Scheduler;
using Cheat = Emulator::Cheat;
extern Scheduler scheduler;
extern Cheat cheat;
struct Thread : Emulator::Thread {
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
Emulator::Thread::create(entrypoint, frequency);
scheduler.append(*this);
}
inline auto synchronize(Thread& thread) -> void {
if(clock() >= thread.clock()) scheduler.resume(thread);
}
};
struct Region {
static inline auto NTSCJ() -> bool;
static inline auto NTSCU() -> bool;
static inline auto PAL() -> bool;
};
#include <fc/controller/controller.hpp>
#include <fc/system/system.hpp>
#include <fc/memory/memory.hpp>
#include <fc/cartridge/cartridge.hpp>
#include <fc/cpu/cpu.hpp>
#include <fc/apu/apu.hpp>
#include <fc/ppu/ppu.hpp>
}
#include <fc/interface/interface.hpp>

View file

@ -0,0 +1,211 @@
#include <fc/fc.hpp>
namespace Famicom {
Settings settings;
auto Interface::information() -> Information {
Information information;
information.manufacturer = "Nintendo";
information.name = "Famicom";
information.extension = "fc";
information.resettable = true;
return information;
}
auto Interface::display() -> Display {
Display display;
display.type = Display::Type::CRT;
display.colors = 1 << 9;
display.width = 256;
display.height = 240;
display.internalWidth = 256;
display.internalHeight = 240;
display.aspectCorrection = 8.0 / 7.0;
return display;
}
auto Interface::color(uint32 n) -> uint64 {
double saturation = 2.0;
double hue = 0.0;
double contrast = 1.0;
double brightness = 1.0;
double gamma = settings.colorEmulation ? 1.8 : 2.2;
int color = (n & 0x0f), level = color < 0xe ? int(n >> 4 & 3) : 1;
static const double black = 0.518, white = 1.962, attenuation = 0.746;
static const double levels[8] = {
0.350, 0.518, 0.962, 1.550,
1.094, 1.506, 1.962, 1.962,
};
double lo_and_hi[2] = {
levels[level + 4 * (color == 0x0)],
levels[level + 4 * (color < 0xd)],
};
double y = 0.0, i = 0.0, q = 0.0;
auto wave = [](int p, int color) { return (color + p + 8) % 12 < 6; };
for(int p : range(12)) {
double spot = lo_and_hi[wave(p, color)];
if(((n & 0x040) && wave(p, 12))
|| ((n & 0x080) && wave(p, 4))
|| ((n & 0x100) && wave(p, 8))
) spot *= attenuation;
double v = (spot - black) / (white - black);
v = (v - 0.5) * contrast + 0.5;
v *= brightness / 12.0;
y += v;
i += v * cos((Math::Pi / 6.0) * (p + hue));
q += v * sin((Math::Pi / 6.0) * (p + hue));
}
i *= saturation;
q *= saturation;
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : pow(f, 2.2 / gamma); };
uint64 r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
uint64 g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
uint64 b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
return r << 32 | g << 16 | b << 0;
}
auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::hashes() -> vector<string> {
return {cartridge.hash()};
}
auto Interface::manifests() -> vector<string> {
return {cartridge.manifest()};
}
auto Interface::titles() -> vector<string> {
return {cartridge.title()};
}
auto Interface::load() -> bool {
return system.load(this);
}
auto Interface::save() -> void {
system.save();
}
auto Interface::unload() -> void {
save();
system.unload();
}
auto Interface::ports() -> vector<Port> { return {
{ID::Port::Controller1, "Controller Port 1"},
{ID::Port::Controller2, "Controller Port 2"},
{ID::Port::Expansion, "Expansion Port" }};
}
auto Interface::devices(uint port) -> vector<Device> {
if(port == ID::Port::Controller1) return {
{ID::Device::None, "None" },
{ID::Device::Gamepad, "Gamepad"}
};
if(port == ID::Port::Controller2) return {
{ID::Device::None, "None" },
{ID::Device::Gamepad, "Gamepad"}
};
if(port == ID::Port::Expansion) return {
{ID::Device::None, "None"}
};
return {};
}
auto Interface::inputs(uint device) -> vector<Input> {
using Type = Input::Type;
if(device == ID::Device::None) return {
};
if(device == ID::Device::Gamepad) return {
{Type::Hat, "Up" },
{Type::Hat, "Down" },
{Type::Hat, "Left" },
{Type::Hat, "Right" },
{Type::Button, "B" },
{Type::Button, "A" },
{Type::Control, "Select"},
{Type::Control, "Start" }
};
return {};
}
auto Interface::connected(uint port) -> uint {
if(port == ID::Port::Controller1) return settings.controllerPort1;
if(port == ID::Port::Controller2) return settings.controllerPort2;
if(port == ID::Port::Expansion) return settings.expansionPort;
return 0;
}
auto Interface::connect(uint port, uint device) -> void {
if(port == ID::Port::Controller1) controllerPort1.connect(settings.controllerPort1 = device);
if(port == ID::Port::Controller2) controllerPort2.connect(settings.controllerPort2 = device);
}
auto Interface::power() -> void {
system.power(/* reset = */ false);
}
auto Interface::reset() -> void {
system.power(/* reset = */ true);
}
auto Interface::run() -> void {
system.run();
}
auto Interface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto Interface::cheats(const vector<string>& list) -> void {
cheat.assign(list);
}
auto Interface::cap(const string& name) -> bool {
if(name == "Color Emulation") return true;
if(name == "Scanline Emulation") return true;
return false;
}
auto Interface::get(const string& name) -> any {
if(name == "Color Emulation") return settings.colorEmulation;
if(name == "Scanline Emulation") return settings.scanlineEmulation;
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
Emulator::video.setPalette();
return true;
}
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
return false;
}
}

View file

@ -0,0 +1,70 @@
#if defined(CORE_FC)
namespace Famicom {
struct ID {
enum : uint {
System,
Famicom,
};
struct Port { enum : uint {
Controller1,
Controller2,
Expansion,
};};
struct Device { enum : uint {
None,
Gamepad,
};};
};
struct Interface : Emulator::Interface {
auto information() -> Information override;
auto display() -> Display override;
auto color(uint32 color) -> uint64 override;
auto loaded() -> bool override;
auto hashes() -> vector<string> override;
auto manifests() -> vector<string> override;
auto titles() -> vector<string> override;
auto load() -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto ports() -> vector<Port> override;
auto devices(uint port) -> vector<Device> override;
auto inputs(uint device) -> vector<Input> override;
auto connected(uint port) -> uint override;
auto connect(uint port, uint device) -> void override;
auto power() -> void override;
auto reset() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheats(const vector<string>&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
};
struct Settings {
bool colorEmulation = true;
bool scanlineEmulation = true;
uint controllerPort1 = ID::Device::Gamepad;
uint controllerPort2 = ID::Device::Gamepad;
uint expansionPort = ID::Device::None;
};
extern Settings settings;
}
#endif

View file

@ -0,0 +1,34 @@
#include <fc/fc.hpp>
namespace Famicom {
Bus bus;
//$0000-07ff = RAM (2KB)
//$0800-1fff = RAM (mirror)
//$2000-2007 = PPU
//$2008-3fff = PPU (mirror)
//$4000-4017 = APU + I/O
//$4018-ffff = Cartridge
auto Bus::read(uint16 addr) -> uint8 {
uint8 data = cartridge.readPRG(addr);
if(addr <= 0x1fff) data = cpu.readRAM(addr);
else if(addr <= 0x3fff) data = ppu.readIO(addr);
else if(addr <= 0x4017) data = cpu.readIO(addr);
if(cheat) {
if(auto result = cheat.find(addr, data)) return result();
}
return data;
}
auto Bus::write(uint16 addr, uint8 data) -> void {
cartridge.writePRG(addr, data);
if(addr <= 0x1fff) return cpu.writeRAM(addr, data);
if(addr <= 0x3fff) return ppu.writeIO(addr, data);
if(addr <= 0x4017) return cpu.writeIO(addr, data);
}
}

View file

@ -0,0 +1,6 @@
struct Bus {
auto read(uint16 addr) -> uint8;
auto write(uint16 addr, uint8 data) -> void;
};
extern Bus bus;

144
higan/fc/ppu/memory.cpp Normal file
View file

@ -0,0 +1,144 @@
auto PPU::readCIRAM(uint11 addr) -> uint8 {
return ciram[addr];
}
auto PPU::writeCIRAM(uint11 addr, uint8 data) -> void {
ciram[addr] = data;
}
auto PPU::readCGRAM(uint5 addr) -> uint8 {
if((addr & 0x13) == 0x10) addr &= ~0x10;
uint8 data = cgram[addr];
if(io.grayscale) data &= 0x30;
return data;
}
auto PPU::writeCGRAM(uint5 addr, uint8 data) -> void {
if((addr & 0x13) == 0x10) addr &= ~0x10;
cgram[addr] = data;
}
auto PPU::readIO(uint16 addr) -> uint8 {
uint8 result = 0x00;
switch(addr.bits(0,2)) {
//PPUSTATUS
case 2:
result |= io.mdr.bits(0,4);
result |= io.spriteOverflow << 5;
result |= io.spriteZeroHit << 6;
result |= io.nmiFlag << 7;
io.v.latch = 0;
io.nmiHold = 0;
cpu.nmiLine(io.nmiFlag = 0);
break;
//OAMDATA
case 4:
result = oam[io.oamAddress];
break;
//PPUDATA
case 7:
if(enable() && (io.ly <= 240 || io.ly == 261)) return 0x00;
addr = (uint14)io.v.address;
if(addr <= 0x1fff) {
result = io.busData;
io.busData = cartridge.readCHR(addr);
} else if(addr <= 0x3eff) {
result = io.busData;
io.busData = cartridge.readCHR(addr);
} else if(addr <= 0x3fff) {
result = readCGRAM(addr);
io.busData = cartridge.readCHR(addr);
}
io.v.address += io.vramIncrement;
break;
}
return result;
}
auto PPU::writeIO(uint16 addr, uint8 data) -> void {
io.mdr = data;
switch(addr.bits(0,2)) {
//PPUCTRL
case 0:
io.t.nametable = data.bits(0,1);
io.vramIncrement = data.bit (2) ? 32 : 1;
io.spriteAddress = data.bit (3) ? 0x1000 : 0x0000;
io.bgAddress = data.bit (4) ? 0x1000 : 0x0000;
io.spriteHeight = data.bit (5) ? 16 : 8;
io.masterSelect = data.bit (6);
io.nmiEnable = data.bit (7);
cpu.nmiLine(io.nmiEnable && io.nmiHold && io.nmiFlag);
break;
//PPUMASK
case 1:
io.grayscale = data.bit (0);
io.bgEdgeEnable = data.bit (1);
io.spriteEdgeEnable = data.bit (2);
io.bgEnable = data.bit (3);
io.spriteEnable = data.bit (4);
io.emphasis = data.bits(5,7);
break;
//PPUSTATUS
case 2:
break;
//OAMADDR
case 3:
io.oamAddress = data;
break;
//OAMDATA
case 4:
if(io.oamAddress.bits(0,1) == 2) data.bits(2,4) = 0; //clear non-existent bits (always read back as 0)
oam[io.oamAddress++] = data;
break;
//PPUSCROLL
case 5:
if(io.v.latch++ == 0) {
io.v.fineX = data.bits(0,2);
io.t.tileX = data.bits(3,7);
} else {
io.t.fineY = data.bits(0,2);
io.t.tileY = data.bits(3,7);
}
break;
//PPUADDR
case 6:
if(io.v.latch++ == 0) {
io.t.addressHi = data.bits(0,5);
} else {
io.t.addressLo = data.bits(0,7);
io.v.address = io.t.address;
}
break;
//PPUDATA
case 7:
if(enable() && (io.ly <= 240 || io.ly == 261)) return;
addr = (uint14)io.v.address;
if(addr <= 0x1fff) {
cartridge.writeCHR(addr, data);
} else if(addr <= 0x3eff) {
cartridge.writeCHR(addr, data);
} else if(addr <= 0x3fff) {
writeCGRAM(addr, data);
}
io.v.address += io.vramIncrement;
break;
}
}

72
higan/fc/ppu/ppu.cpp Normal file
View file

@ -0,0 +1,72 @@
#include <fc/fc.hpp>
namespace Famicom {
PPU ppu;
#include "memory.cpp"
#include "render.cpp"
#include "serialization.cpp"
auto PPU::Enter() -> void {
while(true) scheduler.synchronize(), ppu.main();
}
auto PPU::main() -> void {
renderScanline();
}
auto PPU::step(uint clocks) -> void {
uint L = vlines();
while(clocks--) {
if(io.ly == 240 && io.lx == 340) io.nmiHold = 1;
if(io.ly == 241 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == 241 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
if(io.ly == L-2 && io.lx == 340) io.spriteZeroHit = 0, io.spriteOverflow = 0;
if(io.ly == L-2 && io.lx == 340) io.nmiHold = 0;
if(io.ly == L-1 && io.lx == 0) io.nmiFlag = io.nmiHold;
if(io.ly == L-1 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
Thread::step(rate());
synchronize(cpu);
io.lx++;
}
}
auto PPU::scanline() -> void {
io.lx = 0;
if(++io.ly == vlines()) {
io.ly = 0;
frame();
}
cartridge.scanline(io.ly);
}
auto PPU::frame() -> void {
io.field++;
scheduler.exit(Scheduler::Event::Frame);
}
auto PPU::refresh() -> void {
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
}
auto PPU::power(bool reset) -> void {
create(PPU::Enter, system.frequency());
io = {};
latch = {};
if(!reset) {
for(auto& data : ciram ) data = 0;
for(auto& data : cgram ) data = 0;
for(auto& data : oam ) data = 0;
}
for(auto& data : buffer) data = 0;
}
}

124
higan/fc/ppu/ppu.hpp Normal file
View file

@ -0,0 +1,124 @@
struct PPU : Thread {
inline auto rate() const -> uint { return Region::PAL() ? 5 : 4; }
inline auto vlines() const -> uint { return Region::PAL() ? 312 : 262; }
//ppu.cpp
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto scanline() -> void;
auto frame() -> void;
auto refresh() -> void;
auto power(bool reset) -> void;
//memory.cpp
auto readCIRAM(uint11 addr) -> uint8;
auto writeCIRAM(uint11 addr, uint8 data) -> void;
auto readCGRAM(uint5 addr) -> uint8;
auto writeCGRAM(uint5 addr, uint8 data) -> void;
auto readIO(uint16 addr) -> uint8;
auto writeIO(uint16 addr, uint8 data) -> void;
//render.cpp
auto enable() const -> bool;
auto loadCHR(uint16 addr) -> uint8;
auto renderPixel() -> void;
auto renderSprite() -> void;
auto renderScanline() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct IO {
//internal
uint8 mdr;
uint1 field;
uint lx = 0;
uint ly = 0;
uint8 busData;
union Union {
auto& operator=(const Union& u) { value = u.value; return *this; }
uint value = 0;
NaturalBitField<uint, 0, 4> tileX;
NaturalBitField<uint, 5, 9> tileY;
NaturalBitField<uint,10,11> nametable;
NaturalBitField<uint,10,10> nametableX;
NaturalBitField<uint,11,11> nametableY;
NaturalBitField<uint,12,14> fineY;
NaturalBitField<uint, 0,14> address;
NaturalBitField<uint, 0, 7> addressLo;
NaturalBitField<uint, 8,14> addressHi;
NaturalBitField<uint,15,15> latch;
NaturalBitField<uint,16,18> fineX;
} v, t;
bool nmiHold = 0;
bool nmiFlag = 0;
//$2000
uint vramIncrement = 1;
uint spriteAddress = 0;
uint bgAddress = 0;
uint spriteHeight = 0;
bool masterSelect = 0;
bool nmiEnable = 0;
//$2001
bool grayscale = 0;
bool bgEdgeEnable = 0;
bool spriteEdgeEnable = 0;
bool bgEnable = 0;
bool spriteEnable = 0;
uint3 emphasis;
//$2002
bool spriteOverflow = 0;
bool spriteZeroHit = 0;
//$2003
uint8 oamAddress;
} io;
struct OAM {
//serialization.cpp
auto serialize(serializer&) -> void;
uint8 id = 64;
uint8 y = 0xff;
uint8 tile = 0xff;
uint8 attr = 0xff;
uint8 x = 0xff;
uint8 tiledataLo = 0;
uint8 tiledataHi = 0;
};
struct Latches {
uint16 nametable;
uint16 attribute;
uint16 tiledataLo;
uint16 tiledataHi;
uint oamIterator = 0;
uint oamCounter = 0;
OAM oam[8]; //primary
OAM soam[8]; //secondary
} latch;
uint8 ciram[2048];
uint8 cgram[32];
uint8 oam[256];
uint32 buffer[256 * 262];
};
extern PPU ppu;

211
higan/fc/ppu/render.cpp Normal file
View file

@ -0,0 +1,211 @@
auto PPU::enable() const -> bool {
return io.bgEnable || io.spriteEnable;
}
auto PPU::loadCHR(uint16 addr) -> uint8 {
return enable() ? cartridge.readCHR(addr) : (uint8)0x00;
}
auto PPU::renderPixel() -> void {
uint32* output = buffer + io.ly * 256;
uint x = io.lx - 1;
uint mask = 0x8000 >> (io.v.fineX + (x & 7));
uint palette = 0;
uint objectPalette = 0;
bool objectPriority = 0;
palette |= latch.tiledataLo & mask ? 1 : 0;
palette |= latch.tiledataHi & mask ? 2 : 0;
if(palette) {
uint attr = latch.attribute;
if(mask >= 256) attr >>= 2;
palette |= (attr & 3) << 2;
}
if(!io.bgEnable) palette = 0;
if(!io.bgEdgeEnable && x < 8) palette = 0;
if(io.spriteEnable)
for(int sprite = 7; sprite >= 0; sprite--) {
if(!io.spriteEdgeEnable && x < 8) continue;
if(latch.oam[sprite].id == 64) continue;
uint spriteX = x - latch.oam[sprite].x;
if(spriteX >= 8) continue;
if(latch.oam[sprite].attr & 0x40) spriteX ^= 7;
uint mask = 0x80 >> spriteX;
uint spritePalette = 0;
spritePalette |= latch.oam[sprite].tiledataLo & mask ? 1 : 0;
spritePalette |= latch.oam[sprite].tiledataHi & mask ? 2 : 0;
if(spritePalette == 0) continue;
if(latch.oam[sprite].id == 0 && palette && x != 255) io.spriteZeroHit = 1;
spritePalette |= (latch.oam[sprite].attr & 3) << 2;
objectPriority = latch.oam[sprite].attr & 0x20;
objectPalette = 16 + spritePalette;
}
if(objectPalette) {
if(palette == 0 || objectPriority == 0) palette = objectPalette;
}
if(!enable()) palette = 0;
output[x] = io.emphasis << 6 | readCGRAM(palette);
}
auto PPU::renderSprite() -> void {
if(!enable()) return;
uint n = latch.oamIterator++;
int ly = io.ly == vlines() - 1 ? -1 : io.ly;
uint y = ly - oam[n * 4 + 0];
if(y >= io.spriteHeight) return;
if(latch.oamCounter == 8) {
io.spriteOverflow = 1;
return;
}
auto& o = latch.soam[latch.oamCounter++];
o.id = n;
o.y = oam[n * 4 + 0];
o.tile = oam[n * 4 + 1];
o.attr = oam[n * 4 + 2];
o.x = oam[n * 4 + 3];
}
auto PPU::renderScanline() -> void {
//Vblank
if(io.ly >= 240 && io.ly <= vlines() - 2) return step(341), scanline();
latch.oamIterator = 0;
latch.oamCounter = 0;
for(auto n : range(8)) latch.soam[n] = {};
// 0
step(1);
// 1-256
for(uint tile : range(32)) {
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
uint tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
renderPixel();
step(1);
renderPixel();
step(1);
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
if(io.v.tileY & 2) attribute >>= 4;
if(io.v.tileX & 2) attribute >>= 2;
renderPixel();
step(1);
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
if(enable() && tile == 31 && ++io.v.fineY == 0 && ++io.v.tileY == 30) io.v.nametableY++, io.v.tileY = 0;
renderPixel();
renderSprite();
step(1);
uint tiledataLo = loadCHR(tileaddr + 0);
renderPixel();
step(1);
renderPixel();
step(1);
uint tiledataHi = loadCHR(tileaddr + 8);
renderPixel();
step(1);
renderPixel();
renderSprite();
step(1);
latch.nametable = latch.nametable << 8 | nametable;
latch.attribute = latch.attribute << 2 | (attribute & 3);
latch.tiledataLo = latch.tiledataLo << 8 | tiledataLo;
latch.tiledataHi = latch.tiledataHi << 8 | tiledataHi;
}
for(auto n : range(8)) latch.oam[n] = latch.soam[n];
//257-320
for(uint sprite : range(8)) {
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
step(1);
if(enable() && sprite == 0) {
//258
io.v.nametableX = io.t.nametableX;
io.v.tileX = io.t.tileX;
}
step(1);
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
uint tileaddr = io.spriteHeight == 8
? io.spriteAddress + latch.oam[sprite].tile * 16
: (latch.oam[sprite].tile & ~1) * 16 + (latch.oam[sprite].tile & 1) * 0x1000;
step(2);
uint spriteY = (io.ly - latch.oam[sprite].y) & (io.spriteHeight - 1);
if(latch.oam[sprite].attr & 0x80) spriteY ^= io.spriteHeight - 1;
tileaddr += spriteY + (spriteY & 8);
latch.oam[sprite].tiledataLo = loadCHR(tileaddr + 0);
step(2);
latch.oam[sprite].tiledataHi = loadCHR(tileaddr + 8);
step(2);
if(enable() && sprite == 6 && io.ly == vlines() - 1) {
//305
io.v.address = io.t.address;
}
}
//321-336
for(uint tile : range(2)) {
uint nametable = loadCHR(0x2000 | (uint12)io.v.address);
uint tileaddr = io.bgAddress | nametable << 4 | io.v.fineY;
step(2);
uint attribute = loadCHR(0x23c0 | io.v.nametable << 10 | (io.v.tileY >> 2) << 3 | io.v.tileX >> 2);
if(io.v.tileY & 2) attribute >>= 4;
if(io.v.tileX & 2) attribute >>= 2;
step(1);
if(enable() && ++io.v.tileX == 0) io.v.nametableX++;
step(1);
uint tiledataLo = loadCHR(tileaddr + 0);
step(2);
uint tiledataHi = loadCHR(tileaddr + 8);
step(2);
latch.nametable = latch.nametable << 8 | nametable;
latch.attribute = latch.attribute << 2 | (attribute & 3);
latch.tiledataLo = latch.tiledataLo << 8 | tiledataLo;
latch.tiledataHi = latch.tiledataHi << 8 | tiledataHi;
}
//337-338
loadCHR(0x2000 | (uint12)io.v.address);
step(1);
bool skip = enable() && io.field == 1 && io.ly == vlines() - 1;
step(1);
//339
loadCHR(0x2000 | (uint12)io.v.address);
step(1);
//340
if(!skip) step(1);
return scanline();
}

View file

@ -0,0 +1,64 @@
auto PPU::serialize(serializer& s) -> void {
Thread::serialize(s);
s.integer(io.mdr);
s.integer(io.field);
s.integer(io.lx);
s.integer(io.ly);
s.integer(io.busData);
s.integer(io.v.value);
s.integer(io.t.value);
s.integer(io.nmiHold);
s.integer(io.nmiFlag);
s.integer(io.vramIncrement);
s.integer(io.spriteAddress);
s.integer(io.bgAddress);
s.integer(io.spriteHeight);
s.integer(io.masterSelect);
s.integer(io.nmiEnable);
s.integer(io.grayscale);
s.integer(io.bgEdgeEnable);
s.integer(io.spriteEdgeEnable);
s.integer(io.bgEnable);
s.integer(io.spriteEnable);
s.integer(io.emphasis);
s.integer(io.spriteOverflow);
s.integer(io.spriteZeroHit);
s.integer(io.oamAddress);
s.integer(latch.nametable);
s.integer(latch.attribute);
s.integer(latch.tiledataLo);
s.integer(latch.tiledataHi);
s.integer(latch.oamIterator);
s.integer(latch.oamCounter);
for(auto& o : latch.oam) o.serialize(s);
for(auto& o : latch.soam) o.serialize(s);
s.array(ciram);
s.array(cgram);
s.array(oam);
s.array(buffer);
}
auto PPU::OAM::serialize(serializer& s) -> void {
s.integer(id);
s.integer(y);
s.integer(tile);
s.integer(attr);
s.integer(x);
s.integer(tiledataLo);
s.integer(tiledataHi);
}

View file

@ -0,0 +1,60 @@
auto System::serialize() -> serializer {
serializer s(_serializeSize);
uint signature = 0x31545342;
char version[16] = {0};
char description[512] = {0};
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
s.integer(signature);
s.array(version);
s.array(description);
serializeAll(s);
return s;
}
auto System::unserialize(serializer& s) -> bool {
uint signature;
char version[16];
char description[512];
s.integer(signature);
s.array(version);
s.array(description);
if(signature != 0x31545342) return false;
if(string{version} != Emulator::SerializerVersion) return false;
power(/* reset = */ false);
serializeAll(s);
return true;
}
auto System::serialize(serializer& s) -> void {
}
auto System::serializeAll(serializer& s) -> void {
system.serialize(s);
cartridge.serialize(s);
cpu.serialize(s);
apu.serialize(s);
ppu.serialize(s);
controllerPort1.serialize(s);
controllerPort2.serialize(s);
}
auto System::serializeInit() -> void {
serializer s;
uint signature = 0;
char version[16];
char description[512];
s.integer(signature);
s.array(version);
s.array(description);
serializeAll(s);
_serializeSize = s.size();
}

View file

@ -0,0 +1,91 @@
#include <fc/fc.hpp>
namespace Famicom {
#include "serialization.cpp"
System system;
Scheduler scheduler;
Cheat cheat;
auto System::run() -> void {
if(scheduler.enter() == Scheduler::Event::Frame) ppu.refresh();
}
auto System::runToSave() -> void {
scheduler.synchronize(cpu);
scheduler.synchronize(apu);
scheduler.synchronize(ppu);
scheduler.synchronize(cartridge);
for(auto peripheral : cpu.peripherals) scheduler.synchronize(*peripheral);
}
auto System::load(Emulator::Interface* interface) -> bool {
information = {};
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else {
return false;
}
auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false;
if(cartridge.region() == "NTSC-J") {
information.region = Region::NTSCJ;
information.frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
}
if(cartridge.region() == "NTSC-U") {
information.region = Region::NTSCU;
information.frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
}
if(cartridge.region() == "PAL") {
information.region = Region::PAL;
information.frequency = Emulator::Constants::Colorburst::PAL * 6.0;
}
this->interface = interface;
serializeInit();
return information.loaded = true;
}
auto System::save() -> void {
cartridge.save();
}
auto System::unload() -> void {
if(!loaded()) return;
cpu.peripherals.reset();
controllerPort1.unload();
controllerPort2.unload();
cartridge.unload();
information.loaded = false;
}
auto System::power(bool reset) -> void {
Emulator::video.reset(interface);
Emulator::video.setPalette();
Emulator::audio.reset(interface);
scheduler.reset();
cartridge.power();
cpu.power(reset);
apu.power(reset);
ppu.power(reset);
scheduler.primary(cpu);
controllerPort1.power(ID::Port::Controller1);
controllerPort2.power(ID::Port::Controller2);
controllerPort1.connect(settings.controllerPort1);
controllerPort2.connect(settings.controllerPort2);
}
auto System::init() -> void {
assert(interface != nullptr);
}
auto System::term() -> void {
}
}

View file

@ -0,0 +1,44 @@
struct System {
enum class Region : uint { NTSCJ, NTSCU, PAL };
auto loaded() const -> bool { return information.loaded; }
auto region() const -> Region { return information.region; }
auto frequency() const -> double { return information.frequency; }
auto run() -> void;
auto runToSave() -> void;
auto load(Emulator::Interface*) -> bool;
auto save() -> void;
auto unload() -> void;
auto power(bool reset) -> void;
auto init() -> void;
auto term() -> void;
//serialization.cpp
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize(serializer&) -> void;
auto serializeAll(serializer&) -> void;
auto serializeInit() -> void;
private:
Emulator::Interface* interface = nullptr;
struct Information {
bool loaded = false;
Region region = Region::NTSCJ;
double frequency = Emulator::Constants::Colorburst::NTSC * 6.0;
string manifest;
} information;
uint _serializeSize = 0;
};
extern System system;
auto Region::NTSCJ() -> bool { return system.region() == System::Region::NTSCJ; }
auto Region::NTSCU() -> bool { return system.region() == System::Region::NTSCU; }
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }

13
higan/gb/GNUmakefile Normal file
View file

@ -0,0 +1,13 @@
processors += sm83
objects += gb-interface gb-system
objects += gb-memory gb-cartridge
objects += gb-cpu gb-ppu gb-apu
obj/gb-interface.o: gb/interface/interface.cpp
obj/gb-system.o: gb/system/system.cpp
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp
obj/gb-memory.o: gb/memory/memory.cpp
obj/gb-cpu.o: gb/cpu/cpu.cpp
obj/gb-ppu.o: gb/ppu/ppu.cpp
obj/gb-apu.o: gb/apu/apu.cpp

Some files were not shown because too many files have changed in this diff Show more