mirror of
https://github.com/DerKoun/bsnes-hd.git
synced 2024-05-31 18:38:07 -04:00
bsnes107r1
This commit is contained in:
parent
ee164c449b
commit
7d436cad7a
BIN
firmware/cx4.data.rom
Normal file
BIN
firmware/cx4.data.rom
Normal file
Binary file not shown.
BIN
firmware/sgb1.boot.rom
Normal file
BIN
firmware/sgb1.boot.rom
Normal file
Binary file not shown.
BIN
firmware/sgb2.boot.rom
Normal file
BIN
firmware/sgb2.boot.rom
Normal file
Binary file not shown.
99
higan/GNUmakefile
Normal file
99
higan/GNUmakefile
Normal 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/*)
|
71
higan/emulator/audio/audio.cpp
Normal file
71
higan/emulator/audio/audio.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
83
higan/emulator/audio/audio.hpp
Normal file
83
higan/emulator/audio/audio.hpp
Normal 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;
|
||||
|
||||
}
|
106
higan/emulator/audio/stream.cpp
Normal file
106
higan/emulator/audio/stream.cpp
Normal 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
48
higan/emulator/cheat.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
11
higan/emulator/emulator.cpp
Normal file
11
higan/emulator/emulator.cpp
Normal 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;
|
||||
|
||||
}
|
60
higan/emulator/emulator.hpp
Normal file
60
higan/emulator/emulator.hpp
Normal 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
110
higan/emulator/game.hpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
101
higan/emulator/interface.hpp
Normal file
101
higan/emulator/interface.hpp
Normal 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; }
|
||||
};
|
||||
|
||||
}
|
30
higan/emulator/memory/memory.hpp
Normal file
30
higan/emulator/memory/memory.hpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
63
higan/emulator/memory/readable.hpp
Normal file
63
higan/emulator/memory/readable.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
65
higan/emulator/memory/writable.hpp
Normal file
65
higan/emulator/memory/writable.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
29
higan/emulator/platform.hpp
Normal file
29
higan/emulator/platform.hpp
Normal 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
96
higan/emulator/random.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
6
higan/emulator/resource/GNUmakefile
Normal file
6
higan/emulator/resource/GNUmakefile
Normal file
|
@ -0,0 +1,6 @@
|
|||
all:
|
||||
sourcery resource.bml resource.cpp resource.hpp
|
||||
|
||||
clean:
|
||||
rm resource.cpp
|
||||
rm resource.hpp
|
5
higan/emulator/resource/resource.bml
Normal file
5
higan/emulator/resource/resource.bml
Normal 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
|
45
higan/emulator/resource/resource.cpp
Normal file
45
higan/emulator/resource/resource.cpp
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
7
higan/emulator/resource/resource.hpp
Normal file
7
higan/emulator/resource/resource.hpp
Normal 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];
|
||||
}
|
||||
}
|
BIN
higan/emulator/resource/sprite/crosshair-blue.png
Normal file
BIN
higan/emulator/resource/sprite/crosshair-blue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 332 B |
BIN
higan/emulator/resource/sprite/crosshair-green.png
Normal file
BIN
higan/emulator/resource/sprite/crosshair-green.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 329 B |
BIN
higan/emulator/resource/sprite/crosshair-red.png
Normal file
BIN
higan/emulator/resource/sprite/crosshair-red.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 342 B |
91
higan/emulator/scheduler.hpp
Normal file
91
higan/emulator/scheduler.hpp
Normal 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
61
higan/emulator/thread.hpp
Normal 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
129
higan/emulator/types.hpp
Normal 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>;
|
20
higan/emulator/video/sprite.cpp
Normal file
20
higan/emulator/video/sprite.cpp
Normal 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;
|
||||
}
|
191
higan/emulator/video/video.cpp
Normal file
191
higan/emulator/video/video.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
80
higan/emulator/video/video.hpp
Normal file
80
higan/emulator/video/video.hpp
Normal 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
13
higan/fc/GNUmakefile
Normal 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
319
higan/fc/apu/apu.cpp
Normal 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
182
higan/fc/apu/apu.hpp
Normal 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
89
higan/fc/apu/dmc.cpp
Normal 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
26
higan/fc/apu/envelope.cpp
Normal 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
42
higan/fc/apu/noise.cpp
Normal 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
38
higan/fc/apu/pulse.cpp
Normal 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;
|
||||
}
|
104
higan/fc/apu/serialization.cpp
Normal file
104
higan/fc/apu/serialization.cpp
Normal 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
40
higan/fc/apu/sweep.cpp
Normal 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
40
higan/fc/apu/triangle.cpp
Normal 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;
|
||||
}
|
110
higan/fc/cartridge/board/bandai-fcg.cpp
Normal file
110
higan/fc/cartridge/board/bandai-fcg.cpp
Normal 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;
|
||||
};
|
223
higan/fc/cartridge/board/board.cpp
Normal file
223
higan/fc/cartridge/board/board.cpp
Normal 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;
|
||||
}
|
48
higan/fc/cartridge/board/board.hpp
Normal file
48
higan/fc/cartridge/board/board.hpp
Normal 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;
|
||||
};
|
34
higan/fc/cartridge/board/konami-vrc1.cpp
Normal file
34
higan/fc/cartridge/board/konami-vrc1.cpp
Normal 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;
|
||||
};
|
51
higan/fc/cartridge/board/konami-vrc2.cpp
Normal file
51
higan/fc/cartridge/board/konami-vrc2.cpp
Normal 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;
|
||||
};
|
51
higan/fc/cartridge/board/konami-vrc3.cpp
Normal file
51
higan/fc/cartridge/board/konami-vrc3.cpp
Normal 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;
|
||||
};
|
55
higan/fc/cartridge/board/konami-vrc4.cpp
Normal file
55
higan/fc/cartridge/board/konami-vrc4.cpp
Normal 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;
|
||||
};
|
39
higan/fc/cartridge/board/konami-vrc6.cpp
Normal file
39
higan/fc/cartridge/board/konami-vrc6.cpp
Normal 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;
|
||||
};
|
41
higan/fc/cartridge/board/konami-vrc7.cpp
Normal file
41
higan/fc/cartridge/board/konami-vrc7.cpp
Normal 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;
|
||||
};
|
46
higan/fc/cartridge/board/nes-axrom.cpp
Normal file
46
higan/fc/cartridge/board/nes-axrom.cpp
Normal 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;
|
||||
};
|
47
higan/fc/cartridge/board/nes-bnrom.cpp
Normal file
47
higan/fc/cartridge/board/nes-bnrom.cpp
Normal 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;
|
||||
};
|
49
higan/fc/cartridge/board/nes-cnrom.cpp
Normal file
49
higan/fc/cartridge/board/nes-cnrom.cpp
Normal 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;
|
||||
};
|
47
higan/fc/cartridge/board/nes-exrom.cpp
Normal file
47
higan/fc/cartridge/board/nes-exrom.cpp
Normal 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;
|
||||
};
|
86
higan/fc/cartridge/board/nes-fxrom.cpp
Normal file
86
higan/fc/cartridge/board/nes-fxrom.cpp
Normal 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];
|
||||
};
|
56
higan/fc/cartridge/board/nes-gxrom.cpp
Normal file
56
higan/fc/cartridge/board/nes-gxrom.cpp
Normal 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;
|
||||
};
|
42
higan/fc/cartridge/board/nes-hkrom.cpp
Normal file
42
higan/fc/cartridge/board/nes-hkrom.cpp
Normal 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;
|
||||
};
|
44
higan/fc/cartridge/board/nes-nrom.cpp
Normal file
44
higan/fc/cartridge/board/nes-nrom.cpp
Normal 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;
|
||||
};
|
92
higan/fc/cartridge/board/nes-pxrom.cpp
Normal file
92
higan/fc/cartridge/board/nes-pxrom.cpp
Normal 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];
|
||||
};
|
95
higan/fc/cartridge/board/nes-sxrom.cpp
Normal file
95
higan/fc/cartridge/board/nes-sxrom.cpp
Normal 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;
|
||||
};
|
61
higan/fc/cartridge/board/nes-txrom.cpp
Normal file
61
higan/fc/cartridge/board/nes-txrom.cpp
Normal 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;
|
||||
};
|
50
higan/fc/cartridge/board/nes-uxrom.cpp
Normal file
50
higan/fc/cartridge/board/nes-uxrom.cpp
Normal 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;
|
||||
};
|
217
higan/fc/cartridge/board/sunsoft-5b.cpp
Normal file
217
higan/fc/cartridge/board/sunsoft-5b.cpp
Normal 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];
|
||||
};
|
74
higan/fc/cartridge/cartridge.cpp
Normal file
74
higan/fc/cartridge/cartridge.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
48
higan/fc/cartridge/cartridge.hpp
Normal file
48
higan/fc/cartridge/cartridge.hpp
Normal 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;
|
17
higan/fc/cartridge/chip/chip.cpp
Normal file
17
higan/fc/cartridge/chip/chip.cpp
Normal 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();
|
||||
}
|
8
higan/fc/cartridge/chip/chip.hpp
Normal file
8
higan/fc/cartridge/chip/chip.hpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
struct Board;
|
||||
|
||||
struct Chip {
|
||||
Chip(Board& board);
|
||||
auto tick() -> void;
|
||||
|
||||
Board& board;
|
||||
};
|
126
higan/fc/cartridge/chip/mmc1.cpp
Normal file
126
higan/fc/cartridge/chip/mmc1.cpp
Normal 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;
|
||||
};
|
181
higan/fc/cartridge/chip/mmc3.cpp
Normal file
181
higan/fc/cartridge/chip/mmc3.cpp
Normal 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;
|
||||
};
|
493
higan/fc/cartridge/chip/mmc5.cpp
Normal file
493
higan/fc/cartridge/chip/mmc5.cpp
Normal 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;
|
||||
};
|
192
higan/fc/cartridge/chip/mmc6.cpp
Normal file
192
higan/fc/cartridge/chip/mmc6.cpp
Normal 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;
|
||||
};
|
75
higan/fc/cartridge/chip/vrc1.cpp
Normal file
75
higan/fc/cartridge/chip/vrc1.cpp
Normal 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;
|
||||
};
|
105
higan/fc/cartridge/chip/vrc2.cpp
Normal file
105
higan/fc/cartridge/chip/vrc2.cpp
Normal 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;
|
||||
};
|
89
higan/fc/cartridge/chip/vrc3.cpp
Normal file
89
higan/fc/cartridge/chip/vrc3.cpp
Normal 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;
|
||||
};
|
173
higan/fc/cartridge/chip/vrc4.cpp
Normal file
173
higan/fc/cartridge/chip/vrc4.cpp
Normal 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;
|
||||
};
|
312
higan/fc/cartridge/chip/vrc6.cpp
Normal file
312
higan/fc/cartridge/chip/vrc6.cpp
Normal 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;
|
||||
};
|
144
higan/fc/cartridge/chip/vrc7.cpp
Normal file
144
higan/fc/cartridge/chip/vrc7.cpp
Normal 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;
|
||||
};
|
4
higan/fc/cartridge/serialization.cpp
Normal file
4
higan/fc/cartridge/serialization.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
auto Cartridge::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
return board->serialize(s);
|
||||
}
|
58
higan/fc/controller/controller.cpp
Normal file
58
higan/fc/controller/controller.cpp
Normal 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 {
|
||||
}
|
||||
|
||||
}
|
45
higan/fc/controller/controller.hpp
Normal file
45
higan/fc/controller/controller.hpp
Normal 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"
|
36
higan/fc/controller/gamepad/gamepad.cpp
Normal file
36
higan/fc/controller/gamepad/gamepad.cpp
Normal 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);
|
||||
}
|
||||
}
|
22
higan/fc/controller/gamepad/gamepad.hpp
Normal file
22
higan/fc/controller/gamepad/gamepad.hpp
Normal 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
44
higan/fc/cpu/cpu.cpp
Normal 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
58
higan/fc/cpu/cpu.hpp
Normal 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
49
higan/fc/cpu/memory.cpp
Normal 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);
|
||||
}
|
19
higan/fc/cpu/serialization.cpp
Normal file
19
higan/fc/cpu/serialization.cpp
Normal 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
64
higan/fc/cpu/timing.cpp
Normal 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
47
higan/fc/fc.hpp
Normal 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>
|
211
higan/fc/interface/interface.cpp
Normal file
211
higan/fc/interface/interface.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
70
higan/fc/interface/interface.hpp
Normal file
70
higan/fc/interface/interface.hpp
Normal 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
|
34
higan/fc/memory/memory.cpp
Normal file
34
higan/fc/memory/memory.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
6
higan/fc/memory/memory.hpp
Normal file
6
higan/fc/memory/memory.hpp
Normal 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
144
higan/fc/ppu/memory.cpp
Normal 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
72
higan/fc/ppu/ppu.cpp
Normal 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
124
higan/fc/ppu/ppu.hpp
Normal 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
211
higan/fc/ppu/render.cpp
Normal 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();
|
||||
}
|
64
higan/fc/ppu/serialization.cpp
Normal file
64
higan/fc/ppu/serialization.cpp
Normal 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);
|
||||
}
|
60
higan/fc/system/serialization.cpp
Normal file
60
higan/fc/system/serialization.cpp
Normal 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();
|
||||
}
|
91
higan/fc/system/system.cpp
Normal file
91
higan/fc/system/system.cpp
Normal 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 {
|
||||
}
|
||||
|
||||
}
|
44
higan/fc/system/system.hpp
Normal file
44
higan/fc/system/system.hpp
Normal 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
13
higan/gb/GNUmakefile
Normal 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
Loading…
Reference in a new issue