mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-20 11:41:41 +02:00
Compare commits
55 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c074c6e064 | ||
|
50420e3dd2 | ||
|
b08449215a | ||
|
9b452c9f5f | ||
|
3681961ca5 | ||
|
20ac95ee49 | ||
|
fdc41611cf | ||
|
839813d0f1 | ||
|
7f3cfa17b9 | ||
|
ae5d380d06 | ||
|
3ebc77c148 | ||
|
6ae0abe3d3 | ||
|
0955295475 | ||
|
7cdae5195a | ||
|
e2ee6689a0 | ||
|
55e507d5df | ||
|
a2d3b8ba15 | ||
|
1929ad47d2 | ||
|
7403e69307 | ||
|
19e1d89f00 | ||
|
aff00506c5 | ||
|
e846c83d47 | ||
|
06d44b4878 | ||
|
25eaaa82f4 | ||
|
2d83300235 | ||
|
680d16561e | ||
|
379ab6991f | ||
|
d3413db04a | ||
|
a7f7985581 | ||
|
b586471562 | ||
|
c33065fbd1 | ||
|
79e7e6ab9e | ||
|
3d3ac8c1db | ||
|
b0d2f5033e | ||
|
570eb9c5f5 | ||
|
7dc62e3a69 | ||
|
fc7d5991ce | ||
|
29be18ce0c | ||
|
810cbdafb4 | ||
|
4b29f4bad7 | ||
|
ef65bb862a | ||
|
0d0af39b44 | ||
|
6c83329cae | ||
|
32a95a9761 | ||
|
a89a3da77a | ||
|
7a748e093e | ||
|
d158c8f293 | ||
|
71bda4144a | ||
|
ad51f1478e | ||
|
d0ddd87e9c | ||
|
605a8aa3e9 | ||
|
a8323d0d2b | ||
|
d7998b23ef | ||
|
344e63d928 | ||
|
f1ebef2ea8 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
higan/profile/WonderSwan.sys/internal.ram
|
||||
higan/profile/WonderSwan Color.sys/internal.ram
|
@@ -1,16 +1,11 @@
|
||||
include ../nall/GNUmakefile
|
||||
|
||||
fc := fc
|
||||
sfc := sfc
|
||||
gb := gb
|
||||
gba := gba
|
||||
|
||||
profile := accuracy
|
||||
target := tomoko
|
||||
# target := loki
|
||||
# console := true
|
||||
|
||||
flags += -I. -I.. -O3
|
||||
objects := libco
|
||||
objects := libco audio video resource
|
||||
|
||||
# profile-guided optimization mode
|
||||
# pgo := instrument
|
||||
@@ -58,6 +53,11 @@ compile = \
|
||||
|
||||
all: build;
|
||||
|
||||
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco/)
|
||||
obj/audio.o: audio/audio.cpp $(call rwildcard,audio/)
|
||||
obj/video.o: video/video.cpp $(call rwildcard,video/)
|
||||
obj/resource.o: resource/resource.cpp $(call rwildcard,resource/)
|
||||
|
||||
ui := target-$(target)
|
||||
include $(ui)/GNUmakefile
|
||||
|
||||
|
90
higan/audio/audio.cpp
Normal file
90
higan/audio/audio.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#include "stream.cpp"
|
||||
Audio audio;
|
||||
|
||||
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
|
||||
if(channels_) channels = channels_();
|
||||
if(frequency_) frequency = frequency_();
|
||||
|
||||
streams.reset();
|
||||
reverb.reset();
|
||||
|
||||
reverb.resize(channels);
|
||||
for(auto c : range(channels)) {
|
||||
reverb[c].resize(7);
|
||||
reverb[c][0].resize(1229);
|
||||
reverb[c][1].resize(1559);
|
||||
reverb[c][2].resize(1907);
|
||||
reverb[c][3].resize(4057);
|
||||
reverb[c][4].resize(8117);
|
||||
reverb[c][5].resize(8311);
|
||||
reverb[c][6].resize(9931);
|
||||
}
|
||||
}
|
||||
|
||||
auto Audio::setInterface(Interface* interface) -> void {
|
||||
this->interface = interface;
|
||||
}
|
||||
|
||||
auto Audio::setVolume(double volume) -> void {
|
||||
this->volume = volume;
|
||||
}
|
||||
|
||||
auto Audio::setBalance(double balance) -> void {
|
||||
this->balance = balance;
|
||||
}
|
||||
|
||||
auto Audio::setReverb(bool enabled) -> void {
|
||||
this->reverbEnable = enabled;
|
||||
}
|
||||
|
||||
auto Audio::createStream(uint channels, double frequency) -> shared_pointer<Stream> {
|
||||
shared_pointer<Stream> stream = new Stream;
|
||||
stream->reset(channels, frequency, this->frequency);
|
||||
streams.append(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
auto Audio::process() -> void {
|
||||
while(true) {
|
||||
for(auto& stream : streams) {
|
||||
if(!stream->pending()) return;
|
||||
}
|
||||
|
||||
double samples[channels] = {0};
|
||||
for(auto& stream : streams) {
|
||||
double buffer[16];
|
||||
uint length = stream->read(buffer), offset = 0;
|
||||
|
||||
for(auto c : range(channels)) {
|
||||
samples[c] += buffer[offset];
|
||||
if(++offset >= length) offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for(auto c : range(channels)) {
|
||||
samples[c] /= streams.size();
|
||||
|
||||
if(reverbEnable) {
|
||||
samples[c] *= 0.125;
|
||||
for(auto n : range(7)) samples[c] += 0.125 * reverb[c][n].last();
|
||||
for(auto n : range(7)) reverb[c][n].write(samples[c]);
|
||||
samples[c] *= 8.000;
|
||||
}
|
||||
|
||||
samples[c] *= volume;
|
||||
}
|
||||
|
||||
if(channels == 2) {
|
||||
if(balance < 0.0) samples[1] *= 1.0 + balance;
|
||||
if(balance > 0.0) samples[0] *= 1.0 - balance;
|
||||
}
|
||||
|
||||
interface->audioSample(samples, channels);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
65
higan/audio/audio.hpp
Normal file
65
higan/audio/audio.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/dsp/iir/biquad.hpp>
|
||||
#include <nall/dsp/resampler/cubic.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Interface;
|
||||
struct Audio;
|
||||
struct Stream;
|
||||
|
||||
struct Audio {
|
||||
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
|
||||
auto setInterface(Interface*) -> void;
|
||||
|
||||
auto setVolume(double volume) -> void;
|
||||
auto setBalance(double balance) -> void;
|
||||
auto setReverb(bool enabled) -> 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 = 0.0;
|
||||
|
||||
double volume = 1.0;
|
||||
double balance = 0.0;
|
||||
|
||||
bool reverbEnable = false;
|
||||
vector<vector<queue<double>>> reverb;
|
||||
|
||||
friend class Stream;
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
auto reset(uint channels, double inputFrequency, double outputFrequency) -> 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:
|
||||
const uint order = 6; //Nth-order filter (must be an even number)
|
||||
struct Channel {
|
||||
vector<DSP::IIR::Biquad> iir;
|
||||
DSP::Resampler::Cubic resampler;
|
||||
};
|
||||
vector<Channel> channels;
|
||||
|
||||
friend class Audio;
|
||||
};
|
||||
|
||||
extern Audio audio;
|
||||
|
||||
}
|
35
higan/audio/stream.cpp
Normal file
35
higan/audio/stream.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
auto Stream::reset(uint channels_, double inputFrequency, double outputFrequency) -> void {
|
||||
channels.reset();
|
||||
channels.resize(channels_);
|
||||
|
||||
for(auto& channel : channels) {
|
||||
if(outputFrequency / inputFrequency <= 0.5) {
|
||||
channel.iir.resize(order / 2);
|
||||
for(auto phase : range(order / 2)) {
|
||||
double q = DSP::IIR::Biquad::butterworth(order, phase);
|
||||
channel.iir[phase].reset(DSP::IIR::Biquad::Type::LowPass, 20000.0 / inputFrequency, q);
|
||||
}
|
||||
}
|
||||
|
||||
channel.resampler.reset(inputFrequency, outputFrequency);
|
||||
}
|
||||
}
|
||||
|
||||
auto Stream::pending() const -> bool {
|
||||
return channels && channels[0].resampler.pending();
|
||||
}
|
||||
|
||||
auto Stream::read(double* samples) -> uint {
|
||||
for(auto c : range(channels)) samples[c] = channels[c].resampler.read();
|
||||
return channels.size();
|
||||
}
|
||||
|
||||
auto Stream::write(const double* samples) -> void {
|
||||
for(auto c : range(channels)) {
|
||||
double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
|
||||
for(auto& iir : channels[c].iir) sample = iir.process(sample);
|
||||
channels[c].resampler.write(sample);
|
||||
}
|
||||
|
||||
audio.process();
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=higan
|
||||
Comment=Nintendo emulator
|
||||
Comment=Emulator
|
||||
Exec=higan
|
||||
Icon=higan
|
||||
Terminal=false
|
||||
|
@@ -11,7 +11,7 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>higan.icns</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
@@ -1,23 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <nall/nall.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include <audio/audio.hpp>
|
||||
#include <video/video.hpp>
|
||||
#include <resource/resource.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "097";
|
||||
static const string Version = "099";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
||||
#if defined(PROFILE_ACCURACY)
|
||||
static const string Profile = "Accuracy";
|
||||
#elif defined(PROFILE_BALANCED)
|
||||
static const string Profile = "Balanced";
|
||||
#elif defined(PROFILE_PERFORMANCE)
|
||||
static const string Profile = "Performance";
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "interface.hpp"
|
||||
@@ -52,5 +47,3 @@ template<typename R, typename... P> struct hook<auto (P...) -> R> {
|
||||
#else
|
||||
#define privileged private
|
||||
#endif
|
||||
|
||||
using varuint = varuint_t<uint>;
|
||||
|
@@ -4,6 +4,7 @@ namespace Emulator {
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
uint width;
|
||||
uint height;
|
||||
@@ -16,13 +17,13 @@ struct Interface {
|
||||
} capability;
|
||||
} information;
|
||||
|
||||
struct Media {
|
||||
struct Medium {
|
||||
uint id;
|
||||
string name;
|
||||
string type;
|
||||
bool bootable; //false for cartridge slots (eg Sufami Turbo cartridges)
|
||||
};
|
||||
vector<Media> media;
|
||||
vector<Medium> media;
|
||||
|
||||
struct Device {
|
||||
uint id;
|
||||
@@ -34,23 +35,22 @@ struct Interface {
|
||||
string name;
|
||||
uintptr guid; //user data field
|
||||
};
|
||||
vector<Input> input;
|
||||
vector<uint> order;
|
||||
vector<Input> inputs;
|
||||
};
|
||||
|
||||
struct Port {
|
||||
uint id;
|
||||
string name;
|
||||
vector<Device> device;
|
||||
vector<Device> devices;
|
||||
};
|
||||
vector<Port> port;
|
||||
vector<Port> ports;
|
||||
|
||||
struct Bind {
|
||||
virtual auto loadRequest(uint, string, string, bool) -> void {}
|
||||
virtual auto loadRequest(uint, string, bool) -> void {}
|
||||
virtual auto saveRequest(uint, string) -> void {}
|
||||
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
|
||||
virtual auto audioSample(int16, int16) -> void {}
|
||||
virtual auto audioSample(const double*, uint) -> void {}
|
||||
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
|
||||
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
|
||||
virtual auto dipSettings(const Markup::Node&) -> uint { return 0; }
|
||||
@@ -64,7 +64,7 @@ struct Interface {
|
||||
auto loadRequest(uint id, string path, bool required) -> void { return bind->loadRequest(id, path, required); }
|
||||
auto saveRequest(uint id, string path) -> void { return bind->saveRequest(id, path); }
|
||||
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
|
||||
auto audioSample(int16 lsample, int16 rsample) -> void { return bind->audioSample(lsample, rsample); }
|
||||
auto audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
|
||||
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
|
||||
auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
|
||||
auto dipSettings(const Markup::Node& node) -> uint { return bind->dipSettings(node); }
|
||||
@@ -74,7 +74,13 @@ struct Interface {
|
||||
//information
|
||||
virtual auto manifest() -> string = 0;
|
||||
virtual auto title() -> string = 0;
|
||||
|
||||
//video information
|
||||
virtual auto videoFrequency() -> double = 0;
|
||||
virtual auto videoColors() -> uint32 { return 1 << 19; }
|
||||
virtual auto videoColor(uint32 color) -> uint64 { return 0; }
|
||||
|
||||
//audio information
|
||||
virtual auto audioFrequency() -> double = 0;
|
||||
|
||||
//media interface
|
||||
@@ -108,6 +114,9 @@ struct Interface {
|
||||
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; }
|
||||
|
||||
//shared functions
|
||||
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -1,16 +1,16 @@
|
||||
fc_objects := fc-interface fc-system fc-scheduler fc-input
|
||||
fc_objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
|
||||
fc_objects += fc-cheat fc-video
|
||||
objects += $(fc_objects)
|
||||
processors += r6502
|
||||
|
||||
obj/fc-interface.o: $(fc)/interface/interface.cpp $(call rwildcard,$(fc)/interface/)
|
||||
obj/fc-system.o: $(fc)/system/system.cpp $(call rwildcard,$(fc)/system/)
|
||||
obj/fc-scheduler.o: $(fc)/scheduler/scheduler.cpp $(call rwildcard,$(fc)/scheduler/)
|
||||
obj/fc-input.o: $(fc)/input/input.cpp $(call rwildcard,$(fc)/input/)
|
||||
obj/fc-memory.o: $(fc)/memory/memory.cpp $(call rwildcard,$(fc)/memory/)
|
||||
obj/fc-cartridge.o: $(fc)/cartridge/cartridge.cpp $(call rwildcard,$(fc)/cartridge/)
|
||||
obj/fc-cpu.o: $(fc)/cpu/cpu.cpp $(call rwildcard,$(fc)/cpu/)
|
||||
obj/fc-apu.o: $(fc)/apu/apu.cpp $(call rwildcard,$(fc)/apu/)
|
||||
obj/fc-ppu.o: $(fc)/ppu/ppu.cpp $(call rwildcard,$(fc)/ppu/)
|
||||
obj/fc-cheat.o: $(fc)/cheat/cheat.cpp $(call rwildcard,$(fc)/cheat/)
|
||||
obj/fc-video.o: $(fc)/video/video.cpp $(call rwildcard,$(fc)/video/)
|
||||
objects += fc-interface fc-system fc-scheduler fc-input
|
||||
objects += fc-memory fc-cartridge fc-cpu fc-apu fc-ppu
|
||||
objects += fc-cheat
|
||||
|
||||
obj/fc-interface.o: fc/interface/interface.cpp $(call rwildcard,fc/interface/)
|
||||
obj/fc-system.o: fc/system/system.cpp $(call rwildcard,fc/system/)
|
||||
obj/fc-scheduler.o: fc/scheduler/scheduler.cpp $(call rwildcard,fc/scheduler/)
|
||||
obj/fc-input.o: fc/input/input.cpp $(call rwildcard,fc/input/)
|
||||
obj/fc-memory.o: fc/memory/memory.cpp $(call rwildcard,fc/memory/)
|
||||
obj/fc-cartridge.o: fc/cartridge/cartridge.cpp $(call rwildcard,fc/cartridge/)
|
||||
obj/fc-cpu.o: fc/cpu/cpu.cpp $(call rwildcard,fc/cpu/)
|
||||
obj/fc-apu.o: fc/apu/apu.cpp $(call rwildcard,fc/apu/)
|
||||
obj/fc-ppu.o: fc/ppu/ppu.cpp $(call rwildcard,fc/ppu/)
|
||||
obj/fc-cheat.o: fc/cheat/cheat.cpp $(call rwildcard,fc/cheat/)
|
||||
|
@@ -34,43 +34,37 @@ APU::APU() {
|
||||
}
|
||||
}
|
||||
|
||||
auto APU::Main() -> void {
|
||||
apu.main();
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
uint pulse_output, triangle_output, noise_output, dmc_output;
|
||||
|
||||
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();
|
||||
|
||||
pulse_output = pulse[0].clock();
|
||||
pulse_output += pulse[1].clock();
|
||||
triangle_output = triangle.clock();
|
||||
noise_output = noise.clock();
|
||||
dmc_output = dmc.clock();
|
||||
clock_frame_counter_divider();
|
||||
|
||||
clock_frame_counter_divider();
|
||||
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
|
||||
|
||||
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
|
||||
output = filter.run_hipass_strong(output);
|
||||
output += cartridge_sample;
|
||||
output = filter.run_hipass_weak(output);
|
||||
//output = filter.run_lopass(output);
|
||||
output = sclamp<16>(output);
|
||||
|
||||
output = filter.run_hipass_strong(output);
|
||||
output += cartridge_sample;
|
||||
output = filter.run_hipass_weak(output);
|
||||
//output = filter.run_lopass(output);
|
||||
output = sclamp<16>(output);
|
||||
stream->sample(output / 32768.0);
|
||||
|
||||
interface->audioSample(output, output);
|
||||
|
||||
tick();
|
||||
}
|
||||
tick();
|
||||
}
|
||||
|
||||
auto APU::tick() -> void {
|
||||
clock += 12;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto APU::set_irq_line() -> void {
|
||||
@@ -94,7 +88,8 @@ auto APU::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::reset() -> void {
|
||||
create(APU::Main, 21477272);
|
||||
create(APU::Enter, 21'477'272);
|
||||
stream = Emulator::audio.createStream(1, 21'477'272.0 / 12.0);
|
||||
|
||||
pulse[0].reset();
|
||||
pulse[1].reset();
|
||||
|
@@ -1,7 +1,9 @@
|
||||
struct APU : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
APU();
|
||||
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
auto set_irq_line() -> void;
|
||||
|
@@ -5,20 +5,14 @@ struct BandaiFCG : Board {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(1);
|
||||
irq_counter_enable = false;
|
||||
}
|
||||
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(1);
|
||||
irq_counter_enable = false;
|
||||
}
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto ciram_addr(uint addr) const -> uint {
|
||||
@@ -33,7 +27,7 @@ struct BandaiFCG : Board {
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
if(addr & 0x8000) {
|
||||
bool region = addr & 0x4000;
|
||||
uint bank = (region == 0 ? prg_bank : 0x0f);
|
||||
uint bank = (region == 0 ? prg_bank : (uint8)0x0f);
|
||||
return prgrom.read((bank << 14) | (addr & 0x3fff));
|
||||
}
|
||||
return cpu.mdr();
|
||||
|
@@ -36,10 +36,10 @@ Board::Board(Markup::Node& document) {
|
||||
chrrom.size = crom["size"].natural();
|
||||
chrram.size = cram["size"].natural();
|
||||
|
||||
if(prgrom.size) prgrom.data = new uint8[prgrom.size]();
|
||||
if(prgram.size) prgram.data = new uint8[prgram.size]();
|
||||
if(chrrom.size) chrrom.data = new uint8[chrrom.size]();
|
||||
if(chrram.size) chrram.data = new uint8[chrram.size]();
|
||||
if(prgrom.size) prgrom.data = new uint8_t[prgrom.size]();
|
||||
if(prgram.size) prgram.data = new uint8_t[prgram.size]();
|
||||
if(chrrom.size) chrrom.data = new uint8_t[chrrom.size]();
|
||||
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
|
||||
|
||||
if(auto name = prom["name"].text()) interface->loadRequest(ID::ProgramROM, name, true);
|
||||
if(auto name = pram["name"].text()) interface->loadRequest(ID::ProgramRAM, name, false);
|
||||
@@ -80,19 +80,13 @@ auto Board::mirror(uint addr, uint size) -> uint {
|
||||
}
|
||||
|
||||
auto Board::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cartridge.clock += 12 * 4095;
|
||||
tick();
|
||||
}
|
||||
cartridge.clock += 12 * 4095;
|
||||
tick();
|
||||
}
|
||||
|
||||
auto Board::tick() -> void {
|
||||
cartridge.clock += 12;
|
||||
if(cartridge.clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
if(cartridge.clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto Board::chr_read(uint addr) -> uint8 {
|
||||
|
@@ -44,25 +44,19 @@ struct Sunsoft5B : Board {
|
||||
} pulse[3];
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(irq_enable);
|
||||
}
|
||||
|
||||
if(irq_counter_enable) {
|
||||
if(--irq_counter == 0xffff) {
|
||||
cpu.set_irq_line(irq_enable);
|
||||
}
|
||||
}
|
||||
|
||||
pulse[0].clock();
|
||||
pulse[1].clock();
|
||||
pulse[2].clock();
|
||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||
apu.set_sample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
pulse[0].clock();
|
||||
pulse[1].clock();
|
||||
pulse[2].clock();
|
||||
int16 output = dac[pulse[0].output] + dac[pulse[1].output] + dac[pulse[2].output];
|
||||
apu.set_sample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_read(uint addr) -> uint8 {
|
||||
|
@@ -6,10 +6,6 @@ namespace Famicom {
|
||||
#include "board/board.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::loaded() const -> bool {
|
||||
return _loaded;
|
||||
}
|
||||
|
||||
auto Cartridge::sha256() const -> string {
|
||||
return _sha256;
|
||||
}
|
||||
@@ -22,8 +18,8 @@ auto Cartridge::title() const -> string {
|
||||
return information.title;
|
||||
}
|
||||
|
||||
auto Cartridge::Main() -> void {
|
||||
cartridge.main();
|
||||
auto Cartridge::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cartridge.main();
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
@@ -40,14 +36,9 @@ auto Cartridge::load() -> void {
|
||||
sha.data(board->prgrom.data, board->prgrom.size);
|
||||
sha.data(board->chrrom.data, board->chrrom.size);
|
||||
_sha256 = sha.digest();
|
||||
|
||||
system.load();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
_loaded = false;
|
||||
memory.reset();
|
||||
}
|
||||
|
||||
@@ -56,7 +47,7 @@ auto Cartridge::power() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::reset() -> void {
|
||||
create(Cartridge::Main, 21477272);
|
||||
create(Cartridge::Enter, 21'477'272);
|
||||
board->reset();
|
||||
}
|
||||
|
||||
|
@@ -2,10 +2,9 @@
|
||||
#include "board/board.hpp"
|
||||
|
||||
struct Cartridge : Thread {
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
auto loaded() const -> bool;
|
||||
auto sha256() const -> string;
|
||||
auto manifest() const -> string;
|
||||
auto title() const -> string;
|
||||
@@ -31,7 +30,6 @@ struct Cartridge : Thread {
|
||||
|
||||
//privileged:
|
||||
Board* board = nullptr;
|
||||
bool _loaded = false;
|
||||
string _sha256;
|
||||
|
||||
auto prg_read(uint addr) -> uint8;
|
||||
|
@@ -4,14 +4,8 @@ struct MMC1 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(writedelay) writedelay--;
|
||||
tick();
|
||||
}
|
||||
if(writedelay) writedelay--;
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) -> uint {
|
||||
|
@@ -3,15 +3,9 @@ struct MMC3 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
|
@@ -4,17 +4,11 @@ struct MMC5 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
//scanline() resets this; if no scanlines detected, enter video blanking period
|
||||
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||
|
||||
//scanline() resets this; if no scanlines detected, enter video blanking period
|
||||
if(++cpu_cycle_counter >= 200) blank(); //113-114 normal; ~2500 across Vblank period
|
||||
|
||||
cpu.set_irq_line(irq_enable && irq_pending);
|
||||
tick();
|
||||
}
|
||||
cpu.set_irq_line(irq_enable && irq_pending);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto scanline(uint y) -> void {
|
||||
@@ -94,7 +88,7 @@ struct MMC5 : Chip {
|
||||
auto prg_write(uint addr, uint8 data) -> void {
|
||||
if((addr & 0xfc00) == 0x5c00) {
|
||||
//writes 0x00 *during* Vblank (not during screen rendering ...)
|
||||
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : 0x00;
|
||||
if(exram_mode == 0 || exram_mode == 1) exram[addr & 0x03ff] = in_frame ? data : (uint8)0x00;
|
||||
if(exram_mode == 2) exram[addr & 0x03ff] = data;
|
||||
return;
|
||||
}
|
||||
@@ -277,7 +271,7 @@ struct MMC5 : Chip {
|
||||
switch(nametable_mode[(addr >> 10) & 3]) {
|
||||
case 0: return ppu.ciram_read(0x0000 | (addr & 0x03ff));
|
||||
case 1: return ppu.ciram_read(0x0400 | (addr & 0x03ff));
|
||||
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : 0x00;
|
||||
case 2: return exram_mode < 2 ? exram[addr & 0x03ff] : (uint8)0x00;
|
||||
case 3: return (hcounter & 2) == 0 ? fillmode_tile : fillmode_color;
|
||||
}
|
||||
}
|
||||
|
@@ -3,15 +3,9 @@ struct MMC6 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
if(irq_delay) irq_delay--;
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto irq_test(uint addr) -> void {
|
||||
|
@@ -3,31 +3,25 @@ struct VRC3 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) { //16-bit
|
||||
if(++irq_counter.w == 0) {
|
||||
irq_line = 1;
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_counter.w = irq_latch;
|
||||
}
|
||||
}
|
||||
if(irq_mode == 1) { //8-bit
|
||||
if(++irq_counter.l == 0) {
|
||||
irq_line = 1;
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_counter.l = irq_latch;
|
||||
}
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) { //16-bit
|
||||
if(++irq_counter.w == 0) {
|
||||
irq_line = 1;
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_counter.w = irq_latch;
|
||||
}
|
||||
}
|
||||
if(irq_mode == 1) { //8-bit
|
||||
if(++irq_counter.l == 0) {
|
||||
irq_line = 1;
|
||||
irq_enable = irq_acknowledge;
|
||||
irq_counter.l = irq_latch;
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
@@ -90,8 +84,8 @@ struct VRC3 : Chip {
|
||||
uint16 irq_latch;
|
||||
struct {
|
||||
union {
|
||||
uint16 w;
|
||||
struct { uint8 order_lsb2(l, h); };
|
||||
uint16_t w;
|
||||
struct { uint8_t order_lsb2(l, h); };
|
||||
};
|
||||
} irq_counter;
|
||||
bool irq_line;
|
||||
|
@@ -3,26 +3,11 @@ struct VRC4 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irq_mode == 1) {
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
@@ -32,9 +17,18 @@ struct VRC4 : Chip {
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cpu.set_irq_line(irq_line);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
|
@@ -77,26 +77,11 @@ struct VRC6 : Chip {
|
||||
} sawtooth;
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irq_mode == 1) {
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
@@ -105,16 +90,25 @@ struct VRC6 : Chip {
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
|
||||
pulse1.clock();
|
||||
pulse2.clock();
|
||||
sawtooth.clock();
|
||||
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
|
||||
apu.set_sample(-output);
|
||||
|
||||
tick();
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
|
||||
pulse1.clock();
|
||||
pulse2.clock();
|
||||
sawtooth.clock();
|
||||
int output = (pulse1.output + pulse2.output + sawtooth.output) << 7;
|
||||
apu.set_sample(-output);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto prg_addr(uint addr) const -> uint {
|
||||
|
@@ -6,26 +6,11 @@ struct VRC7 : Chip {
|
||||
}
|
||||
|
||||
auto main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(irq_mode == 1) {
|
||||
if(irq_enable) {
|
||||
if(irq_mode == 0) {
|
||||
irq_scalar -= 3;
|
||||
if(irq_scalar <= 0) {
|
||||
irq_scalar += 341;
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
@@ -34,10 +19,19 @@ struct VRC7 : Chip {
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
|
||||
tick();
|
||||
if(irq_mode == 1) {
|
||||
if(irq_counter == 0xff) {
|
||||
irq_counter = irq_latch;
|
||||
irq_line = 1;
|
||||
} else {
|
||||
irq_counter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
cpu.set_irq_line(irq_line);
|
||||
|
||||
tick();
|
||||
}
|
||||
|
||||
auto reg_write(uint addr, uint8 data) -> void {
|
||||
|
@@ -7,33 +7,23 @@ namespace Famicom {
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cpu.main();
|
||||
}
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(status.interrupt_pending) {
|
||||
interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
if(status.interrupt_pending) return interrupt();
|
||||
exec();
|
||||
}
|
||||
|
||||
auto CPU::add_clocks(uint clocks) -> void {
|
||||
apu.clock -= clocks;
|
||||
if(apu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(apu.thread);
|
||||
if(apu.clock < 0 && !scheduler.synchronizing()) co_switch(apu.thread);
|
||||
|
||||
ppu.clock -= clocks;
|
||||
if(ppu.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(ppu.thread);
|
||||
if(ppu.clock < 0 && !scheduler.synchronizing()) co_switch(ppu.thread);
|
||||
|
||||
cartridge.clock -= clocks;
|
||||
if(cartridge.clock < 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cartridge.thread);
|
||||
if(cartridge.clock < 0 && !scheduler.synchronizing()) co_switch(cartridge.thread);
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
@@ -48,7 +38,7 @@ auto CPU::power() -> void {
|
||||
|
||||
auto CPU::reset() -> void {
|
||||
R6502::reset();
|
||||
create(CPU::Enter, 21477272);
|
||||
create(CPU::Enter, 21'477'272);
|
||||
|
||||
regs.pc = bus.read(0xfffc) << 0;
|
||||
regs.pc |= bus.read(0xfffd) << 8;
|
||||
|
@@ -1,22 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
//license: GPLv3
|
||||
//started: 2011-09-05
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <processor/r6502/r6502.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
namespace Info {
|
||||
static const string Name = "bnes";
|
||||
static const uint SerializerVersion = 2;
|
||||
static const uint SerializerVersion = 3;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bnes - Famicom emulator
|
||||
authors: byuu, Ryphecha
|
||||
license: GPLv3
|
||||
project started: 2011-09-05
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
namespace Famicom {
|
||||
@@ -27,7 +22,7 @@ namespace Famicom {
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
||||
thread = co_create(65'536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
}
|
||||
@@ -51,7 +46,6 @@ namespace Famicom {
|
||||
#include <fc/apu/apu.hpp>
|
||||
#include <fc/ppu/ppu.hpp>
|
||||
#include <fc/cheat/cheat.hpp>
|
||||
#include <fc/video/video.hpp>
|
||||
}
|
||||
|
||||
#include <fc/interface/interface.hpp>
|
||||
|
@@ -15,12 +15,15 @@ auto Input::latch(bool data) -> void {
|
||||
}
|
||||
|
||||
auto Input::data(bool port) -> bool {
|
||||
//table to convert native button ordering to Emulator::Interface ordering
|
||||
static const uint lookup[] = {5, 4, 6, 7, 0, 1, 2, 3};
|
||||
|
||||
bool result = 0;
|
||||
|
||||
if(port == 0) {
|
||||
if(port1 == Device::Joypad) {
|
||||
if(counter1 >= 8) return 1;
|
||||
result = interface->inputPoll(0, 0u, counter1);
|
||||
result = interface->inputPoll(0, 0u, lookup[counter1]);
|
||||
if(latchdata == 0) counter1++;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +31,7 @@ auto Input::data(bool port) -> bool {
|
||||
if(port == 1) {
|
||||
if(port2 == Device::Joypad) {
|
||||
if(counter2 >= 8) return 1;
|
||||
result = interface->inputPoll(1, 0u, counter2);
|
||||
result = interface->inputPoll(1, 0u, lookup[counter2]);
|
||||
if(latchdata == 0) counter2++;
|
||||
}
|
||||
}
|
||||
|
@@ -8,37 +8,38 @@ Settings settings;
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.name = "Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.resettable = true;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.resettable = true;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
media.append({ID::Famicom, "Famicom", "fc", true});
|
||||
|
||||
{ Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.input.append({4, 0, "Up" });
|
||||
device.input.append({5, 0, "Down" });
|
||||
device.input.append({6, 0, "Left" });
|
||||
device.input.append({7, 0, "Right" });
|
||||
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||
this->device.append(device);
|
||||
device.inputs.append({0, 0, "Up" });
|
||||
device.inputs.append({1, 0, "Down" });
|
||||
device.inputs.append({2, 0, "Left" });
|
||||
device.inputs.append({3, 0, "Right" });
|
||||
device.inputs.append({4, 0, "B" });
|
||||
device.inputs.append({5, 0, "A" });
|
||||
device.inputs.append({6, 0, "Select"});
|
||||
device.inputs.append({7, 0, "Start" });
|
||||
devices.append(device);
|
||||
}
|
||||
|
||||
port.append({0, "Port 1"});
|
||||
port.append({1, "Port 2"});
|
||||
ports.append({0, "Port 1"});
|
||||
ports.append({1, "Port 2"});
|
||||
|
||||
for(auto& device : this->device) {
|
||||
for(auto& port : this->port) {
|
||||
for(auto& device : devices) {
|
||||
for(auto& port : ports) {
|
||||
if(device.portmask & (1 << port.id)) {
|
||||
port.device.append(device);
|
||||
port.devices.append(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,12 +57,67 @@ auto Interface::videoFrequency() -> double {
|
||||
return 21477272.0 / (262.0 * 1364.0 - 4.0);
|
||||
}
|
||||
|
||||
auto Interface::videoColors() -> uint32 {
|
||||
return 1 << 9;
|
||||
}
|
||||
|
||||
auto Interface::videoColor(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 ? (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::audioFrequency() -> double {
|
||||
return 21477272.0 / 12.0;
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return cartridge.loaded();
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::sha256() -> string {
|
||||
@@ -84,7 +140,7 @@ auto Interface::group(uint id) -> uint {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
cartridge.load();
|
||||
system.load();
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -131,7 +187,7 @@ auto Interface::save(uint id, const stream& stream) -> void {
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
cartridge.unload();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
@@ -147,7 +203,7 @@ auto Interface::run() -> void {
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runtosave();
|
||||
system.runToSave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
@@ -180,7 +236,11 @@ auto Interface::get(const string& name) -> any {
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
if(name == "Scanline Emulation" && value.is<bool>()) return settings.scanlineEmulation = value.get<bool>(), true;
|
||||
return false;
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ struct Interface : Emulator::Interface {
|
||||
auto manifest() -> string;
|
||||
auto title() -> string;
|
||||
auto videoFrequency() -> double;
|
||||
auto videoColors() -> uint32;
|
||||
auto videoColor(uint32 color) -> uint64;
|
||||
auto audioFrequency() -> double;
|
||||
|
||||
auto loaded() -> bool;
|
||||
@@ -53,7 +55,7 @@ struct Interface : Emulator::Interface {
|
||||
auto set(const string& name, const any& value) -> bool override;
|
||||
|
||||
private:
|
||||
vector<Device> device;
|
||||
vector<Device> devices;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
|
@@ -2,21 +2,16 @@
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "serialization.cpp"
|
||||
PPU ppu;
|
||||
|
||||
auto PPU::Main() -> void {
|
||||
ppu.main();
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::PPU) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
raster_scanline();
|
||||
}
|
||||
raster_scanline();
|
||||
}
|
||||
|
||||
auto PPU::tick() -> void {
|
||||
@@ -47,14 +42,18 @@ auto PPU::scanline() -> void {
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
status.field ^= 1;
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
auto PPU::refresh() -> void {
|
||||
Emulator::video.refresh(buffer, 256 * sizeof(uint32), 256, 240);
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
}
|
||||
|
||||
auto PPU::reset() -> void {
|
||||
create(PPU::Main, 21477272);
|
||||
create(PPU::Enter, 21'477'272);
|
||||
|
||||
status.mdr = 0x00;
|
||||
status.field = 0;
|
||||
@@ -113,7 +112,6 @@ auto PPU::read(uint16 addr) -> uint8 {
|
||||
break;
|
||||
case 4: //OAMDATA
|
||||
result = oam[status.oam_addr];
|
||||
if((status.oam_addr & 3) == 3) result &= 0xe3;
|
||||
break;
|
||||
case 7: //PPUDATA
|
||||
if(raster_enable() && (status.ly <= 240 || status.ly == 261)) return 0x00;
|
||||
@@ -164,6 +162,7 @@ auto PPU::write(uint16 addr, uint8 data) -> void {
|
||||
status.oam_addr = data;
|
||||
return;
|
||||
case 4: //OAMDATA
|
||||
if(status.oam_addr.bits(0,1) == 2) data.bits(2,4) = 0; //clear non-existent bits (always read back as 0)
|
||||
oam[status.oam_addr++] = data;
|
||||
return;
|
||||
case 5: //PPUSCROLL
|
||||
|
@@ -1,10 +1,11 @@
|
||||
struct PPU : Thread {
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto tick() -> void;
|
||||
|
||||
auto scanline() -> void;
|
||||
auto frame() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
@@ -4,25 +4,41 @@ namespace Famicom {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
auto Scheduler::enter() -> void {
|
||||
host_thread = co_active();
|
||||
co_switch(thread);
|
||||
}
|
||||
|
||||
auto Scheduler::exit(ExitReason reason) -> void {
|
||||
exit_reason = reason;
|
||||
thread = co_active();
|
||||
co_switch(host_thread);
|
||||
}
|
||||
|
||||
auto Scheduler::power() -> void {
|
||||
}
|
||||
|
||||
auto Scheduler::reset() -> void {
|
||||
host_thread = co_active();
|
||||
thread = cpu.thread;
|
||||
sync = SynchronizeMode::None;
|
||||
exit_reason = ExitReason::UnknownEvent;
|
||||
host = co_active();
|
||||
resume = cpu.thread;
|
||||
}
|
||||
|
||||
auto Scheduler::enter(Mode mode_) -> Event {
|
||||
mode = mode_;
|
||||
host = co_active();
|
||||
co_switch(resume);
|
||||
if(event == Event::Frame) ppu.refresh();
|
||||
return event;
|
||||
}
|
||||
|
||||
auto Scheduler::exit(Event event_) -> void {
|
||||
event = event_;
|
||||
resume = co_active();
|
||||
co_switch(host);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||
if(thread == ppu.thread) {
|
||||
while(enter(Mode::SynchronizePPU) != Event::Synchronize);
|
||||
} else {
|
||||
resume = thread;
|
||||
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == ppu.thread && mode == Mode::SynchronizePPU) return exit(Event::Synchronize);
|
||||
if(co_active() != ppu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronizing() const -> bool {
|
||||
return mode == Mode::SynchronizeAll;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,16 +1,28 @@
|
||||
struct Scheduler : property<Scheduler> {
|
||||
enum class SynchronizeMode : uint { None, PPU, All } sync;
|
||||
enum class ExitReason : uint { UnknownEvent, FrameEvent, SynchronizeEvent };
|
||||
struct Scheduler {
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizePPU,
|
||||
SynchronizeAll,
|
||||
};
|
||||
|
||||
auto enter() -> void;
|
||||
auto exit(ExitReason) -> void;
|
||||
enum class Event : uint {
|
||||
Unknown,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
auto enter(Mode = Mode::Run) -> Event;
|
||||
auto exit(Event) -> void;
|
||||
auto synchronize(cothread_t) -> void;
|
||||
auto synchronize() -> void;
|
||||
auto synchronizing() const -> bool;
|
||||
|
||||
cothread_t host_thread; //program thread (used to exit emulation)
|
||||
cothread_t thread; //active emulation thread (used to enter emulation)
|
||||
readonly<ExitReason> exit_reason;
|
||||
private:
|
||||
cothread_t host = nullptr;
|
||||
cothread_t resume = nullptr;
|
||||
Mode mode = Mode::Run;
|
||||
Event event = Event::Unknown;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(serialize_size);
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -28,14 +28,14 @@ auto System::unserialize(serializer& s) -> bool {
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
}
|
||||
|
||||
auto System::serialize_all(serializer& s) -> void {
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
system.serialize(s);
|
||||
input.serialize(s);
|
||||
cartridge.serialize(s);
|
||||
@@ -44,7 +44,7 @@ auto System::serialize_all(serializer& s) -> void {
|
||||
ppu.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serialize_init() -> void {
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0, version = 0;
|
||||
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serialize_size = s.size();
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
}
|
||||
|
@@ -2,50 +2,35 @@
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
auto System::loaded() const -> bool { return _loaded; }
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
auto System::runtosave() -> void {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::PPU;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.thread = cpu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.thread = apu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.thread = cartridge.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
||||
}
|
||||
|
||||
auto System::runthreadtosave() -> void {
|
||||
while(true) {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason() == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
auto System::runToSave() -> void {
|
||||
scheduler.synchronize(ppu.thread);
|
||||
scheduler.synchronize(cpu.thread);
|
||||
scheduler.synchronize(apu.thread);
|
||||
scheduler.synchronize(cartridge.thread);
|
||||
}
|
||||
|
||||
auto System::load() -> void {
|
||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
cartridge.load();
|
||||
serializeInit();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
serialize_init();
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
cartridge.unload();
|
||||
_loaded = false;
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
@@ -54,22 +39,28 @@ auto System::power() -> void {
|
||||
apu.power();
|
||||
ppu.power();
|
||||
input.reset();
|
||||
scheduler.power();
|
||||
reset();
|
||||
}
|
||||
|
||||
auto System::reset() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
|
||||
Emulator::audio.reset();
|
||||
Emulator::audio.setInterface(interface);
|
||||
|
||||
cartridge.reset();
|
||||
cpu.reset();
|
||||
apu.reset();
|
||||
ppu.reset();
|
||||
input.reset();
|
||||
scheduler.reset();
|
||||
video.reset();
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
assert(interface != 0);
|
||||
assert(interface != nullptr);
|
||||
input.connect(0, Input::Device::Joypad);
|
||||
input.connect(1, Input::Device::None);
|
||||
}
|
||||
|
@@ -1,27 +1,36 @@
|
||||
struct System {
|
||||
auto loaded() const -> bool;
|
||||
|
||||
auto run() -> void;
|
||||
auto runtosave() -> void;
|
||||
auto runthreadtosave() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto load() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
auto serialize_all(serializer&) -> void;
|
||||
auto serialize_init() -> void;
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
struct Information {
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
uint serialize_size;
|
||||
private:
|
||||
bool _loaded = false;
|
||||
uint _serializeSize = 0;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
|
6
higan/fc/system/video.cpp
Normal file
6
higan/fc/system/video.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
auto System::configureVideoPalette() -> void {
|
||||
Emulator::video.setPalette();
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
}
|
@@ -1,105 +0,0 @@
|
||||
#include <fc/fc.hpp>
|
||||
#include <cmath>
|
||||
|
||||
#define VIDEO_CPP
|
||||
namespace Famicom {
|
||||
|
||||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[256 * 480];
|
||||
paletteStandard = new uint32[1 << 9];
|
||||
paletteEmulation = new uint32[1 << 9];
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
delete[] output;
|
||||
delete[] paletteStandard;
|
||||
delete[] paletteEmulation;
|
||||
}
|
||||
|
||||
auto Video::reset() -> void {
|
||||
memory::fill(output, 256 * 480);
|
||||
|
||||
for(auto color : range(1 << 9)) {
|
||||
paletteStandard[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 2.2);
|
||||
paletteEmulation[color] = generateColor(color, 2.0, 0.0, 1.0, 1.0, 1.8);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
if(settings.scanlineEmulation) {
|
||||
for(uint y = 0; y < 240; y++) {
|
||||
auto source = ppu.buffer + y * 256;
|
||||
auto targetLo = output + y * 512;
|
||||
auto targetHi = output + y * 512 + 256;
|
||||
for(uint x = 0; x < 256; x++) {
|
||||
auto color = palette[*source++];
|
||||
*targetLo++ = color;
|
||||
*targetHi++ = (255 << 24) | ((color & 0xfefefe) >> 1);
|
||||
}
|
||||
}
|
||||
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 480);
|
||||
} else {
|
||||
for(uint y = 0; y < 240; y++) {
|
||||
auto source = ppu.buffer + y * 256;
|
||||
auto target = output + y * 256;
|
||||
for(uint x = 0; x < 256; x++) {
|
||||
*target++ = palette[*source++];
|
||||
}
|
||||
}
|
||||
interface->videoRefresh(output, 256 * sizeof(uint32), 256, 240);
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::generateColor(
|
||||
uint n, double saturation, double hue,
|
||||
double contrast, double brightness, double gamma
|
||||
) -> uint32 {
|
||||
int color = (n & 0x0f), level = color < 0xe ? (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 * std::cos((3.141592653 / 6.0) * (p + hue));
|
||||
q += v * std::sin((3.141592653 / 6.0) * (p + hue));
|
||||
}
|
||||
|
||||
i *= saturation;
|
||||
q *= saturation;
|
||||
|
||||
auto gammaAdjust = [=](double f) { return f < 0.0 ? 0.0 : std::pow(f, 2.2 / gamma); };
|
||||
uint r = uclamp<16>(65535.0 * gammaAdjust(y + 0.946882 * i + 0.623557 * q));
|
||||
uint g = uclamp<16>(65535.0 * gammaAdjust(y + -0.274788 * i + -0.635691 * q));
|
||||
uint b = uclamp<16>(65535.0 * gammaAdjust(y + -1.108545 * i + 1.709007 * q));
|
||||
|
||||
return (255 << 24) | ((r >> 8) << 16) | ((g >> 8) << 8) | ((b >> 8) << 0);
|
||||
}
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
struct Video {
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
auto reset() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
uint32* output = nullptr;
|
||||
uint32* paletteStandard = nullptr;
|
||||
uint32* paletteEmulation = nullptr;
|
||||
|
||||
private:
|
||||
auto generateColor(uint, double, double, double, double, double) -> uint32;
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -1,16 +1,16 @@
|
||||
gb_objects := gb-interface gb-system gb-scheduler
|
||||
gb_objects += gb-memory gb-cartridge
|
||||
gb_objects += gb-cpu gb-ppu gb-apu
|
||||
gb_objects += gb-cheat gb-video
|
||||
objects += $(gb_objects)
|
||||
processors += lr35902
|
||||
|
||||
obj/gb-interface.o: $(gb)/interface/interface.cpp $(call rwildcard,$(gb)/interface/)
|
||||
obj/gb-system.o: $(gb)/system/system.cpp $(call rwildcard,$(gb)/system/)
|
||||
obj/gb-scheduler.o: $(gb)/scheduler/scheduler.cpp $(call rwildcard,$(gb)/scheduler/)
|
||||
obj/gb-cartridge.o: $(gb)/cartridge/cartridge.cpp $(call rwildcard,$(gb)/cartridge/)
|
||||
obj/gb-memory.o: $(gb)/memory/memory.cpp $(call rwildcard,$(gb)/memory/)
|
||||
obj/gb-cpu.o: $(gb)/cpu/cpu.cpp $(call rwildcard,$(gb)/cpu/)
|
||||
obj/gb-ppu.o: $(gb)/ppu/ppu.cpp $(call rwildcard,$(gb)/ppu/)
|
||||
obj/gb-apu.o: $(gb)/apu/apu.cpp $(call rwildcard,$(gb)/apu/)
|
||||
obj/gb-cheat.o: $(gb)/cheat/cheat.cpp $(call rwildcard,$(gb)/cheat/)
|
||||
obj/gb-video.o: $(gb)/video/video.cpp $(call rwildcard,$(gb)/video/)
|
||||
objects += gb-interface gb-system gb-scheduler
|
||||
objects += gb-memory gb-cartridge
|
||||
objects += gb-cpu gb-ppu gb-apu
|
||||
objects += gb-cheat
|
||||
|
||||
obj/gb-interface.o: gb/interface/interface.cpp $(call rwildcard,gb/interface/)
|
||||
obj/gb-system.o: gb/system/system.cpp $(call rwildcard,gb/system/)
|
||||
obj/gb-scheduler.o: gb/scheduler/scheduler.cpp $(call rwildcard,gb/scheduler/)
|
||||
obj/gb-cartridge.o: gb/cartridge/cartridge.cpp $(call rwildcard,gb/cartridge/)
|
||||
obj/gb-memory.o: gb/memory/memory.cpp $(call rwildcard,gb/memory/)
|
||||
obj/gb-cpu.o: gb/cpu/cpu.cpp $(call rwildcard,gb/cpu/)
|
||||
obj/gb-ppu.o: gb/ppu/ppu.cpp $(call rwildcard,gb/ppu/)
|
||||
obj/gb-apu.o: gb/apu/apu.cpp $(call rwildcard,gb/apu/)
|
||||
obj/gb-cheat.o: gb/cheat/cheat.cpp $(call rwildcard,gb/cheat/)
|
||||
|
@@ -10,52 +10,49 @@ namespace GameBoy {
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
auto APU::Main() -> void {
|
||||
apu.main();
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
square1.run();
|
||||
square2.run();
|
||||
wave.run();
|
||||
noise.run();
|
||||
sequencer.run();
|
||||
|
||||
square1.run();
|
||||
square2.run();
|
||||
wave.run();
|
||||
noise.run();
|
||||
sequencer.run();
|
||||
hipass(sequencer.center, sequencer.centerBias);
|
||||
hipass(sequencer.left, sequencer.leftBias);
|
||||
hipass(sequencer.right, sequencer.rightBias);
|
||||
|
||||
hipass(sequencer.center, sequencer.centerBias);
|
||||
hipass(sequencer.left, sequencer.leftBias);
|
||||
hipass(sequencer.right, sequencer.rightBias);
|
||||
|
||||
interface->audioSample(sequencer.left, sequencer.right);
|
||||
|
||||
if(cycle == 0) { //512hz
|
||||
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
|
||||
square1.clockLength();
|
||||
square2.clockLength();
|
||||
wave.clockLength();
|
||||
noise.clockLength();
|
||||
}
|
||||
if(phase == 2 || phase == 6) { //128hz
|
||||
square1.clockSweep();
|
||||
}
|
||||
if(phase == 7) { //64hz
|
||||
square1.clockEnvelope();
|
||||
square2.clockEnvelope();
|
||||
noise.clockEnvelope();
|
||||
}
|
||||
phase++;
|
||||
}
|
||||
cycle++;
|
||||
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||
co_switch(scheduler.active_thread = cpu.thread);
|
||||
}
|
||||
if(!system.sgb()) {
|
||||
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
|
||||
} else {
|
||||
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
|
||||
interface->audioSample(samples, 2);
|
||||
}
|
||||
|
||||
if(cycle == 0) { //512hz
|
||||
if(phase == 0 || phase == 2 || phase == 4 || phase == 6) { //256hz
|
||||
square1.clockLength();
|
||||
square2.clockLength();
|
||||
wave.clockLength();
|
||||
noise.clockLength();
|
||||
}
|
||||
if(phase == 2 || phase == 6) { //128hz
|
||||
square1.clockSweep();
|
||||
}
|
||||
if(phase == 7) { //64hz
|
||||
square1.clockEnvelope();
|
||||
square2.clockEnvelope();
|
||||
noise.clockEnvelope();
|
||||
}
|
||||
phase++;
|
||||
}
|
||||
cycle++;
|
||||
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
//filter to remove DC bias
|
||||
@@ -65,7 +62,8 @@ auto APU::hipass(int16& sample, int64& bias) -> void {
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
create(Main, 2 * 1024 * 1024);
|
||||
create(Enter, 2 * 1024 * 1024);
|
||||
if(!system.sgb()) stream = Emulator::audio.createStream(2, 2 * 1024 * 1024);
|
||||
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
|
||||
|
||||
square1.power();
|
||||
|
@@ -1,5 +1,7 @@
|
||||
struct APU : Thread, MMIO {
|
||||
static auto Main() -> void;
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto hipass(int16& sample, int64& bias) -> void;
|
||||
auto power() -> void;
|
||||
|
@@ -66,16 +66,16 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
if(addr == 0xff21) { //NR42
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff22) { //NR43
|
||||
frequency = data >> 4;
|
||||
narrow = data & 0x08;
|
||||
divisor = data & 0x07;
|
||||
frequency = data.bits(7,4);
|
||||
narrow = data.bit (3);
|
||||
divisor = data.bits(2,0);
|
||||
period = getPeriod();
|
||||
}
|
||||
|
||||
@@ -84,10 +84,9 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
counter = data.bit(6);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
lfsr = -1;
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
@@ -95,7 +94,7 @@ auto APU::Noise::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -68,26 +68,26 @@ auto APU::Sequencer::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff24) { //NR50
|
||||
leftEnable = (uint1)(data >> 7);
|
||||
leftVolume = (uint3)(data >> 4);
|
||||
rightEnable = (uint1)(data >> 3);
|
||||
rightVolume = (uint3)(data >> 0);
|
||||
leftEnable = data.bit (7);
|
||||
leftVolume = data.bits(6,4);
|
||||
rightEnable = data.bit (3);
|
||||
rightVolume = data.bits(2,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff25) { //NR51
|
||||
noise.leftEnable = data & 0x80;
|
||||
wave.leftEnable = data & 0x40;
|
||||
square2.leftEnable = data & 0x20;
|
||||
square1.leftEnable = data & 0x10;
|
||||
noise.rightEnable = data & 0x08;
|
||||
wave.rightEnable = data & 0x04;
|
||||
square2.rightEnable = data & 0x02;
|
||||
square1.rightEnable = data & 0x01;
|
||||
noise.leftEnable = data.bit(7);
|
||||
wave.leftEnable = data.bit(6);
|
||||
square2.leftEnable = data.bit(5);
|
||||
square1.leftEnable = data.bit(4);
|
||||
noise.rightEnable = data.bit(3);
|
||||
wave.rightEnable = data.bit(2);
|
||||
square2.rightEnable = data.bit(1);
|
||||
square1.rightEnable = data.bit(0);
|
||||
}
|
||||
|
||||
if(addr == 0xff26) { //NR52
|
||||
if(enable != (bool)(data & 0x80)) {
|
||||
enable = data & 0x80;
|
||||
if(enable != data.bit(7)) {
|
||||
enable = data.bit(7);
|
||||
|
||||
if(!enable) {
|
||||
//power(bool) resets length counters when true (eg for CGB only)
|
||||
|
@@ -86,38 +86,37 @@ auto APU::Square1::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff10) { //NR10
|
||||
if(sweepEnable && sweepNegate && !(data & 0x08)) enable = false;
|
||||
sweepFrequency = (data >> 4) & 7;
|
||||
sweepDirection = data & 0x08;
|
||||
sweepShift = data & 0x07;
|
||||
if(sweepEnable && sweepNegate && !data.bit(3)) enable = false;
|
||||
sweepFrequency = data.bits(6,4);
|
||||
sweepDirection = data.bit (3);
|
||||
sweepShift = data.bits(2,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff11) { //NR11
|
||||
duty = data >> 6;
|
||||
length = 64 - (data & 0x3f);
|
||||
duty = data.bits(7,6);
|
||||
length = 64 - data.bits(5,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff12) { //NR12
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff13) { //NR13
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff14) { //NR14
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
@@ -125,7 +124,7 @@ auto APU::Square1::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
|
||||
frequencyShadow = frequency;
|
||||
|
@@ -60,31 +60,30 @@ auto APU::Square2::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff16) { //NR21
|
||||
duty = data >> 6;
|
||||
length = 64 - (data & 0x3f);
|
||||
duty = data.bits(7,6);
|
||||
length = 64 - data.bits(5,0);
|
||||
}
|
||||
|
||||
if(addr == 0xff17) { //NR22
|
||||
envelopeVolume = data >> 4;
|
||||
envelopeDirection = data & 0x08;
|
||||
envelopeFrequency = data & 0x07;
|
||||
envelopeVolume = data.bits(7,4);
|
||||
envelopeDirection = data.bit (3);
|
||||
envelopeFrequency = data.bits(2,0);
|
||||
if(!dacEnable()) enable = false;
|
||||
}
|
||||
|
||||
if(addr == 0xff18) { //NR23
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff19) { //NR24
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
enable = dacEnable();
|
||||
period = 2 * (2048 - frequency);
|
||||
envelopePeriod = envelopeFrequency ? (uint)envelopeFrequency : 8;
|
||||
@@ -92,7 +91,7 @@ auto APU::Square2::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 64;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -59,7 +59,7 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
|
||||
|
||||
auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff1a) { //NR30
|
||||
dacEnable = data & 0x80;
|
||||
dacEnable = data.bit(7);
|
||||
if(!dacEnable) enable = false;
|
||||
}
|
||||
|
||||
@@ -68,23 +68,22 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
if(addr == 0xff1c) { //NR32
|
||||
volume = data >> 5;
|
||||
volume = data.bits(6,5);
|
||||
}
|
||||
|
||||
if(addr == 0xff1d) { //NR33
|
||||
frequency = (frequency & 0x0700) | data;
|
||||
frequency.bits(7,0) = data;
|
||||
}
|
||||
|
||||
if(addr == 0xff1e) { //NR34
|
||||
if((apu.phase & 1) && !counter && (data & 0x40)) {
|
||||
if(apu.phase.bit(0) && !counter && data.bit(6)) {
|
||||
if(length && --length == 0) enable = false;
|
||||
}
|
||||
|
||||
bool initialize = data & 0x80;
|
||||
counter = data & 0x40;
|
||||
frequency = ((data & 7) << 8) | (frequency & 0x00ff);
|
||||
counter = data.bit(6);
|
||||
frequency.bits(10,8) = data.bits(2,0);
|
||||
|
||||
if(initialize) {
|
||||
if(data.bit(7)) {
|
||||
if(!system.cgb() && patternHold) {
|
||||
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
||||
if((patternOffset >> 1) <= 3) {
|
||||
@@ -107,7 +106,7 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
if(!length) {
|
||||
length = 256;
|
||||
if((apu.phase & 1) && counter) length--;
|
||||
if(apu.phase.bit(0) && counter) length--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ namespace GameBoy {
|
||||
|
||||
#include "mbc0/mbc0.cpp"
|
||||
#include "mbc1/mbc1.cpp"
|
||||
#include "mbc1m/mbc1m.cpp"
|
||||
#include "mbc2/mbc2.cpp"
|
||||
#include "mbc3/mbc3.cpp"
|
||||
#include "mbc5/mbc5.cpp"
|
||||
@@ -13,15 +14,6 @@ namespace GameBoy {
|
||||
#include "serialization.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
Cartridge::Cartridge() {
|
||||
loaded = false;
|
||||
sha256 = "";
|
||||
}
|
||||
|
||||
Cartridge::~Cartridge() {
|
||||
unload();
|
||||
}
|
||||
|
||||
auto Cartridge::manifest() const -> string {
|
||||
return information.markup;
|
||||
}
|
||||
@@ -30,25 +22,9 @@ auto Cartridge::title() const -> string {
|
||||
return information.title;
|
||||
}
|
||||
|
||||
//intended for use with Super Game Boy for when no Game Boy cartridge is inserted
|
||||
auto Cartridge::load_empty(System::Revision revision) -> void {
|
||||
unload();
|
||||
romsize = 32768;
|
||||
romdata = allocate<uint8>(romsize, 0xff);
|
||||
ramsize = 0;
|
||||
mapper = &mbc0;
|
||||
sha256 = Hash::SHA256(romdata, romsize).digest();
|
||||
loaded = true;
|
||||
system.load(revision);
|
||||
}
|
||||
|
||||
auto Cartridge::load(System::Revision revision) -> void {
|
||||
unload();
|
||||
|
||||
system.revision = revision; //needed for ID::Manifest to return correct group ID
|
||||
if(revision != System::Revision::SuperGameBoy) {
|
||||
interface->loadRequest(ID::Manifest, "manifest.bml", true);
|
||||
}
|
||||
information.markup = "";
|
||||
interface->loadRequest(ID::Manifest, "manifest.bml", !system.sgb());
|
||||
|
||||
information.mapper = Mapper::Unknown;
|
||||
information.ram = false;
|
||||
@@ -65,6 +41,7 @@ auto Cartridge::load(System::Revision revision) -> void {
|
||||
auto mapperid = document["board/mapper"].text();
|
||||
if(mapperid == "none" ) information.mapper = Mapper::MBC0;
|
||||
if(mapperid == "MBC1" ) information.mapper = Mapper::MBC1;
|
||||
if(mapperid == "MBC1M") information.mapper = Mapper::MBC1M;
|
||||
if(mapperid == "MBC2" ) information.mapper = Mapper::MBC2;
|
||||
if(mapperid == "MBC3" ) information.mapper = Mapper::MBC3;
|
||||
if(mapperid == "MBC5" ) information.mapper = Mapper::MBC5;
|
||||
@@ -78,26 +55,24 @@ auto Cartridge::load(System::Revision revision) -> void {
|
||||
auto rom = document["board/rom"];
|
||||
auto ram = document["board/ram"];
|
||||
|
||||
romsize = rom["size"].natural();
|
||||
romsize = max(32768u, rom["size"].natural());
|
||||
romdata = allocate<uint8>(romsize, 0xff);
|
||||
|
||||
ramsize = ram["size"].natural();
|
||||
ramdata = allocate<uint8>(ramsize, 0xff);
|
||||
|
||||
//Super Game Boy core loads memory from Super Famicom core
|
||||
if(revision != System::Revision::SuperGameBoy) {
|
||||
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, true);
|
||||
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
|
||||
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
|
||||
}
|
||||
if(auto name = rom["name"].text()) interface->loadRequest(ID::ROM, name, !system.sgb());
|
||||
if(auto name = ram["name"].text()) interface->loadRequest(ID::RAM, name, false);
|
||||
if(auto name = ram["name"].text()) memory.append({ID::RAM, name});
|
||||
|
||||
information.romsize = rom["size"].natural();
|
||||
information.ramsize = ram["size"].natural();
|
||||
information.romsize = romsize;
|
||||
information.ramsize = ramsize;
|
||||
information.battery = (bool)ram["name"];
|
||||
|
||||
switch(information.mapper) { default:
|
||||
case Mapper::MBC0: mapper = &mbc0; break;
|
||||
case Mapper::MBC1: mapper = &mbc1; break;
|
||||
case Mapper::MBC1M: mapper = &mbc1m; break;
|
||||
case Mapper::MBC2: mapper = &mbc2; break;
|
||||
case Mapper::MBC3: mapper = &mbc3; break;
|
||||
case Mapper::MBC5: mapper = &mbc5; break;
|
||||
@@ -107,14 +82,11 @@ auto Cartridge::load(System::Revision revision) -> void {
|
||||
}
|
||||
|
||||
sha256 = Hash::SHA256(romdata, romsize).digest();
|
||||
loaded = true;
|
||||
system.load(revision);
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
if(romdata) { delete[] romdata; romdata = nullptr; romsize = 0; }
|
||||
if(ramdata) { delete[] ramdata; ramdata = nullptr; ramsize = 0; }
|
||||
loaded = false;
|
||||
}
|
||||
|
||||
auto Cartridge::rom_read(uint addr) -> uint8 {
|
||||
@@ -144,7 +116,7 @@ auto Cartridge::mmio_read(uint16 addr) -> uint8 {
|
||||
|
||||
if(bootrom_enable) {
|
||||
const uint8* data = nullptr;
|
||||
switch(system.revision) { default:
|
||||
switch(system.revision()) { default:
|
||||
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
|
||||
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
|
||||
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
|
||||
@@ -170,6 +142,7 @@ auto Cartridge::power() -> void {
|
||||
|
||||
mbc0.power();
|
||||
mbc1.power();
|
||||
mbc1m.power();
|
||||
mbc2.power();
|
||||
mbc3.power();
|
||||
mbc5.power();
|
||||
|
@@ -1,8 +1,4 @@
|
||||
struct Cartridge : MMIO, property<Cartridge> {
|
||||
Cartridge();
|
||||
~Cartridge();
|
||||
|
||||
auto load_empty(System::Revision revision) -> void;
|
||||
auto load(System::Revision revision) -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
@@ -20,6 +16,7 @@ struct Cartridge : MMIO, property<Cartridge> {
|
||||
|
||||
#include "mbc0/mbc0.hpp"
|
||||
#include "mbc1/mbc1.hpp"
|
||||
#include "mbc1m/mbc1m.hpp"
|
||||
#include "mbc2/mbc2.hpp"
|
||||
#include "mbc3/mbc3.hpp"
|
||||
#include "mbc5/mbc5.hpp"
|
||||
@@ -30,6 +27,7 @@ struct Cartridge : MMIO, property<Cartridge> {
|
||||
enum Mapper : uint {
|
||||
MBC0,
|
||||
MBC1,
|
||||
MBC1M,
|
||||
MBC2,
|
||||
MBC3,
|
||||
MBC5,
|
||||
@@ -62,7 +60,6 @@ struct Cartridge : MMIO, property<Cartridge> {
|
||||
};
|
||||
vector<Memory> memory;
|
||||
|
||||
readonly<bool> loaded;
|
||||
readonly<string> sha256;
|
||||
|
||||
uint8* romdata = nullptr;
|
||||
|
@@ -9,7 +9,7 @@ auto Cartridge::HuC3::mmio_read(uint16 addr) -> uint8 {
|
||||
|
||||
if((addr & 0xe000) == 0xa000) { //$a000-bfff
|
||||
if(ram_enable) return cartridge.ram_read((ram_select << 13) | (addr & 0x1fff));
|
||||
return 0xff;
|
||||
return 0x01; //does not return open collection
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
|
40
higan/gb/cartridge/mbc1m/mbc1m.cpp
Normal file
40
higan/gb/cartridge/mbc1m/mbc1m.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
auto Cartridge::MBC1M::mmio_read(uint16 addr) -> uint8 {
|
||||
if((addr & 0xc000) == 0x0000) { //$0000-3fff
|
||||
if(!modeSelect) return cartridge.rom_read(addr & 0x3fff);
|
||||
return cartridge.rom_read((romHi << 4) * 0x4000 + (addr & 0x3fff));
|
||||
}
|
||||
|
||||
if((addr & 0xc000) == 0x4000) { //$4000-7fff
|
||||
return cartridge.rom_read((romHi << 4 | romLo) * 0x4000 + (addr & 0x3fff));
|
||||
}
|
||||
|
||||
if((addr & 0xe000) == 0xa000) { //$a000-bfff
|
||||
return cartridge.ram_read(addr & 0x1fff);
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto Cartridge::MBC1M::mmio_write(uint16 addr, uint8 data) -> void {
|
||||
if((addr & 0xe000) == 0x2000) { //$2000-3fff
|
||||
romLo = data.bits(0,3);
|
||||
}
|
||||
|
||||
if((addr & 0xe000) == 0x4000) { //$4000-5fff
|
||||
romHi = data.bits(0,1);
|
||||
}
|
||||
|
||||
if((addr & 0xe000) == 0x6000) { //$6000-7fff
|
||||
modeSelect = data.bit(0);
|
||||
}
|
||||
|
||||
if((addr & 0xe000) == 0xa000) { //$a000-bfff
|
||||
cartridge.ram_write(addr & 0x1fff, data);
|
||||
}
|
||||
}
|
||||
|
||||
auto Cartridge::MBC1M::power() -> void {
|
||||
romHi = 0;
|
||||
romLo = 1;
|
||||
modeSelect = 0;
|
||||
}
|
9
higan/gb/cartridge/mbc1m/mbc1m.hpp
Normal file
9
higan/gb/cartridge/mbc1m/mbc1m.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
struct MBC1M : MMIO {
|
||||
auto mmio_read(uint16 addr) -> uint8;
|
||||
auto mmio_write(uint16 addr, uint8 data) -> void;
|
||||
auto power() -> void;
|
||||
|
||||
uint4 romLo;
|
||||
uint2 romHi;
|
||||
uint1 modeSelect;
|
||||
} mbc1m;
|
@@ -7,6 +7,10 @@ auto Cartridge::serialize(serializer& s) -> void {
|
||||
s.integer(mbc1.ram_select);
|
||||
s.integer(mbc1.mode_select);
|
||||
|
||||
s.integer(mbc1m.romLo);
|
||||
s.integer(mbc1m.romHi);
|
||||
s.integer(mbc1m.modeSelect);
|
||||
|
||||
s.integer(mbc2.ram_enable);
|
||||
s.integer(mbc2.rom_select);
|
||||
|
||||
|
@@ -8,23 +8,16 @@ namespace GameBoy {
|
||||
#include "serialization.cpp"
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Main() -> void {
|
||||
cpu.main();
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
interrupt_test();
|
||||
exec();
|
||||
}
|
||||
interrupt_test();
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::interrupt_raise(CPU::Interrupt id) -> void {
|
||||
auto CPU::raise(CPU::Interrupt id) -> void {
|
||||
if(id == Interrupt::Vblank) {
|
||||
status.interrupt_request_vblank = 1;
|
||||
if(status.interrupt_enable_vblank) r.halt = false;
|
||||
@@ -52,42 +45,32 @@ auto CPU::interrupt_raise(CPU::Interrupt id) -> void {
|
||||
}
|
||||
|
||||
auto CPU::interrupt_test() -> void {
|
||||
if(r.ime) {
|
||||
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
|
||||
status.interrupt_request_vblank = 0;
|
||||
return interrupt_exec(0x0040);
|
||||
}
|
||||
if(!r.ime) return;
|
||||
|
||||
if(status.interrupt_request_stat && status.interrupt_enable_stat) {
|
||||
status.interrupt_request_stat = 0;
|
||||
return interrupt_exec(0x0048);
|
||||
}
|
||||
|
||||
if(status.interrupt_request_timer && status.interrupt_enable_timer) {
|
||||
status.interrupt_request_timer = 0;
|
||||
return interrupt_exec(0x0050);
|
||||
}
|
||||
|
||||
if(status.interrupt_request_serial && status.interrupt_enable_serial) {
|
||||
status.interrupt_request_serial = 0;
|
||||
return interrupt_exec(0x0058);
|
||||
}
|
||||
|
||||
if(status.interrupt_request_joypad && status.interrupt_enable_joypad) {
|
||||
status.interrupt_request_joypad = 0;
|
||||
return interrupt_exec(0x0060);
|
||||
}
|
||||
if(status.interrupt_request_vblank && status.interrupt_enable_vblank) {
|
||||
status.interrupt_request_vblank = 0;
|
||||
return interrupt(0x0040);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::interrupt_exec(uint16 pc) -> void {
|
||||
op_io();
|
||||
op_io();
|
||||
op_io();
|
||||
r.ime = 0;
|
||||
op_write(--r[SP], r[PC] >> 8);
|
||||
op_write(--r[SP], r[PC] >> 0);
|
||||
r[PC] = pc;
|
||||
if(status.interrupt_request_stat && status.interrupt_enable_stat) {
|
||||
status.interrupt_request_stat = 0;
|
||||
return interrupt(0x0048);
|
||||
}
|
||||
|
||||
if(status.interrupt_request_timer && status.interrupt_enable_timer) {
|
||||
status.interrupt_request_timer = 0;
|
||||
return interrupt(0x0050);
|
||||
}
|
||||
|
||||
if(status.interrupt_request_serial && status.interrupt_enable_serial) {
|
||||
status.interrupt_request_serial = 0;
|
||||
return interrupt(0x0058);
|
||||
}
|
||||
|
||||
if(status.interrupt_request_joypad && status.interrupt_enable_joypad) {
|
||||
status.interrupt_request_joypad = 0;
|
||||
return interrupt(0x0060);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::stop() -> bool {
|
||||
@@ -102,7 +85,7 @@ auto CPU::stop() -> bool {
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
create(Main, 4 * 1024 * 1024);
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
LR35902::power();
|
||||
|
||||
for(uint n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
|
||||
|
@@ -1,11 +1,10 @@
|
||||
struct CPU : Processor::LR35902, Thread, MMIO {
|
||||
enum class Interrupt : uint { Vblank, Stat, Timer, Serial, Joypad };
|
||||
|
||||
static auto Main() -> void;
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto interrupt_raise(Interrupt id) -> void;
|
||||
auto raise(Interrupt id) -> void;
|
||||
auto interrupt_test() -> void;
|
||||
auto interrupt_exec(uint16 pc) -> void;
|
||||
auto stop() -> bool;
|
||||
auto power() -> void;
|
||||
|
||||
@@ -18,9 +17,9 @@ struct CPU : Processor::LR35902, Thread, MMIO {
|
||||
auto mmio_write(uint16 addr, uint8 data) -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto op_io() -> void;
|
||||
auto op_read(uint16 addr) -> uint8;
|
||||
auto op_write(uint16 addr, uint8 data) -> void;
|
||||
auto io() -> void override;
|
||||
auto read(uint16 addr) -> uint8 override;
|
||||
auto write(uint16 addr, uint8 data) -> void override;
|
||||
auto cycle_edge() -> void;
|
||||
auto dma_read(uint16 addr) -> uint8;
|
||||
auto dma_write(uint16 addr, uint8 data) -> void;
|
||||
|
@@ -1,15 +1,15 @@
|
||||
auto CPU::op_io() -> void {
|
||||
auto CPU::io() -> void {
|
||||
cycle_edge();
|
||||
add_clocks(4);
|
||||
}
|
||||
|
||||
auto CPU::op_read(uint16 addr) -> uint8 {
|
||||
auto CPU::read(uint16 addr) -> uint8 {
|
||||
cycle_edge();
|
||||
add_clocks(4);
|
||||
return bus.read(addr);
|
||||
}
|
||||
|
||||
auto CPU::op_write(uint16 addr, uint8 data) -> void {
|
||||
auto CPU::write(uint16 addr, uint8 data) -> void {
|
||||
cycle_edge();
|
||||
add_clocks(4);
|
||||
bus.write(addr, data);
|
||||
|
@@ -18,7 +18,7 @@ auto CPU::mmio_joyp_poll() -> void {
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Left) << 1;
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Right) << 0;
|
||||
|
||||
if(system.revision != System::Revision::SuperGameBoy) {
|
||||
if(system.revision() != System::Revision::SuperGameBoy) {
|
||||
//D-pad pivot makes it impossible to press opposing directions at the same time
|
||||
//however, Super Game Boy BIOS is able to set these bits together
|
||||
if(dpad & 4) dpad &= ~8; //disallow up+down
|
||||
@@ -29,7 +29,7 @@ auto CPU::mmio_joyp_poll() -> void {
|
||||
if(status.p15 == 1 && status.p14 == 1) status.joyp -= status.mlt_req;
|
||||
if(status.p15 == 0) status.joyp &= button ^ 0x0f;
|
||||
if(status.p14 == 0) status.joyp &= dpad ^ 0x0f;
|
||||
if(status.joyp != 0x0f) interrupt_raise(Interrupt::Joypad);
|
||||
if(status.joyp != 0x0f) raise(Interrupt::Joypad);
|
||||
}
|
||||
|
||||
auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||
@@ -38,17 +38,19 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||
|
||||
if(addr == 0xff00) { //JOYP
|
||||
mmio_joyp_poll();
|
||||
return (status.p15 << 5)
|
||||
return 0xc0
|
||||
| (status.p15 << 5)
|
||||
| (status.p14 << 4)
|
||||
| (status.joyp << 0);
|
||||
}
|
||||
|
||||
if(addr == 0xff01) { //SB
|
||||
return 0xff;
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
if(addr == 0xff02) { //SC
|
||||
return (status.serial_transfer << 7)
|
||||
| 0x7e
|
||||
| (status.serial_clock << 0);
|
||||
}
|
||||
|
||||
@@ -65,12 +67,14 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||
}
|
||||
|
||||
if(addr == 0xff07) { //TAC
|
||||
return (status.timer_enable << 2)
|
||||
return 0xf8
|
||||
| (status.timer_enable << 2)
|
||||
| (status.timer_clock << 0);
|
||||
}
|
||||
|
||||
if(addr == 0xff0f) { //IF
|
||||
return (status.interrupt_request_joypad << 4)
|
||||
return 0xe0
|
||||
| (status.interrupt_request_joypad << 4)
|
||||
| (status.interrupt_request_serial << 3)
|
||||
| (status.interrupt_request_timer << 2)
|
||||
| (status.interrupt_request_stat << 1)
|
||||
@@ -123,7 +127,8 @@ auto CPU::mmio_read(uint16 addr) -> uint8 {
|
||||
}
|
||||
|
||||
if(addr == 0xffff) { //IE
|
||||
return (status.interrupt_enable_joypad << 4)
|
||||
return 0xe0
|
||||
| (status.interrupt_enable_joypad << 4)
|
||||
| (status.interrupt_enable_serial << 3)
|
||||
| (status.interrupt_enable_timer << 2)
|
||||
| (status.interrupt_enable_stat << 1)
|
||||
|
@@ -3,9 +3,7 @@
|
||||
// 154 scanlines/frame
|
||||
|
||||
auto CPU::add_clocks(uint clocks) -> void {
|
||||
if(system.sgb()) system.clocks_executed += clocks;
|
||||
|
||||
while(clocks--) {
|
||||
for(auto n : range(clocks)) {
|
||||
if(++status.clock == 0) {
|
||||
cartridge.mbc3.second();
|
||||
}
|
||||
@@ -19,20 +17,23 @@ auto CPU::add_clocks(uint clocks) -> void {
|
||||
if((status.div & 1023) == 0) timer_4096hz();
|
||||
|
||||
ppu.clock -= ppu.frequency;
|
||||
if(ppu.clock < 0) co_switch(scheduler.active_thread = ppu.thread);
|
||||
if(ppu.clock < 0) co_switch(ppu.thread);
|
||||
|
||||
apu.clock -= apu.frequency;
|
||||
if(apu.clock < 0) co_switch(scheduler.active_thread = apu.thread);
|
||||
if(apu.clock < 0) co_switch(apu.thread);
|
||||
}
|
||||
|
||||
if(system.sgb()) scheduler.exit(Scheduler::ExitReason::StepEvent);
|
||||
if(system.sgb()) {
|
||||
system._clocksExecuted += clocks;
|
||||
scheduler.exit(Scheduler::Event::Step);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::timer_262144hz() -> void {
|
||||
if(status.timer_enable && status.timer_clock == 1) {
|
||||
if(++status.tima == 0) {
|
||||
status.tima = status.tma;
|
||||
interrupt_raise(Interrupt::Timer);
|
||||
raise(Interrupt::Timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +42,7 @@ auto CPU::timer_65536hz() -> void {
|
||||
if(status.timer_enable && status.timer_clock == 2) {
|
||||
if(++status.tima == 0) {
|
||||
status.tima = status.tma;
|
||||
interrupt_raise(Interrupt::Timer);
|
||||
raise(Interrupt::Timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +51,7 @@ auto CPU::timer_16384hz() -> void {
|
||||
if(status.timer_enable && status.timer_clock == 3) {
|
||||
if(++status.tima == 0) {
|
||||
status.tima = status.tma;
|
||||
interrupt_raise(Interrupt::Timer);
|
||||
raise(Interrupt::Timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +60,7 @@ auto CPU::timer_8192hz() -> void {
|
||||
if(status.serial_transfer && status.serial_clock) {
|
||||
if(--status.serial_bits == 0) {
|
||||
status.serial_transfer = 0;
|
||||
interrupt_raise(Interrupt::Serial);
|
||||
raise(Interrupt::Serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +69,7 @@ auto CPU::timer_4096hz() -> void {
|
||||
if(status.timer_enable && status.timer_clock == 0) {
|
||||
if(++status.tima == 0) {
|
||||
status.tima = status.tma;
|
||||
interrupt_raise(Interrupt::Timer);
|
||||
raise(Interrupt::Timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
//license: GPLv3
|
||||
//started: 2010-12-27
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <processor/lr35902/lr35902.hpp>
|
||||
|
||||
namespace GameBoy {
|
||||
namespace Info {
|
||||
static const string Name = "bgb";
|
||||
static const uint SerializerVersion = 4;
|
||||
static const uint SerializerVersion = 5;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bgb - Game Boy, Super Game Boy, and Game Boy Color emulator
|
||||
author: byuu
|
||||
license: GPLv3
|
||||
project started: 2010-12-27
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
namespace GameBoy {
|
||||
@@ -27,7 +22,7 @@ namespace GameBoy {
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
||||
thread = co_create(65'536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
}
|
||||
@@ -50,7 +45,6 @@ namespace GameBoy {
|
||||
#include <gb/ppu/ppu.hpp>
|
||||
#include <gb/apu/apu.hpp>
|
||||
#include <gb/cheat/cheat.hpp>
|
||||
#include <gb/video/video.hpp>
|
||||
}
|
||||
|
||||
#include <gb/interface/interface.hpp>
|
||||
|
@@ -9,12 +9,14 @@ Interface::Interface() {
|
||||
interface = this;
|
||||
hook = nullptr;
|
||||
|
||||
information.name = "Game Boy";
|
||||
information.width = 160;
|
||||
information.height = 144;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy";
|
||||
information.width = 160;
|
||||
information.height = 144;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
@@ -23,19 +25,18 @@ Interface::Interface() {
|
||||
|
||||
{
|
||||
Device device{0, ID::Device, "Controller"};
|
||||
device.input.append({0, 0, "Up" });
|
||||
device.input.append({1, 0, "Down" });
|
||||
device.input.append({2, 0, "Left" });
|
||||
device.input.append({3, 0, "Right" });
|
||||
device.input.append({4, 0, "B" });
|
||||
device.input.append({5, 0, "A" });
|
||||
device.input.append({6, 0, "Select"});
|
||||
device.input.append({7, 0, "Start" });
|
||||
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
this->device.append(device);
|
||||
device.inputs.append({0, 0, "Up" });
|
||||
device.inputs.append({1, 0, "Down" });
|
||||
device.inputs.append({2, 0, "Left" });
|
||||
device.inputs.append({3, 0, "Right" });
|
||||
device.inputs.append({4, 0, "B" });
|
||||
device.inputs.append({5, 0, "A" });
|
||||
device.inputs.append({6, 0, "Select"});
|
||||
device.inputs.append({7, 0, "Start" });
|
||||
devices.append(device);
|
||||
}
|
||||
|
||||
port.append({0, "Device", {device[0]}});
|
||||
ports.append({0, "Device", {devices[0]}});
|
||||
}
|
||||
|
||||
auto Interface::manifest() -> string {
|
||||
@@ -50,12 +51,73 @@ auto Interface::videoFrequency() -> double {
|
||||
return 4194304.0 / (154.0 * 456.0);
|
||||
}
|
||||
|
||||
auto Interface::videoColors() -> uint32 {
|
||||
return !system.cgb() ? 1 << 2 : 1 << 15;
|
||||
}
|
||||
|
||||
auto Interface::videoColor(uint32 color) -> uint64 {
|
||||
if(!system.cgb()) {
|
||||
if(!settings.colorEmulation) {
|
||||
uint64 L = image::normalize(3 - color, 2, 16);
|
||||
return L << 32 | L << 16 | L << 0;
|
||||
} else {
|
||||
#define DMG_PALETTE_GREEN
|
||||
//#define DMG_PALETTE_YELLOW
|
||||
//#define DMG_PALETTE_WHITE
|
||||
|
||||
const uint16 monochrome[4][3] = {
|
||||
#if defined(DMG_PALETTE_GREEN)
|
||||
{0xaeae, 0xd9d9, 0x2727},
|
||||
{0x5858, 0xa0a0, 0x2828},
|
||||
{0x2020, 0x6262, 0x2929},
|
||||
{0x1a1a, 0x4545, 0x2a2a},
|
||||
#elif defined(DMG_PALETTE_YELLOW)
|
||||
{0xffff, 0xf7f7, 0x7b7b},
|
||||
{0xb5b5, 0xaeae, 0x4a4a},
|
||||
{0x6b6b, 0x6969, 0x3131},
|
||||
{0x2121, 0x2020, 0x1010},
|
||||
#elif defined(DMG_PALETTE_WHITE)
|
||||
{0xffff, 0xffff, 0xffff},
|
||||
{0xaaaa, 0xaaaa, 0xaaaa},
|
||||
{0x5555, 0x5555, 0x5555},
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
#endif
|
||||
};
|
||||
|
||||
uint64 R = monochrome[color][0];
|
||||
uint64 G = monochrome[color][1];
|
||||
uint64 B = monochrome[color][2];
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
}
|
||||
} else {
|
||||
uint r = color.bits( 0, 4);
|
||||
uint g = color.bits( 5, 9);
|
||||
uint b = color.bits(10,14);
|
||||
|
||||
uint64_t R = image::normalize(r, 5, 16);
|
||||
uint64_t G = image::normalize(g, 5, 16);
|
||||
uint64_t B = image::normalize(b, 5, 16);
|
||||
|
||||
if(settings.colorEmulation) {
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(960, R), 10, 16);
|
||||
G = image::normalize(min(960, G), 10, 16);
|
||||
B = image::normalize(min(960, B), 10, 16);
|
||||
}
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::audioFrequency() -> double {
|
||||
return 4194304.0 / 2.0;
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return cartridge.loaded();
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::sha256() -> string {
|
||||
@@ -72,7 +134,7 @@ auto Interface::group(uint id) -> uint {
|
||||
case ID::Manifest:
|
||||
case ID::ROM:
|
||||
case ID::RAM:
|
||||
switch(system.revision) {
|
||||
switch(system.revision()) {
|
||||
case System::Revision::GameBoy: return ID::GameBoy;
|
||||
case System::Revision::SuperGameBoy: return ID::SuperGameBoy;
|
||||
case System::Revision::GameBoyColor: return ID::GameBoyColor;
|
||||
@@ -83,9 +145,9 @@ auto Interface::group(uint id) -> uint {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
if(id == ID::GameBoy) cartridge.load(System::Revision::GameBoy);
|
||||
if(id == ID::SuperGameBoy) cartridge.load(System::Revision::SuperGameBoy);
|
||||
if(id == ID::GameBoyColor) cartridge.load(System::Revision::GameBoyColor);
|
||||
if(id == ID::GameBoy) system.load(System::Revision::GameBoy);
|
||||
if(id == ID::SuperGameBoy) system.load(System::Revision::SuperGameBoy);
|
||||
if(id == ID::GameBoyColor) system.load(System::Revision::GameBoyColor);
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -100,15 +162,15 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::GameBoyBootROM) {
|
||||
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
|
||||
stream.read((uint8_t*)system.bootROM.dmg, min( 256u, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::SuperGameBoyBootROM) {
|
||||
stream.read(system.bootROM.sgb, min( 256u, stream.size()));
|
||||
stream.read((uint8_t*)system.bootROM.sgb, min( 256u, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::GameBoyColorBootROM) {
|
||||
stream.read(system.bootROM.cgb, min(2048u, stream.size()));
|
||||
stream.read((uint8_t*)system.bootROM.cgb, min(2048u, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::Manifest) {
|
||||
@@ -116,23 +178,23 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::ROM) {
|
||||
stream.read(cartridge.romdata, min(cartridge.romsize, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.romdata, min(cartridge.romsize, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::RAM) {
|
||||
stream.read(cartridge.ramdata, min(stream.size(), cartridge.ramsize));
|
||||
stream.read((uint8_t*)cartridge.ramdata, min(stream.size(), cartridge.ramsize));
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::save(uint id, const stream& stream) -> void {
|
||||
if(id == ID::RAM) {
|
||||
stream.write(cartridge.ramdata, cartridge.ramsize);
|
||||
stream.write((uint8_t*)cartridge.ramdata, cartridge.ramsize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
cartridge.unload();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
@@ -148,7 +210,7 @@ auto Interface::run() -> void {
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runtosave();
|
||||
system.runToSave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
@@ -193,8 +255,18 @@ auto Interface::get(const string& name) -> any {
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Blur Emulation" && value.is<bool>()) return settings.blurEmulation = value.get<bool>(), true;
|
||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
||||
if(name == "Blur Emulation" && value.is<bool>()) {
|
||||
settings.blurEmulation = value.get<bool>();
|
||||
system.configureVideoEffects();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -30,6 +30,8 @@ struct Interface : Emulator::Interface {
|
||||
auto manifest() -> string;
|
||||
auto title() -> string;
|
||||
auto videoFrequency() -> double;
|
||||
auto videoColors() -> uint32;
|
||||
auto videoColor(uint32 color) -> uint64;
|
||||
auto audioFrequency() -> double;
|
||||
|
||||
auto loaded() -> bool;
|
||||
@@ -67,7 +69,7 @@ struct Interface : Emulator::Interface {
|
||||
auto joypWrite(bool p15, bool p14) -> void;
|
||||
|
||||
private:
|
||||
vector<Device> device;
|
||||
vector<Device> devices;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
|
@@ -16,13 +16,13 @@ auto Memory::operator[](uint addr) -> uint8& {
|
||||
auto Memory::allocate(uint size_) -> void {
|
||||
free();
|
||||
size = size_;
|
||||
data = new uint8_t[size]();
|
||||
data = new uint8[size];
|
||||
}
|
||||
|
||||
auto Memory::copy(const uint8_t* data_, unsigned size_) -> void {
|
||||
auto Memory::copy(const uint8* data_, unsigned size_) -> void {
|
||||
free();
|
||||
size = size_;
|
||||
data = new uint8_t[size];
|
||||
data = new uint8[size];
|
||||
memcpy(data, data_, size);
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,7 @@ auto PPU::cgb_read_tile(bool select, uint x, uint y, uint& attr, uint& data) ->
|
||||
|
||||
auto PPU::cgb_scanline() -> void {
|
||||
px = 0;
|
||||
if(!enabled()) return;
|
||||
|
||||
const uint Height = (status.ob_size == 0 ? 8 : 16);
|
||||
sprites = 0;
|
||||
@@ -68,7 +69,7 @@ auto PPU::cgb_run() -> void {
|
||||
ob.priority = 0;
|
||||
|
||||
uint color = 0x7fff;
|
||||
if(status.display_enable) {
|
||||
if(enabled()) {
|
||||
cgb_run_bg();
|
||||
if(status.window_display_enable) cgb_run_window();
|
||||
if(status.ob_enable) cgb_run_ob();
|
||||
|
@@ -19,6 +19,7 @@ auto PPU::dmg_read_tile(bool select, uint x, uint y, uint& data) -> void {
|
||||
|
||||
auto PPU::dmg_scanline() -> void {
|
||||
px = 0;
|
||||
if(!enabled()) return;
|
||||
|
||||
const uint Height = (status.ob_size == 0 ? 8 : 16);
|
||||
sprites = 0;
|
||||
@@ -59,7 +60,7 @@ auto PPU::dmg_run() -> void {
|
||||
ob.palette = 0;
|
||||
|
||||
uint color = 0;
|
||||
if(status.display_enable) {
|
||||
if(enabled()) {
|
||||
if(status.bg_enable) dmg_run_bg();
|
||||
if(status.window_display_enable) dmg_run_window();
|
||||
if(status.ob_enable) dmg_run_ob();
|
||||
|
@@ -24,18 +24,12 @@ auto PPU::mmio_read(uint16 addr) -> uint8 {
|
||||
}
|
||||
|
||||
if(addr == 0xff41) { //STAT
|
||||
uint mode;
|
||||
if(status.ly >= 144) mode = 1; //Vblank
|
||||
else if(status.lx < 80) mode = 2; //OAM
|
||||
else if(status.lx < 252) mode = 3; //LCD
|
||||
else mode = 0; //Hblank
|
||||
|
||||
return (status.interrupt_lyc << 6)
|
||||
| (status.interrupt_oam << 5)
|
||||
| (status.interrupt_vblank << 4)
|
||||
| (status.interrupt_hblank << 3)
|
||||
| ((status.ly == status.lyc) << 2)
|
||||
| (mode << 0);
|
||||
| (status.mode << 0);
|
||||
}
|
||||
|
||||
if(addr == 0xff42) { //SCY
|
||||
@@ -125,7 +119,7 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||
|
||||
//restart cothread to begin new frame
|
||||
auto clock = this->clock;
|
||||
create(Main, 4 * 1024 * 1024);
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
this->clock = clock;
|
||||
}
|
||||
|
||||
@@ -145,6 +139,13 @@ auto PPU::mmio_write(uint16 addr, uint8 data) -> void {
|
||||
status.interrupt_oam = data & 0x20;
|
||||
status.interrupt_vblank = data & 0x10;
|
||||
status.interrupt_hblank = data & 0x08;
|
||||
|
||||
//hardware bug: writes to STAT on DMG,SGB during vblank triggers STAT IRQ
|
||||
//note: this behavior isn't entirely correct; more research is needed ...
|
||||
if(!system.cgb() && status.mode == 1) {
|
||||
cpu.raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -1,77 +1,82 @@
|
||||
#include <gb/gb.hpp>
|
||||
|
||||
//LY = 0-153
|
||||
//Raster = 0-143
|
||||
//Vblank = 144-153
|
||||
|
||||
//LX = 0-455
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
PPU ppu;
|
||||
#include "mmio.cpp"
|
||||
#include "dmg.cpp"
|
||||
#include "cgb.cpp"
|
||||
#include "serialization.cpp"
|
||||
PPU ppu;
|
||||
|
||||
auto PPU::Main() -> void {
|
||||
ppu.main();
|
||||
auto PPU::enabled() const -> bool { return status.display_enable; }
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
status.lx = 0;
|
||||
interface->lcdScanline(); //Super Game Boy notification
|
||||
|
||||
if(status.ly <= 143) {
|
||||
mode(2);
|
||||
scanline();
|
||||
wait(92);
|
||||
|
||||
mode(3);
|
||||
for(auto n : range(160)) {
|
||||
run();
|
||||
wait(1);
|
||||
}
|
||||
|
||||
status.lx = 0;
|
||||
interface->lcdScanline(); //Super Game Boy notification
|
||||
mode(0);
|
||||
if(enabled()) cpu.hblank();
|
||||
wait(204);
|
||||
} else {
|
||||
mode(1);
|
||||
wait(456);
|
||||
}
|
||||
|
||||
if(status.display_enable) {
|
||||
//LYC of zero triggers on LY==153
|
||||
if((status.lyc && status.ly == status.lyc) || (!status.lyc && status.ly == 153)) {
|
||||
if(status.interrupt_lyc) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
status.ly++;
|
||||
|
||||
if(status.ly <= 143) {
|
||||
scanline();
|
||||
if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
if(status.ly == 144) {
|
||||
if(enabled()) cpu.raise(CPU::Interrupt::Vblank);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
if(status.ly == 144) {
|
||||
if(status.interrupt_vblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
else if(status.interrupt_oam) cpu.interrupt_raise(CPU::Interrupt::Stat); //hardware quirk
|
||||
cpu.interrupt_raise(CPU::Interrupt::Vblank);
|
||||
}
|
||||
}
|
||||
|
||||
add_clocks(92);
|
||||
|
||||
if(status.ly <= 143) {
|
||||
for(auto n : range(160)) {
|
||||
if(status.display_enable) run();
|
||||
add_clocks(1);
|
||||
}
|
||||
|
||||
if(status.display_enable) {
|
||||
if(status.interrupt_hblank) cpu.interrupt_raise(CPU::Interrupt::Stat);
|
||||
cpu.hblank();
|
||||
}
|
||||
} else {
|
||||
add_clocks(160);
|
||||
}
|
||||
|
||||
add_clocks(204);
|
||||
|
||||
if(++status.ly == 154) {
|
||||
status.ly = 0;
|
||||
scheduler.exit(Scheduler::ExitReason::FrameEvent);
|
||||
}
|
||||
if(status.ly == 154) {
|
||||
status.ly = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::add_clocks(uint clocks) -> void {
|
||||
auto PPU::mode(uint mode) -> void {
|
||||
status.mode = mode;
|
||||
}
|
||||
|
||||
auto PPU::stat() -> void {
|
||||
bool irq = status.irq;
|
||||
|
||||
status.irq = status.interrupt_hblank && status.mode == 0;
|
||||
status.irq |= status.interrupt_vblank && status.mode == 1;
|
||||
status.irq |= status.interrupt_oam && status.mode == 2;
|
||||
status.irq |= status.interrupt_lyc && coincidence();
|
||||
|
||||
if(!irq && status.irq) cpu.raise(CPU::Interrupt::Stat);
|
||||
}
|
||||
|
||||
auto PPU::coincidence() -> bool {
|
||||
uint ly = status.ly;
|
||||
if(ly == 153 && status.lx >= 92) ly = 0; //LYC=0 triggers early during LY=153
|
||||
return ly == status.lyc;
|
||||
}
|
||||
|
||||
auto PPU::refresh() -> void {
|
||||
if(!system.sgb()) Emulator::video.refresh(screen, 160 * sizeof(uint32), 160, 144);
|
||||
}
|
||||
|
||||
auto PPU::wait(uint clocks) -> void {
|
||||
while(clocks--) {
|
||||
stat();
|
||||
if(status.dma_active) {
|
||||
uint hi = status.dma_clock++;
|
||||
uint lo = hi & (cpu.status.speed_double ? 1 : 3);
|
||||
@@ -90,21 +95,19 @@ auto PPU::add_clocks(uint clocks) -> void {
|
||||
|
||||
status.lx++;
|
||||
clock += cpu.frequency;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) {
|
||||
co_switch(scheduler.active_thread = cpu.thread);
|
||||
}
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::hflip(uint data) const -> uint {
|
||||
return ((data & 0x8080) >> 7) | ((data & 0x4040) >> 5)
|
||||
| ((data & 0x2020) >> 3) | ((data & 0x1010) >> 1)
|
||||
| ((data & 0x0808) << 1) | ((data & 0x0404) << 3)
|
||||
| ((data & 0x0202) << 5) | ((data & 0x0101) << 7);
|
||||
return (data & 0x8080) >> 7 | (data & 0x4040) >> 5
|
||||
| (data & 0x2020) >> 3 | (data & 0x1010) >> 1
|
||||
| (data & 0x0808) << 1 | (data & 0x0404) << 3
|
||||
| (data & 0x0202) << 5 | (data & 0x0101) << 7;
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
create(Main, 4 * 1024 * 1024);
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
|
||||
if(system.cgb()) {
|
||||
scanline = {&PPU::cgb_scanline, this};
|
||||
@@ -141,11 +144,12 @@ auto PPU::power() -> void {
|
||||
for(auto& n : vram) n = 0x00;
|
||||
for(auto& n : oam) n = 0x00;
|
||||
for(auto& n : bgp) n = 0x00;
|
||||
for(auto& n : obp[0]) n = 0x00;
|
||||
for(auto& n : obp[1]) n = 0x00;
|
||||
for(auto& n : obp[0]) n = 3;
|
||||
for(auto& n : obp[1]) n = 3;
|
||||
for(auto& n : bgpd) n = 0x0000;
|
||||
for(auto& n : obpd) n = 0x0000;
|
||||
|
||||
status.irq = false;
|
||||
status.lx = 0;
|
||||
|
||||
status.display_enable = 0;
|
||||
@@ -161,6 +165,7 @@ auto PPU::power() -> void {
|
||||
status.interrupt_oam = 0;
|
||||
status.interrupt_vblank = 0;
|
||||
status.interrupt_hblank = 0;
|
||||
status.mode = 0;
|
||||
|
||||
status.scy = 0;
|
||||
status.scx = 0;
|
||||
|
@@ -1,7 +1,13 @@
|
||||
struct PPU : Thread, MMIO {
|
||||
static auto Main() -> void;
|
||||
auto enabled() const -> bool;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto add_clocks(uint clocks) -> void;
|
||||
auto mode(uint) -> void;
|
||||
auto stat() -> void;
|
||||
auto coincidence() -> bool;
|
||||
auto refresh() -> void;
|
||||
auto wait(uint clocks) -> void;
|
||||
|
||||
auto hflip(uint data) const -> uint;
|
||||
|
||||
@@ -41,6 +47,7 @@ struct PPU : Thread, MMIO {
|
||||
function<auto () -> void> run;
|
||||
|
||||
struct Status {
|
||||
bool irq; //STAT IRQ line
|
||||
uint lx;
|
||||
|
||||
//$ff40 LCDC
|
||||
@@ -58,6 +65,7 @@ struct PPU : Thread, MMIO {
|
||||
bool interrupt_oam;
|
||||
bool interrupt_vblank;
|
||||
bool interrupt_hblank;
|
||||
uint2 mode;
|
||||
|
||||
//$ff42 SCY
|
||||
uint8 scy;
|
||||
|
@@ -9,6 +9,7 @@ auto PPU::serialize(serializer& s) -> void {
|
||||
s.array(bgpd);
|
||||
s.array(obpd);
|
||||
|
||||
s.integer(status.irq);
|
||||
s.integer(status.lx);
|
||||
|
||||
s.integer(status.display_enable);
|
||||
@@ -24,6 +25,7 @@ auto PPU::serialize(serializer& s) -> void {
|
||||
s.integer(status.interrupt_oam);
|
||||
s.integer(status.interrupt_vblank);
|
||||
s.integer(status.interrupt_hblank);
|
||||
s.integer(status.mode);
|
||||
|
||||
s.integer(status.scy);
|
||||
s.integer(status.scx);
|
||||
|
@@ -4,20 +4,41 @@ namespace GameBoy {
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
auto Scheduler::init() -> void {
|
||||
host_thread = co_active();
|
||||
active_thread = cpu.thread;
|
||||
auto Scheduler::power() -> void {
|
||||
host = co_active();
|
||||
resume = cpu.thread;
|
||||
}
|
||||
|
||||
auto Scheduler::enter() -> void {
|
||||
host_thread = co_active();
|
||||
co_switch(active_thread);
|
||||
auto Scheduler::enter(Mode mode_) -> Event {
|
||||
mode = mode_;
|
||||
host = co_active();
|
||||
co_switch(resume);
|
||||
if(event == Event::Frame) ppu.refresh();
|
||||
return event;
|
||||
}
|
||||
|
||||
auto Scheduler::exit(ExitReason reason) -> void {
|
||||
exit_reason = reason;
|
||||
active_thread = co_active();
|
||||
co_switch(host_thread);
|
||||
auto Scheduler::exit(Event event_) -> void {
|
||||
event = event_;
|
||||
resume = co_active();
|
||||
co_switch(host);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize(cothread_t thread) -> void {
|
||||
if(thread == cpu.thread) {
|
||||
while(enter(Mode::SynchronizeCPU) != Event::Synchronize);
|
||||
} else {
|
||||
resume = thread;
|
||||
while(enter(Mode::SynchronizeAll) != Event::Synchronize);
|
||||
}
|
||||
}
|
||||
|
||||
auto Scheduler::synchronize() -> void {
|
||||
if(co_active() == cpu.thread && mode == Mode::SynchronizeCPU) return exit(Event::Synchronize);
|
||||
if(co_active() != cpu.thread && mode == Mode::SynchronizeAll) return exit(Event::Synchronize);
|
||||
}
|
||||
|
||||
auto Scheduler::synchronizing() const -> bool {
|
||||
return mode == Mode::SynchronizeAll;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,14 +1,29 @@
|
||||
struct Scheduler {
|
||||
enum class SynchronizeMode : uint { None, CPU, All } sync;
|
||||
enum class ExitReason : uint { UnknownEvent, StepEvent, FrameEvent, SynchronizeEvent };
|
||||
enum class Mode : uint {
|
||||
Run,
|
||||
SynchronizeCPU,
|
||||
SynchronizeAll,
|
||||
};
|
||||
|
||||
auto init() -> void;
|
||||
auto enter() -> void;
|
||||
auto exit(ExitReason) -> void;
|
||||
enum class Event : uint {
|
||||
Unknown,
|
||||
Step,
|
||||
Frame,
|
||||
Synchronize,
|
||||
};
|
||||
|
||||
cothread_t host_thread = nullptr;
|
||||
cothread_t active_thread = nullptr;
|
||||
ExitReason exit_reason = ExitReason::UnknownEvent;
|
||||
auto power() -> void;
|
||||
auto enter(Mode = Mode::Run) -> Event;
|
||||
auto exit(Event) -> void;
|
||||
auto synchronize(cothread_t) -> void;
|
||||
auto synchronize() -> void;
|
||||
auto synchronizing() const -> bool;
|
||||
|
||||
private:
|
||||
cothread_t host = nullptr;
|
||||
cothread_t resume = nullptr;
|
||||
Mode mode = Mode::Run;
|
||||
Event event = Event::Unknown;
|
||||
};
|
||||
|
||||
extern Scheduler scheduler;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(serialize_size);
|
||||
serializer s(_serializeSize);
|
||||
|
||||
uint signature = 0x31545342, version = Info::SerializerVersion;
|
||||
char hash[64], description[512];
|
||||
@@ -11,7 +11,7 @@ auto System::serialize() -> serializer {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -28,15 +28,15 @@ auto System::unserialize(serializer& s) -> bool {
|
||||
if(version != Info::SerializerVersion) return false;
|
||||
|
||||
power();
|
||||
serialize_all(s);
|
||||
serializeAll(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
s.integer(clocks_executed);
|
||||
s.integer(_clocksExecuted);
|
||||
}
|
||||
|
||||
auto System::serialize_all(serializer& s) -> void {
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
cartridge.serialize(s);
|
||||
system.serialize(s);
|
||||
cpu.serialize(s);
|
||||
@@ -44,10 +44,10 @@ auto System::serialize_all(serializer& s) -> void {
|
||||
apu.serialize(s);
|
||||
}
|
||||
|
||||
auto System::serialize_init() -> void {
|
||||
auto System::serializeInit() -> void {
|
||||
serializer s;
|
||||
|
||||
uint signature = 0, version = 0, crc32 = 0;
|
||||
uint signature = 0, version = 0;
|
||||
char hash[64], description[512];
|
||||
|
||||
s.integer(signature);
|
||||
@@ -55,6 +55,6 @@ auto System::serialize_init() -> void {
|
||||
s.array(hash);
|
||||
s.array(description);
|
||||
|
||||
serialize_all(s);
|
||||
serialize_size = s.size();
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
}
|
||||
|
@@ -2,9 +2,14 @@
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
#include "video.cpp"
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
|
||||
auto System::loaded() const -> bool { return _loaded; }
|
||||
auto System::revision() const -> Revision { return _revision; }
|
||||
auto System::clocksExecuted() const -> uint { return _clocksExecuted; }
|
||||
|
||||
System::System() {
|
||||
for(auto& byte : bootROM.dmg) byte = 0;
|
||||
for(auto& byte : bootROM.sgb) byte = 0;
|
||||
@@ -12,37 +17,13 @@ System::System() {
|
||||
}
|
||||
|
||||
auto System::run() -> void {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
||||
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
auto System::runtosave() -> void {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::CPU;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.active_thread = ppu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.active_thread = apu.thread;
|
||||
runthreadtosave();
|
||||
|
||||
scheduler.sync = Scheduler::SynchronizeMode::None;
|
||||
}
|
||||
|
||||
auto System::runthreadtosave() -> void {
|
||||
while(true) {
|
||||
scheduler.enter();
|
||||
if(scheduler.exit_reason == Scheduler::ExitReason::SynchronizeEvent) break;
|
||||
if(scheduler.exit_reason == Scheduler::ExitReason::FrameEvent) {
|
||||
video.refresh();
|
||||
}
|
||||
}
|
||||
auto System::runToSave() -> void {
|
||||
scheduler.synchronize(cpu.thread);
|
||||
scheduler.synchronize(ppu.thread);
|
||||
scheduler.synchronize(apu.thread);
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
@@ -50,31 +31,53 @@ auto System::init() -> void {
|
||||
}
|
||||
|
||||
auto System::load(Revision revision) -> void {
|
||||
this->revision = revision;
|
||||
serialize_init();
|
||||
if(revision == Revision::SuperGameBoy) return; //Super Famicom core loads boot ROM for SGB
|
||||
_revision = revision;
|
||||
|
||||
interface->loadRequest(ID::SystemManifest, "manifest.bml", true);
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
string path = "system/cpu/rom/name";
|
||||
if(revision == Revision::SuperGameBoy) path = "board/icd2/rom/name";
|
||||
|
||||
if(auto bootROM = document["system/cpu/rom/name"].text()) {
|
||||
if(auto bootROM = document[path].text()) {
|
||||
interface->loadRequest(
|
||||
revision == Revision::GameBoy ? ID::GameBoyBootROM : ID::GameBoyColorBootROM,
|
||||
revision == Revision::GameBoy ? ID::GameBoyBootROM
|
||||
: revision == Revision::SuperGameBoy ? ID::SuperGameBoyBootROM
|
||||
: revision == Revision::GameBoyColor ? ID::GameBoyColorBootROM
|
||||
: ID::GameBoyBootROM,
|
||||
bootROM, true
|
||||
);
|
||||
}
|
||||
|
||||
cartridge.load(revision);
|
||||
serializeInit();
|
||||
_loaded = true;
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
cartridge.unload();
|
||||
_loaded = false;
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
if(!system.sgb()) {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
configureVideoEffects();
|
||||
|
||||
Emulator::audio.reset();
|
||||
Emulator::audio.setInterface(interface);
|
||||
}
|
||||
|
||||
bus.power();
|
||||
cartridge.power();
|
||||
cpu.power();
|
||||
ppu.power();
|
||||
apu.power();
|
||||
video.power();
|
||||
scheduler.init();
|
||||
scheduler.power();
|
||||
|
||||
clocks_executed = 0;
|
||||
_clocksExecuted = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,29 +9,37 @@ struct System {
|
||||
GameBoy,
|
||||
SuperGameBoy,
|
||||
GameBoyColor,
|
||||
} revision;
|
||||
};
|
||||
|
||||
System();
|
||||
|
||||
inline auto dmg() const { return revision == Revision::GameBoy; }
|
||||
inline auto sgb() const { return revision == Revision::SuperGameBoy; }
|
||||
inline auto cgb() const { return revision == Revision::GameBoyColor; }
|
||||
auto loaded() const -> bool;
|
||||
auto revision() const -> Revision;
|
||||
auto clocksExecuted() const -> uint;
|
||||
|
||||
inline auto dmg() const { return _revision == Revision::GameBoy; }
|
||||
inline auto sgb() const { return _revision == Revision::SuperGameBoy; }
|
||||
inline auto cgb() const { return _revision == Revision::GameBoyColor; }
|
||||
|
||||
auto run() -> void;
|
||||
auto runtosave() -> void;
|
||||
auto runthreadtosave() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto load(Revision) -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
auto serialize_all(serializer&) -> void;
|
||||
auto serialize_init() -> void;
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
struct BootROM {
|
||||
uint8 dmg[ 256];
|
||||
@@ -43,8 +51,10 @@ struct System {
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
uint clocks_executed = 0;
|
||||
uint serialize_size = 0;
|
||||
bool _loaded = false;
|
||||
Revision _revision = Revision::GameBoy;
|
||||
uint _serializeSize = 0;
|
||||
uint _clocksExecuted = 0;
|
||||
};
|
||||
|
||||
#include <gb/interface/interface.hpp>
|
||||
|
9
higan/gb/system/video.cpp
Normal file
9
higan/gb/system/video.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
auto System::configureVideoPalette() -> void {
|
||||
if(sgb()) return;
|
||||
Emulator::video.setPalette();
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
if(sgb()) return;
|
||||
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
|
||||
}
|
@@ -1,111 +0,0 @@
|
||||
#include <gb/gb.hpp>
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
Video video;
|
||||
|
||||
Video::Video() {
|
||||
output = new uint32[160 * 144];
|
||||
paletteStandard = new uint32[1 << 15];
|
||||
paletteEmulation = new uint32[1 << 15];
|
||||
}
|
||||
|
||||
Video::~Video() {
|
||||
delete[] output;
|
||||
delete[] paletteStandard;
|
||||
delete[] paletteEmulation;
|
||||
}
|
||||
|
||||
auto Video::power() -> void {
|
||||
memory::fill(output, 160 * 144 * sizeof(uint32));
|
||||
|
||||
if(system.dmg()) {
|
||||
for(auto color : range(1 << 2)) {
|
||||
uint L = image::normalize(3 - color, 2, 8);
|
||||
uint R = monochrome[color][0] >> 8;
|
||||
uint G = monochrome[color][1] >> 8;
|
||||
uint B = monochrome[color][2] >> 8;
|
||||
paletteStandard[color] = (255 << 24) | (L << 16) | (L << 8) | (L << 0);
|
||||
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(system.sgb()) {
|
||||
for(auto color : range(1 << 2)) {
|
||||
paletteStandard[color] = color;
|
||||
paletteEmulation[color] = color;
|
||||
}
|
||||
}
|
||||
|
||||
if(system.cgb()) {
|
||||
for(auto color : range(1 << 15)) {
|
||||
uint r = (uint5)(color >> 0);
|
||||
uint g = (uint5)(color >> 5);
|
||||
uint b = (uint5)(color >> 10);
|
||||
|
||||
{ uint R = image::normalize(r, 5, 8);
|
||||
uint G = image::normalize(g, 5, 8);
|
||||
uint B = image::normalize(b, 5, 8);
|
||||
paletteStandard[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
||||
}
|
||||
|
||||
{ uint R = (r * 26 + g * 4 + b * 2);
|
||||
uint G = ( g * 24 + b * 8);
|
||||
uint B = (r * 6 + g * 4 + b * 22);
|
||||
R = min(960, R) >> 2;
|
||||
G = min(960, G) >> 2;
|
||||
B = min(960, B) >> 2;
|
||||
paletteEmulation[color] = (255 << 24) | (R << 16) | (G << 8) | (B << 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Video::refresh() -> void {
|
||||
auto palette = settings.colorEmulation ? paletteEmulation : paletteStandard;
|
||||
|
||||
for(uint y = 0; y < 144; y++) {
|
||||
auto source = ppu.screen + y * 160;
|
||||
auto target = output + y * 160;
|
||||
|
||||
if(settings.blurEmulation) {
|
||||
for(uint x = 0; x < 160; x++) {
|
||||
auto a = palette[*source++];
|
||||
auto b = *target;
|
||||
*target++ = (a + b - ((a ^ b) & 0x01010101)) >> 1;
|
||||
}
|
||||
} else {
|
||||
for(uint x = 0; x < 160; x++) {
|
||||
auto color = palette[*source++];
|
||||
*target++ = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface->videoRefresh(output, 4 * 160, 160, 144);
|
||||
}
|
||||
|
||||
#define DMG_PALETTE_GREEN
|
||||
//#define DMG_PALETTE_YELLOW
|
||||
//#define DMG_PALETTE_WHITE
|
||||
|
||||
const uint16 Video::monochrome[4][3] = {
|
||||
#if defined(DMG_PALETTE_GREEN)
|
||||
{0xaeae, 0xd9d9, 0x2727},
|
||||
{0x5858, 0xa0a0, 0x2828},
|
||||
{0x2020, 0x6262, 0x2929},
|
||||
{0x1a1a, 0x4545, 0x2a2a},
|
||||
#elif defined(DMG_PALETTE_YELLOW)
|
||||
{0xffff, 0xf7f7, 0x7b7b},
|
||||
{0xb5b5, 0xaeae, 0x4a4a},
|
||||
{0x6b6b, 0x6969, 0x3131},
|
||||
{0x2121, 0x2020, 0x1010},
|
||||
#else //DMG_PALETTE_WHITE
|
||||
{0xffff, 0xffff, 0xffff},
|
||||
{0xaaaa, 0xaaaa, 0xaaaa},
|
||||
{0x5555, 0x5555, 0x5555},
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
struct Video {
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
auto power() -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
uint32* output = nullptr;
|
||||
uint32* paletteStandard = nullptr;
|
||||
uint32* paletteEmulation = nullptr;
|
||||
|
||||
private:
|
||||
static const uint16 monochrome[4][3];
|
||||
};
|
||||
|
||||
extern Video video;
|
@@ -1,15 +1,15 @@
|
||||
gba_objects := gba-memory gba-interface gba-scheduler gba-system
|
||||
gba_objects += gba-video gba-cartridge gba-player
|
||||
gba_objects += gba-cpu gba-ppu gba-apu
|
||||
objects += $(gba_objects)
|
||||
processors += arm
|
||||
|
||||
obj/gba-memory.o: $(gba)/memory/memory.cpp $(call rwildcard,$(gba)/memory)
|
||||
obj/gba-interface.o: $(gba)/interface/interface.cpp $(call rwildcard,$(gba)/interface)
|
||||
obj/gba-scheduler.o: $(gba)/scheduler/scheduler.cpp $(call rwildcard,$(gba)/scheduler)
|
||||
obj/gba-system.o: $(gba)/system/system.cpp $(call rwildcard,$(gba)/system)
|
||||
obj/gba-video.o: $(gba)/video/video.cpp $(call rwildcard,$(gba)/video)
|
||||
obj/gba-cartridge.o: $(gba)/cartridge/cartridge.cpp $(call rwildcard,$(gba)/cartridge)
|
||||
obj/gba-player.o: $(gba)/player/player.cpp $(call rwildcard,$(gba)/player)
|
||||
obj/gba-cpu.o: $(gba)/cpu/cpu.cpp $(call rwildcard,$(gba)/cpu)
|
||||
obj/gba-ppu.o: $(gba)/ppu/ppu.cpp $(call rwildcard,$(gba)/ppu)
|
||||
obj/gba-apu.o: $(gba)/apu/apu.cpp $(call rwildcard,$(gba)/apu)
|
||||
objects += gba-memory gba-interface gba-scheduler gba-system
|
||||
objects += gba-cartridge gba-player
|
||||
objects += gba-cpu gba-ppu gba-apu
|
||||
|
||||
obj/gba-memory.o: gba/memory/memory.cpp $(call rwildcard,gba/memory)
|
||||
obj/gba-interface.o: gba/interface/interface.cpp $(call rwildcard,gba/interface)
|
||||
obj/gba-scheduler.o: gba/scheduler/scheduler.cpp $(call rwildcard,gba/scheduler)
|
||||
obj/gba-system.o: gba/system/system.cpp $(call rwildcard,gba/system)
|
||||
obj/gba-cartridge.o: gba/cartridge/cartridge.cpp $(call rwildcard,gba/cartridge)
|
||||
obj/gba-player.o: gba/player/player.cpp $(call rwildcard,gba/player)
|
||||
obj/gba-cpu.o: gba/cpu/cpu.cpp $(call rwildcard,gba/cpu)
|
||||
obj/gba-ppu.o: gba/ppu/ppu.cpp $(call rwildcard,gba/ppu)
|
||||
obj/gba-apu.o: gba/apu/apu.cpp $(call rwildcard,gba/apu)
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "mmio.cpp"
|
||||
#include "square.cpp"
|
||||
#include "square1.cpp"
|
||||
@@ -15,13 +14,7 @@ namespace GameBoyAdvance {
|
||||
APU apu;
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::All) {
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
apu.main();
|
||||
}
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
@@ -70,17 +63,18 @@ auto APU::main() -> void {
|
||||
if(regs.bias.amplitude == 3) lsample &= ~15, rsample &= ~15;
|
||||
|
||||
if(cpu.regs.mode == CPU::Registers::Mode::Stop) lsample = 0, rsample = 0;
|
||||
interface->audioSample(sclamp<16>(lsample << 6), sclamp<16>(rsample << 6)); //should be <<5, use <<6 for added volume
|
||||
stream->sample(sclamp<16>(lsample << 6) / 32768.0, sclamp<16>(rsample << 6) / 32768.0); //should be <<5; use <<6 for added volume
|
||||
step(512);
|
||||
}
|
||||
|
||||
auto APU::step(uint clocks) -> void {
|
||||
clock += clocks;
|
||||
if(clock >= 0 && scheduler.sync != Scheduler::SynchronizeMode::All) co_switch(cpu.thread);
|
||||
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
create(APU::Enter, 16777216);
|
||||
create(APU::Enter, 16'777'216);
|
||||
stream = Emulator::audio.createStream(2, 16'777'216.0 / 512.0);
|
||||
|
||||
square1.power();
|
||||
square2.power();
|
||||
@@ -90,7 +84,8 @@ auto APU::power() -> void {
|
||||
fifo[0].power();
|
||||
fifo[1].power();
|
||||
|
||||
regs.bias = 0x0200;
|
||||
regs.bias.amplitude = 0;
|
||||
regs.bias.level = 0x200;
|
||||
|
||||
for(uint n = 0x060; n <= 0x0a7; n++) bus.mmio[n] = this;
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
struct APU : Thread, MMIO {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
#include "registers.hpp"
|
||||
|
||||
static auto Enter() -> void;
|
||||
|
@@ -2,215 +2,234 @@ auto APU::read(uint32 addr) -> uint8 {
|
||||
switch(addr) {
|
||||
|
||||
//NR10
|
||||
case 0x04000060: return square1.read(0);
|
||||
case 0x04000061: return 0u;
|
||||
case 0x0400'0060: return square1.read(0);
|
||||
case 0x0400'0061: return 0;
|
||||
|
||||
//NR11 + NR12
|
||||
case 0x04000062: return square1.read(1);
|
||||
case 0x04000063: return square1.read(2);
|
||||
//NR11, NR12
|
||||
case 0x0400'0062: return square1.read(1);
|
||||
case 0x0400'0063: return square1.read(2);
|
||||
|
||||
//NR13 + NR14
|
||||
case 0x04000064: return square1.read(3);
|
||||
case 0x04000065: return square1.read(4);
|
||||
//NR13, NR14
|
||||
case 0x0400'0064: return square1.read(3);
|
||||
case 0x0400'0065: return square1.read(4);
|
||||
|
||||
//NR21 + NR22
|
||||
case 0x04000068: return square2.read(1);
|
||||
case 0x04000069: return square2.read(2);
|
||||
//NR21, NR22
|
||||
case 0x0400'0068: return square2.read(1);
|
||||
case 0x0400'0069: return square2.read(2);
|
||||
|
||||
//NR23 + NR24
|
||||
case 0x0400006c: return square2.read(3);
|
||||
case 0x0400006d: return square2.read(4);
|
||||
//NR23, NR24
|
||||
case 0x0400'006c: return square2.read(3);
|
||||
case 0x0400'006d: return square2.read(4);
|
||||
|
||||
//NR30
|
||||
case 0x04000070: return wave.read(0);
|
||||
case 0x04000071: return 0u;
|
||||
case 0x0400'0070: return wave.read(0);
|
||||
case 0x0400'0071: return 0;
|
||||
|
||||
//NR31 + NR32
|
||||
case 0x04000072: return wave.read(1);
|
||||
case 0x04000073: return wave.read(2);
|
||||
//NR31, NR32
|
||||
case 0x0400'0072: return wave.read(1);
|
||||
case 0x0400'0073: return wave.read(2);
|
||||
|
||||
//NR33 + NR34
|
||||
case 0x04000074: return wave.read(3);
|
||||
case 0x04000075: return wave.read(4);
|
||||
//NR33, NR34
|
||||
case 0x0400'0074: return wave.read(3);
|
||||
case 0x0400'0075: return wave.read(4);
|
||||
|
||||
//NR41 + NR42
|
||||
case 0x04000078: return noise.read(1);
|
||||
case 0x04000079: return noise.read(2);
|
||||
//NR41, NR42
|
||||
case 0x0400'0078: return noise.read(1);
|
||||
case 0x0400'0079: return noise.read(2);
|
||||
|
||||
//NR43 + NR44
|
||||
case 0x0400007c: return noise.read(3);
|
||||
case 0x0400007d: return noise.read(4);
|
||||
//NR43, NR44
|
||||
case 0x0400'007c: return noise.read(3);
|
||||
case 0x0400'007d: return noise.read(4);
|
||||
|
||||
//NR50 + NR51
|
||||
case 0x04000080: return sequencer.read(0);
|
||||
case 0x04000081: return sequencer.read(1);
|
||||
//NR50, NR51
|
||||
case 0x0400'0080: return sequencer.read(0);
|
||||
case 0x0400'0081: return sequencer.read(1);
|
||||
|
||||
//SOUND_CNT_H
|
||||
case 0x04000082:
|
||||
return (fifo[1].volume << 3) | (fifo[0].volume << 2) | (sequencer.volume << 0);
|
||||
case 0x04000083:
|
||||
return (fifo[1].timer << 6) | (fifo[1].lenable << 5) | (fifo[1].renable << 4)
|
||||
| (fifo[0].timer << 2) | (fifo[0].lenable << 1) | (fifo[0].renable << 0);
|
||||
case 0x0400'0082: return (
|
||||
sequencer.volume << 0
|
||||
| fifo[0].volume << 2
|
||||
| fifo[1].volume << 3
|
||||
);
|
||||
|
||||
case 0x0400'0083: return (
|
||||
fifo[0].renable << 0
|
||||
| fifo[0].lenable << 1
|
||||
| fifo[0].timer << 2
|
||||
| fifo[1].renable << 4
|
||||
| fifo[1].lenable << 5
|
||||
| fifo[1].timer << 6
|
||||
);
|
||||
|
||||
//NR52
|
||||
case 0x04000084: return sequencer.read(2);
|
||||
case 0x04000085: return 0u;
|
||||
case 0x0400'0084: return sequencer.read(2);
|
||||
case 0x0400'0085: return 0;
|
||||
|
||||
//SOUNDBIAS
|
||||
case 0x04000088: return regs.bias >> 0;
|
||||
case 0x04000089: return regs.bias >> 8;
|
||||
case 0x0400'0088: return (
|
||||
regs.bias.level.bits(0,7)
|
||||
);
|
||||
case 0x0400'0089: return (
|
||||
regs.bias.level.bits(8,9) << 0
|
||||
| regs.bias.amplitude << 6
|
||||
);
|
||||
|
||||
//WAVE_RAM0_L
|
||||
case 0x04000090: return wave.readram( 0);
|
||||
case 0x04000091: return wave.readram( 1);
|
||||
case 0x0400'0090: return wave.readram( 0);
|
||||
case 0x0400'0091: return wave.readram( 1);
|
||||
|
||||
//WAVE_RAM0_H
|
||||
case 0x04000092: return wave.readram( 2);
|
||||
case 0x04000093: return wave.readram( 3);
|
||||
case 0x0400'0092: return wave.readram( 2);
|
||||
case 0x0400'0093: return wave.readram( 3);
|
||||
|
||||
//WAVE_RAM1_L
|
||||
case 0x04000094: return wave.readram( 4);
|
||||
case 0x04000095: return wave.readram( 5);
|
||||
case 0x0400'0094: return wave.readram( 4);
|
||||
case 0x0400'0095: return wave.readram( 5);
|
||||
|
||||
//WAVE_RAM1_H
|
||||
case 0x04000096: return wave.readram( 6);
|
||||
case 0x04000097: return wave.readram( 7);
|
||||
case 0x0400'0096: return wave.readram( 6);
|
||||
case 0x0400'0097: return wave.readram( 7);
|
||||
|
||||
//WAVE_RAM2_L
|
||||
case 0x04000098: return wave.readram( 8);
|
||||
case 0x04000099: return wave.readram( 9);
|
||||
case 0x0400'0098: return wave.readram( 8);
|
||||
case 0x0400'0099: return wave.readram( 9);
|
||||
|
||||
//WAVE_RAM2_H
|
||||
case 0x0400009a: return wave.readram(10);
|
||||
case 0x0400009b: return wave.readram(11);
|
||||
case 0x0400'009a: return wave.readram(10);
|
||||
case 0x0400'009b: return wave.readram(11);
|
||||
|
||||
//WAVE_RAM3_L
|
||||
case 0x0400009c: return wave.readram(12);
|
||||
case 0x0400009d: return wave.readram(13);
|
||||
case 0x0400'009c: return wave.readram(12);
|
||||
case 0x0400'009d: return wave.readram(13);
|
||||
|
||||
//WAVE_RAM3_H
|
||||
case 0x0400009e: return wave.readram(14);
|
||||
case 0x0400009f: return wave.readram(15);
|
||||
case 0x0400'009e: return wave.readram(14);
|
||||
case 0x0400'009f: return wave.readram(15);
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto APU::write(uint32 addr, uint8 byte) -> void {
|
||||
auto APU::write(uint32 addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
|
||||
//NR10
|
||||
case 0x04000060: return square1.write(0, byte);
|
||||
case 0x04000061: return;
|
||||
case 0x0400'0060: return square1.write(0, data);
|
||||
case 0x0400'0061: return;
|
||||
|
||||
//NR11 + NR12
|
||||
case 0x04000062: return square1.write(1, byte);
|
||||
case 0x04000063: return square1.write(2, byte);
|
||||
//NR11, NR12
|
||||
case 0x0400'0062: return square1.write(1, data);
|
||||
case 0x0400'0063: return square1.write(2, data);
|
||||
|
||||
//NR13 + NR14
|
||||
case 0x04000064: return square1.write(3, byte);
|
||||
case 0x04000065: return square1.write(4, byte);
|
||||
//NR13, NR14
|
||||
case 0x0400'0064: return square1.write(3, data);
|
||||
case 0x0400'0065: return square1.write(4, data);
|
||||
|
||||
//NR21 + NR22
|
||||
case 0x04000068: return square2.write(1, byte);
|
||||
case 0x04000069: return square2.write(2, byte);
|
||||
//NR21, NR22
|
||||
case 0x0400'0068: return square2.write(1, data);
|
||||
case 0x0400'0069: return square2.write(2, data);
|
||||
|
||||
//NR23 + NR24
|
||||
case 0x0400006c: return square2.write(3, byte);
|
||||
case 0x0400006d: return square2.write(4, byte);
|
||||
//NR23, NR24
|
||||
case 0x0400'006c: return square2.write(3, data);
|
||||
case 0x0400'006d: return square2.write(4, data);
|
||||
|
||||
//NR30
|
||||
case 0x04000070: return wave.write(0, byte);
|
||||
case 0x04000071: return;
|
||||
case 0x0400'0070: return wave.write(0, data);
|
||||
case 0x0400'0071: return;
|
||||
|
||||
//NR31 + NR32
|
||||
case 0x04000072: return wave.write(1, byte);
|
||||
case 0x04000073: return wave.write(2, byte);
|
||||
//NR31, NR32
|
||||
case 0x0400'0072: return wave.write(1, data);
|
||||
case 0x0400'0073: return wave.write(2, data);
|
||||
|
||||
//NR33 + NR34
|
||||
case 0x04000074: return wave.write(3, byte);
|
||||
case 0x04000075: return wave.write(4, byte);
|
||||
//NR33, NR34
|
||||
case 0x0400'0074: return wave.write(3, data);
|
||||
case 0x0400'0075: return wave.write(4, data);
|
||||
|
||||
//NR41 + NR42
|
||||
case 0x04000078: return noise.write(1, byte);
|
||||
case 0x04000079: return noise.write(2, byte);
|
||||
//NR41, NR42
|
||||
case 0x0400'0078: return noise.write(1, data);
|
||||
case 0x0400'0079: return noise.write(2, data);
|
||||
|
||||
//NR43 + NR44
|
||||
case 0x0400007c: return noise.write(3, byte);
|
||||
case 0x0400007d: return noise.write(4, byte);
|
||||
//NR43, NR44
|
||||
case 0x0400'007c: return noise.write(3, data);
|
||||
case 0x0400'007d: return noise.write(4, data);
|
||||
|
||||
//NR50 + NR51
|
||||
case 0x04000080: return sequencer.write(0, byte);
|
||||
case 0x04000081: return sequencer.write(1, byte);
|
||||
//NR50, NR51
|
||||
case 0x0400'0080: return sequencer.write(0, data);
|
||||
case 0x0400'0081: return sequencer.write(1, data);
|
||||
|
||||
//SOUND_CNT_H
|
||||
case 0x04000082:
|
||||
sequencer.volume = byte >> 0;
|
||||
fifo[0].volume = byte >> 2;
|
||||
fifo[1].volume = byte >> 3;
|
||||
case 0x0400'0082:
|
||||
sequencer.volume = data.bits(0,1);
|
||||
fifo[0].volume = data.bit (2);
|
||||
fifo[1].volume = data.bit (3);
|
||||
return;
|
||||
case 0x04000083:
|
||||
fifo[0].renable = byte >> 0;
|
||||
fifo[0].lenable = byte >> 1;
|
||||
fifo[0].timer = byte >> 2;
|
||||
if(byte & 1 << 3) fifo[0].reset();
|
||||
fifo[1].renable = byte >> 4;
|
||||
fifo[1].lenable = byte >> 5;
|
||||
fifo[1].timer = byte >> 6;
|
||||
if(byte & 1 << 7) fifo[1].reset();
|
||||
case 0x0400'0083:
|
||||
fifo[0].renable = data.bit(0);
|
||||
fifo[0].lenable = data.bit(1);
|
||||
fifo[0].timer = data.bit(2);
|
||||
if(data.bit(3)) fifo[0].reset();
|
||||
fifo[1].renable = data.bit(4);
|
||||
fifo[1].lenable = data.bit(5);
|
||||
fifo[1].timer = data.bit(6);
|
||||
if(data.bit(7)) fifo[1].reset();
|
||||
return;
|
||||
|
||||
//NR52
|
||||
case 0x04000084: return sequencer.write(2, byte);
|
||||
case 0x04000085: return;
|
||||
case 0x0400'0084: return sequencer.write(2, data);
|
||||
case 0x0400'0085: return;
|
||||
|
||||
//SOUNDBIAS
|
||||
case 0x04000088: regs.bias = (regs.bias & 0xff00) | (byte << 0); return;
|
||||
case 0x04000089: regs.bias = (regs.bias & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0088:
|
||||
regs.bias.level.bits(0,7) = data;
|
||||
return;
|
||||
case 0x0400'0089:
|
||||
regs.bias.level.bits(8,9) = data.bits(0,1);
|
||||
regs.bias.amplitude = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//WAVE_RAM0_L
|
||||
case 0x04000090: return wave.writeram( 0, byte);
|
||||
case 0x04000091: return wave.writeram( 1, byte);
|
||||
case 0x0400'0090: return wave.writeram( 0, data);
|
||||
case 0x0400'0091: return wave.writeram( 1, data);
|
||||
|
||||
//WAVE_RAM0_H
|
||||
case 0x04000092: return wave.writeram( 2, byte);
|
||||
case 0x04000093: return wave.writeram( 3, byte);
|
||||
case 0x0400'0092: return wave.writeram( 2, data);
|
||||
case 0x0400'0093: return wave.writeram( 3, data);
|
||||
|
||||
//WAVE_RAM1_L
|
||||
case 0x04000094: return wave.writeram( 4, byte);
|
||||
case 0x04000095: return wave.writeram( 5, byte);
|
||||
case 0x0400'0094: return wave.writeram( 4, data);
|
||||
case 0x0400'0095: return wave.writeram( 5, data);
|
||||
|
||||
//WAVE_RAM1_H
|
||||
case 0x04000096: return wave.writeram( 6, byte);
|
||||
case 0x04000097: return wave.writeram( 7, byte);
|
||||
case 0x0400'0096: return wave.writeram( 6, data);
|
||||
case 0x0400'0097: return wave.writeram( 7, data);
|
||||
|
||||
//WAVE_RAM2_L
|
||||
case 0x04000098: return wave.writeram( 8, byte);
|
||||
case 0x04000099: return wave.writeram( 9, byte);
|
||||
case 0x0400'0098: return wave.writeram( 8, data);
|
||||
case 0x0400'0099: return wave.writeram( 9, data);
|
||||
|
||||
//WAVE_RAM2_H
|
||||
case 0x0400009a: return wave.writeram(10, byte);
|
||||
case 0x0400009b: return wave.writeram(11, byte);
|
||||
case 0x0400'009a: return wave.writeram(10, data);
|
||||
case 0x0400'009b: return wave.writeram(11, data);
|
||||
|
||||
//WAVE_RAM3_L
|
||||
case 0x0400009c: return wave.writeram(12, byte);
|
||||
case 0x0400009d: return wave.writeram(13, byte);
|
||||
case 0x0400'009c: return wave.writeram(12, data);
|
||||
case 0x0400'009d: return wave.writeram(13, data);
|
||||
|
||||
//WAVE_RAM3_H
|
||||
case 0x0400009e: return wave.writeram(14, byte);
|
||||
case 0x0400009f: return wave.writeram(15, byte);
|
||||
case 0x0400'009e: return wave.writeram(14, data);
|
||||
case 0x0400'009f: return wave.writeram(15, data);
|
||||
|
||||
//FIFO_A_L
|
||||
//FIFO_A_H
|
||||
case 0x040000a0: case 0x040000a1:
|
||||
case 0x040000a2: case 0x040000a3:
|
||||
return fifo[0].write(byte);
|
||||
case 0x0400'00a0: case 0x0400'00a1:
|
||||
case 0x0400'00a2: case 0x0400'00a3:
|
||||
return fifo[0].write(data);
|
||||
|
||||
//FIFO_B_L
|
||||
//FIFO_B_H
|
||||
case 0x040000a4: case 0x040000a5:
|
||||
case 0x040000a6: case 0x040000a7:
|
||||
return fifo[1].write(byte);
|
||||
case 0x0400'00a4: case 0x0400'00a5:
|
||||
case 0x0400'00a6: case 0x0400'00a7:
|
||||
return fifo[1].write(data);
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +0,0 @@
|
||||
APU::Registers::SoundBias::operator uint16() const {
|
||||
return (
|
||||
(level << 0)
|
||||
| (amplitude << 14)
|
||||
);
|
||||
}
|
||||
|
||||
auto APU::Registers::SoundBias::operator=(uint16 source) -> uint16 {
|
||||
level = (source >> 0) & 1023;
|
||||
amplitude = (source >> 14) & 3;
|
||||
return operator uint16();
|
||||
}
|
@@ -2,10 +2,6 @@ struct Registers {
|
||||
struct SoundBias {
|
||||
uint10 level;
|
||||
uint2 amplitude;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const SoundBias&) -> SoundBias& = delete;
|
||||
} bias;
|
||||
|
||||
uint clock;
|
||||
|
@@ -23,10 +23,6 @@ Cartridge::~Cartridge() {
|
||||
delete[] flash.data;
|
||||
}
|
||||
|
||||
auto Cartridge::loaded() const -> bool {
|
||||
return isLoaded;
|
||||
}
|
||||
|
||||
auto Cartridge::sha256() const -> string {
|
||||
return information.sha256;
|
||||
}
|
||||
@@ -96,16 +92,10 @@ auto Cartridge::load() -> void {
|
||||
}
|
||||
|
||||
information.sha256 = Hash::SHA256(mrom.data, mrom.size).digest();
|
||||
|
||||
system.load();
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
if(isLoaded) {
|
||||
isLoaded = false;
|
||||
memory.reset();
|
||||
}
|
||||
memory.reset();
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
struct Cartridge {
|
||||
#include "memory.hpp"
|
||||
|
||||
auto loaded() const -> bool;
|
||||
auto sha256() const -> string;
|
||||
auto manifest() const -> string;
|
||||
auto title() const -> string;
|
||||
@@ -31,7 +30,6 @@ struct Cartridge {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
bool isLoaded = false;
|
||||
bool hasSRAM = false;
|
||||
bool hasEEPROM = false;
|
||||
bool hasFLASH = false;
|
||||
|
@@ -1,10 +1,10 @@
|
||||
auto CPU::bus_idle() -> void {
|
||||
auto CPU::busIdle() -> void {
|
||||
prefetch_step(1);
|
||||
}
|
||||
|
||||
auto CPU::bus_read(unsigned mode, uint32 addr) -> uint32 {
|
||||
unsigned wait = bus_wait(mode, addr);
|
||||
unsigned word = pipeline.fetch.instruction;
|
||||
auto CPU::busRead(uint mode, uint32 addr) -> uint32 {
|
||||
uint wait = busWait(mode, addr);
|
||||
uint word = pipeline.fetch.instruction;
|
||||
|
||||
if(addr >= 0x1000'0000) {
|
||||
prefetch_step(wait);
|
||||
@@ -35,8 +35,8 @@ auto CPU::bus_read(unsigned mode, uint32 addr) -> uint32 {
|
||||
return word;
|
||||
}
|
||||
|
||||
auto CPU::bus_write(unsigned mode, uint32 addr, uint32 word) -> void {
|
||||
unsigned wait = bus_wait(mode, addr);
|
||||
auto CPU::busWrite(uint mode, uint32 addr, uint32 word) -> void {
|
||||
uint wait = busWait(mode, addr);
|
||||
|
||||
if(addr >= 0x1000'0000) {
|
||||
prefetch_step(wait);
|
||||
@@ -57,7 +57,7 @@ auto CPU::bus_write(unsigned mode, uint32 addr, uint32 word) -> void {
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
|
||||
auto CPU::busWait(uint mode, uint32 addr) -> uint {
|
||||
if(addr >= 0x1000'0000) return 1; //unmapped
|
||||
if(addr < 0x0200'0000) return 1;
|
||||
if(addr < 0x0300'0000) return (16 - regs.memory.control.ewramwait) * (mode & Word ? 2 : 1);
|
||||
@@ -65,9 +65,9 @@ auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
|
||||
if(addr < 0x0700'0000) return mode & Word ? 2 : 1;
|
||||
if(addr < 0x0800'0000) return 1;
|
||||
|
||||
static unsigned timings[] = {5, 4, 3, 9};
|
||||
unsigned n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
|
||||
unsigned s = regs.wait.control.swait[addr >> 25 & 3];
|
||||
static uint timings[] = {5, 4, 3, 9};
|
||||
uint n = timings[regs.wait.control.nwait[addr >> 25 & 3]];
|
||||
uint s = regs.wait.control.swait[addr >> 25 & 3];
|
||||
|
||||
switch(addr & 0x0e00'0000) {
|
||||
case 0x0800'0000: s = s ? 2 : 3; break;
|
||||
@@ -79,7 +79,7 @@ auto CPU::bus_wait(unsigned mode, uint32 addr) -> unsigned {
|
||||
bool sequential = (mode & Sequential);
|
||||
if((addr & 0x1fffe) == 0) sequential = false; //N cycle on 16-bit ROM crossing 128KB page boundary (RAM S==N)
|
||||
|
||||
unsigned clocks = sequential ? s : n;
|
||||
uint clocks = sequential ? s : n;
|
||||
if(mode & Word) clocks += s; //16-bit bus requires two transfers for words
|
||||
return clocks;
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
#include "registers.cpp"
|
||||
#include "prefetch.cpp"
|
||||
#include "bus.cpp"
|
||||
#include "mmio.cpp"
|
||||
@@ -16,21 +15,21 @@ CPU::CPU() {
|
||||
iwram = new uint8[ 32 * 1024];
|
||||
ewram = new uint8[256 * 1024];
|
||||
|
||||
regs.dma[0].source.bits(27); regs.dma[0].run.source.bits(27);
|
||||
regs.dma[0].target.bits(27); regs.dma[0].run.target.bits(27);
|
||||
regs.dma[0].length.bits(14); regs.dma[0].run.length.bits(14);
|
||||
regs.dma[0].source.resize(27); regs.dma[0].run.source.resize(27);
|
||||
regs.dma[0].target.resize(27); regs.dma[0].run.target.resize(27);
|
||||
regs.dma[0].length.resize(14); regs.dma[0].run.length.resize(14);
|
||||
|
||||
regs.dma[1].source.bits(28); regs.dma[1].run.source.bits(28);
|
||||
regs.dma[1].target.bits(27); regs.dma[1].run.target.bits(27);
|
||||
regs.dma[1].length.bits(14); regs.dma[1].run.length.bits(14);
|
||||
regs.dma[1].source.resize(28); regs.dma[1].run.source.resize(28);
|
||||
regs.dma[1].target.resize(27); regs.dma[1].run.target.resize(27);
|
||||
regs.dma[1].length.resize(14); regs.dma[1].run.length.resize(14);
|
||||
|
||||
regs.dma[2].source.bits(28); regs.dma[2].run.source.bits(28);
|
||||
regs.dma[2].target.bits(27); regs.dma[2].run.target.bits(27);
|
||||
regs.dma[2].length.bits(14); regs.dma[2].run.length.bits(14);
|
||||
regs.dma[2].source.resize(28); regs.dma[2].run.source.resize(28);
|
||||
regs.dma[2].target.resize(27); regs.dma[2].run.target.resize(27);
|
||||
regs.dma[2].length.resize(14); regs.dma[2].run.length.resize(14);
|
||||
|
||||
regs.dma[3].source.bits(28); regs.dma[3].run.source.bits(28);
|
||||
regs.dma[3].target.bits(28); regs.dma[3].run.target.bits(28);
|
||||
regs.dma[3].length.bits(16); regs.dma[3].run.length.bits(16);
|
||||
regs.dma[3].source.resize(28); regs.dma[3].run.source.resize(28);
|
||||
regs.dma[3].target.resize(28); regs.dma[3].run.target.resize(28);
|
||||
regs.dma[3].length.resize(16); regs.dma[3].run.length.resize(16);
|
||||
}
|
||||
|
||||
CPU::~CPU() {
|
||||
@@ -39,14 +38,7 @@ CPU::~CPU() {
|
||||
}
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) {
|
||||
if(scheduler.sync == Scheduler::SynchronizeMode::CPU) {
|
||||
scheduler.sync = Scheduler::SynchronizeMode::All;
|
||||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
cpu.main();
|
||||
}
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
@@ -63,7 +55,7 @@ auto CPU::main() -> void {
|
||||
processor.irqline = regs.ime && (regs.irq.enable & regs.irq.flag);
|
||||
|
||||
if(regs.mode == Registers::Mode::Stop) {
|
||||
if((regs.irq.enable.keypad & regs.irq.flag.keypad) == 0) {
|
||||
if(!(regs.irq.enable & regs.irq.flag & Interrupt::Keypad)) {
|
||||
sync_step(16); //STOP does not advance timers
|
||||
} else {
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
@@ -74,7 +66,7 @@ auto CPU::main() -> void {
|
||||
dma_run();
|
||||
|
||||
if(regs.mode == Registers::Mode::Halt) {
|
||||
if((regs.irq.enable & regs.irq.flag) == 0) {
|
||||
if(!(regs.irq.enable & regs.irq.flag)) {
|
||||
step(16);
|
||||
} else {
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
@@ -99,16 +91,19 @@ auto CPU::sync_step(uint clocks) -> void {
|
||||
}
|
||||
|
||||
auto CPU::keypad_run() -> void {
|
||||
if(regs.keypad.control.enable == false) return;
|
||||
//lookup table to convert button indexes to Emulator::Interface indexes
|
||||
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1, 7, 6};
|
||||
|
||||
if(!regs.keypad.control.enable) return;
|
||||
|
||||
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
|
||||
for(auto n : range(10)) {
|
||||
if(regs.keypad.control.flag[n] == false) continue;
|
||||
bool input = interface->inputPoll(0, 0, n);
|
||||
if(!regs.keypad.control.flag[n]) continue;
|
||||
bool input = interface->inputPoll(0, 0, lookup[n]);
|
||||
if(regs.keypad.control.condition == 0) test |= input;
|
||||
if(regs.keypad.control.condition == 1) test &= input;
|
||||
}
|
||||
if(test) regs.irq.flag.keypad = true;
|
||||
if(test) regs.irq.flag |= Interrupt::Keypad;
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
@@ -123,7 +118,14 @@ auto CPU::power() -> void {
|
||||
dma.target = 0;
|
||||
dma.length = 0;
|
||||
dma.data = 0;
|
||||
dma.control = 0;
|
||||
dma.control.targetmode = 0;
|
||||
dma.control.sourcemode = 0;
|
||||
dma.control.repeat = 0;
|
||||
dma.control.size = 0;
|
||||
dma.control.drq = 0;
|
||||
dma.control.timingmode = 0;
|
||||
dma.control.irq = 0;
|
||||
dma.control.enable = 0;
|
||||
dma.pending = 0;
|
||||
dma.run.target = 0;
|
||||
dma.run.source = 0;
|
||||
@@ -133,17 +135,30 @@ auto CPU::power() -> void {
|
||||
timer.period = 0;
|
||||
timer.reload = 0;
|
||||
timer.pending = false;
|
||||
timer.control = 0;
|
||||
timer.control.frequency = 0;
|
||||
timer.control.cascade = 0;
|
||||
timer.control.irq = 0;
|
||||
timer.control.enable = 0;
|
||||
}
|
||||
regs.keypad.control = 0;
|
||||
for(auto& flag : regs.keypad.control.flag) flag = 0;
|
||||
regs.keypad.control.enable = 0;
|
||||
regs.keypad.control.condition = 0;
|
||||
regs.ime = 0;
|
||||
regs.irq.enable = 0;
|
||||
regs.irq.flag = 0;
|
||||
regs.wait.control = 0;
|
||||
for(auto& nwait : regs.wait.control.nwait) nwait = 0;
|
||||
for(auto& swait : regs.wait.control.swait) swait = 0;
|
||||
regs.wait.control.phi = 0;
|
||||
regs.wait.control.prefetch = 0;
|
||||
regs.wait.control.gametype = 0;
|
||||
regs.postboot = 0;
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
regs.clock = 0;
|
||||
regs.memory.control = 0x0d00'0020;
|
||||
regs.memory.control.disable = 0;
|
||||
regs.memory.control.unknown1 = 0;
|
||||
regs.memory.control.ewram = 1;
|
||||
regs.memory.control.ewramwait = 13;
|
||||
regs.memory.control.unknown2 = 0;
|
||||
|
||||
pending.dma.vblank = 0;
|
||||
pending.dma.hblank = 0;
|
||||
|
@@ -2,6 +2,25 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
||||
using ARM::read;
|
||||
using ARM::write;
|
||||
|
||||
struct Interrupt {
|
||||
enum : uint {
|
||||
VBlank = 0x0001,
|
||||
HBlank = 0x0002,
|
||||
VCoincidence = 0x0004,
|
||||
Timer0 = 0x0008,
|
||||
Timer1 = 0x0010,
|
||||
Timer2 = 0x0020,
|
||||
Timer3 = 0x0040,
|
||||
Serial = 0x0080,
|
||||
DMA0 = 0x0100,
|
||||
DMA1 = 0x0200,
|
||||
DMA2 = 0x0400,
|
||||
DMA3 = 0x0800,
|
||||
Keypad = 0x1000,
|
||||
Cartridge = 0x2000,
|
||||
};
|
||||
};
|
||||
|
||||
#include "registers.hpp"
|
||||
#include "prefetch.hpp"
|
||||
#include "state.hpp"
|
||||
@@ -11,7 +30,6 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
||||
~CPU();
|
||||
|
||||
static auto Enter() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
|
||||
auto step(uint clocks) -> void override;
|
||||
@@ -21,10 +39,10 @@ struct CPU : Processor::ARM, Thread, MMIO {
|
||||
auto power() -> void;
|
||||
|
||||
//bus.cpp
|
||||
auto bus_idle() -> void override;
|
||||
auto bus_read(uint mode, uint32 addr) -> uint32 override;
|
||||
auto bus_write(uint mode, uint32 addr, uint32 word) -> void override;
|
||||
auto bus_wait(uint mode, uint32 addr) -> uint;
|
||||
auto busIdle() -> void override;
|
||||
auto busRead(uint mode, uint32 addr) -> uint32 override;
|
||||
auto busWrite(uint mode, uint32 addr, uint32 word) -> void override;
|
||||
auto busWait(uint mode, uint32 addr) -> uint;
|
||||
|
||||
//mmio.cpp
|
||||
auto read(uint32 addr) -> uint8;
|
||||
|
@@ -7,8 +7,8 @@ auto CPU::dma_run() -> void {
|
||||
auto& dma = regs.dma[n];
|
||||
if(dma.pending) {
|
||||
dma_exec(dma);
|
||||
if(dma.control.irq) regs.irq.flag.dma[n] = 1;
|
||||
if(dma.control.drq && n == 3) regs.irq.flag.cartridge = 1;
|
||||
if(dma.control.irq) regs.irq.flag |= Interrupt::DMA0 << n;
|
||||
if(dma.control.drq && n == 3) regs.irq.flag |= Interrupt::Cartridge;
|
||||
transferred = true;
|
||||
break;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ auto CPU::dma_exec(Registers::DMA& dma) -> void {
|
||||
uint32 addr = dma.run.source;
|
||||
if(mode & Word) addr &= ~3;
|
||||
if(mode & Half) addr &= ~1;
|
||||
dma.data = bus_read(mode, addr);
|
||||
dma.data = busRead(mode, addr);
|
||||
}
|
||||
|
||||
if(dma.run.target < 0x0200'0000) {
|
||||
@@ -48,7 +48,7 @@ auto CPU::dma_exec(Registers::DMA& dma) -> void {
|
||||
uint32 addr = dma.run.target;
|
||||
if(mode & Word) addr &= ~3;
|
||||
if(mode & Half) addr &= ~1;
|
||||
bus_write(mode, addr, dma.data);
|
||||
busWrite(mode, addr, dma.data);
|
||||
}
|
||||
|
||||
switch(dma.control.sourcemode) {
|
||||
|
@@ -1,321 +1,416 @@
|
||||
auto CPU::read(uint32 addr) -> uint8 {
|
||||
uint8 result = 0;
|
||||
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
|
||||
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DMA0CNT_H
|
||||
//DMA1CNT_H
|
||||
//DMA2CNT_H
|
||||
//DMA3CNT_H
|
||||
case 0x040000ba: case 0x040000bb:
|
||||
case 0x040000c6: case 0x040000c7:
|
||||
case 0x040000d2: case 0x040000d3:
|
||||
case 0x040000de: case 0x040000df: {
|
||||
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return dma.control >> shift;
|
||||
}
|
||||
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de: return (
|
||||
dma().control.targetmode << 5
|
||||
| dma().control.sourcemode.bit(0) << 7
|
||||
);
|
||||
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: return (
|
||||
dma().control.sourcemode.bit(1) << 0
|
||||
| dma().control.repeat << 1
|
||||
| dma().control.size << 2
|
||||
| dma().control.drq << 3
|
||||
| dma().control.timingmode << 4
|
||||
| dma().control.irq << 6
|
||||
| dma().control.enable << 7
|
||||
);
|
||||
|
||||
//TM0CNT_L
|
||||
//TM1CNT_L
|
||||
//TM2CNT_L
|
||||
//TM3CNT_L
|
||||
case 0x04000100: case 0x04000101:
|
||||
case 0x04000104: case 0x04000105:
|
||||
case 0x04000108: case 0x04000109:
|
||||
case 0x0400010c: case 0x0400010d: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return timer.period >> shift;
|
||||
}
|
||||
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: return timer().period.byte(0);
|
||||
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: return timer().period.byte(1);
|
||||
|
||||
//TIM0CNT_H
|
||||
case 0x04000102: case 0x04000103:
|
||||
case 0x04000106: case 0x04000107:
|
||||
case 0x0400010a: case 0x0400010b:
|
||||
case 0x0400010e: case 0x0400010f: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
return timer.control >> shift;
|
||||
}
|
||||
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
|
||||
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: return (
|
||||
timer().control.frequency << 0
|
||||
| timer().control.cascade << 2
|
||||
| timer().control.irq << 6
|
||||
| timer().control.enable << 7
|
||||
);
|
||||
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f: return 0;
|
||||
|
||||
//SIOMULTI0 (SIODATA32_L)
|
||||
//SIOMULTI1 (SIODATA32_H)
|
||||
//SIOMULTI2
|
||||
//SIOMULTI3
|
||||
case 0x04000120: case 0x04000121:
|
||||
case 0x04000122: case 0x04000123:
|
||||
case 0x04000124: case 0x04000125:
|
||||
case 0x04000126: case 0x04000127: {
|
||||
if(auto data = player.read()) return data() >> ((addr & 3) << 3);
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
return data >> shift;
|
||||
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
|
||||
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126: {
|
||||
if(auto data = player.read()) return data().byte(addr.bits(0,1));
|
||||
return regs.serial.data[addr.bits(1,2)].byte(0);
|
||||
}
|
||||
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127: {
|
||||
if(auto data = player.read()) return data().byte(addr.bits(0,1));
|
||||
return regs.serial.data[addr.bits(1,2)].byte(1);
|
||||
}
|
||||
|
||||
//SIOCNT
|
||||
case 0x04000128: return regs.serial.control >> 0;
|
||||
case 0x04000129: return regs.serial.control >> 8;
|
||||
case 0x0400'0128: return (
|
||||
regs.serial.control.shiftclockselect << 0
|
||||
| regs.serial.control.shiftclockfrequency << 1
|
||||
| regs.serial.control.transferenablereceive << 2
|
||||
| regs.serial.control.transferenablesend << 3
|
||||
| regs.serial.control.startbit << 7
|
||||
);
|
||||
case 0x0400'0129: return (
|
||||
regs.serial.control.transferlength << 4
|
||||
| regs.serial.control.irqenable << 6
|
||||
);
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400012a: return regs.serial.data8;
|
||||
case 0x0400012b: return 0u;
|
||||
case 0x0400'012a: return regs.serial.data8;
|
||||
case 0x0400'012b: return 0;
|
||||
|
||||
//KEYINPUT
|
||||
case 0x04000130:
|
||||
case 0x04000130: {
|
||||
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1};
|
||||
if(auto result = player.keyinput()) return result() >> 0;
|
||||
for(unsigned n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, n) << n;
|
||||
if((result & 0xc0) == 0xc0) result &= ~0xc0; //up+down cannot be pressed simultaneously
|
||||
if((result & 0x30) == 0x30) result &= ~0x30; //left+right cannot be pressed simultaneously
|
||||
uint8 result = 0;
|
||||
for(uint n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, lookup[n]) << n;
|
||||
if((result & 0xc0) == 0xc0) result &= (uint8)~0xc0; //up+down cannot be pressed simultaneously
|
||||
if((result & 0x30) == 0x30) result &= (uint8)~0x30; //left+right cannot be pressed simultaneously
|
||||
return result ^ 0xff;
|
||||
case 0x04000131:
|
||||
}
|
||||
case 0x04000131: {
|
||||
if(auto result = player.keyinput()) return result() >> 8;
|
||||
result |= interface->inputPoll(0, 0, 8) << 0;
|
||||
result |= interface->inputPoll(0, 0, 9) << 1;
|
||||
uint8 result = 0;
|
||||
result |= interface->inputPoll(0, 0, 7) << 0;
|
||||
result |= interface->inputPoll(0, 0, 6) << 1;
|
||||
return result ^ 0x03;
|
||||
}
|
||||
|
||||
//KEYCNT
|
||||
case 0x04000132: return regs.keypad.control >> 0;
|
||||
case 0x04000133: return regs.keypad.control >> 8;
|
||||
case 0x0400'0132: return (
|
||||
regs.keypad.control.flag[0] << 0
|
||||
| regs.keypad.control.flag[1] << 1
|
||||
| regs.keypad.control.flag[2] << 2
|
||||
| regs.keypad.control.flag[3] << 3
|
||||
| regs.keypad.control.flag[4] << 4
|
||||
| regs.keypad.control.flag[5] << 5
|
||||
| regs.keypad.control.flag[6] << 6
|
||||
| regs.keypad.control.flag[7] << 7
|
||||
);
|
||||
case 0x0400'0133: return (
|
||||
regs.keypad.control.flag[8] << 0
|
||||
| regs.keypad.control.flag[9] << 1
|
||||
| regs.keypad.control.enable << 6
|
||||
| regs.keypad.control.condition << 7
|
||||
);
|
||||
|
||||
//RCNT
|
||||
case 0x04000134: return regs.joybus.settings >> 0;
|
||||
case 0x04000135: return regs.joybus.settings >> 8;
|
||||
case 0x0400'0134: return (
|
||||
regs.joybus.settings.sc << 0
|
||||
| regs.joybus.settings.sd << 1
|
||||
| regs.joybus.settings.si << 2
|
||||
| regs.joybus.settings.so << 3
|
||||
| regs.joybus.settings.scmode << 4
|
||||
| regs.joybus.settings.sdmode << 5
|
||||
| regs.joybus.settings.simode << 6
|
||||
| regs.joybus.settings.somode << 7
|
||||
);
|
||||
case 0x0400'0135: return (
|
||||
regs.joybus.settings.irqenable << 0
|
||||
| regs.joybus.settings.mode << 6
|
||||
);
|
||||
|
||||
//JOYCNT
|
||||
case 0x04000140: return regs.joybus.control >> 0;
|
||||
case 0x04000141: return regs.joybus.control >> 8;
|
||||
case 0x0400'0140: return (
|
||||
regs.joybus.control.resetsignal << 0
|
||||
| regs.joybus.control.receivecomplete << 1
|
||||
| regs.joybus.control.sendcomplete << 2
|
||||
| regs.joybus.control.irqenable << 6
|
||||
);
|
||||
case 0x0400'0141: return 0;
|
||||
case 0x0400'0142: return 0;
|
||||
case 0x0400'0143: return 0;
|
||||
|
||||
//JOY_RECV_L
|
||||
//JOY_RECV_H
|
||||
case 0x04000150: return regs.joybus.receive >> 0;
|
||||
case 0x04000151: return regs.joybus.receive >> 8;
|
||||
case 0x04000152: return regs.joybus.receive >> 16;
|
||||
case 0x04000153: return regs.joybus.receive >> 24;
|
||||
//JOY_RECV_L, JOY_RECV_H
|
||||
case 0x0400'0150: return regs.joybus.receive.byte(0);
|
||||
case 0x0400'0151: return regs.joybus.receive.byte(1);
|
||||
case 0x0400'0152: return regs.joybus.receive.byte(2);
|
||||
case 0x0400'0153: return regs.joybus.receive.byte(3);
|
||||
|
||||
//JOY_TRANS_L
|
||||
//JOY_TRANS_H
|
||||
case 0x04000154: return regs.joybus.transmit >> 0;
|
||||
case 0x04000155: return regs.joybus.transmit >> 8;
|
||||
case 0x04000156: return regs.joybus.transmit >> 16;
|
||||
case 0x04000157: return regs.joybus.transmit >> 24;
|
||||
//JOY_TRANS_L, JOY_TRANS_H
|
||||
case 0x0400'0154: return regs.joybus.transmit.byte(0);
|
||||
case 0x0400'0155: return regs.joybus.transmit.byte(1);
|
||||
case 0x0400'0156: return regs.joybus.transmit.byte(2);
|
||||
case 0x0400'0157: return regs.joybus.transmit.byte(3);
|
||||
|
||||
//JOYSTAT
|
||||
case 0x04000158: return regs.joybus.status >> 0;
|
||||
case 0x04000159: return regs.joybus.status >> 8;
|
||||
case 0x0400'0158: return (
|
||||
regs.joybus.status.receiveflag << 1
|
||||
| regs.joybus.status.sendflag << 3
|
||||
| regs.joybus.status.generalflag << 4
|
||||
);
|
||||
case 0x0400'0159: return 0;
|
||||
case 0x0400'015a: return 0;
|
||||
case 0x0400'015b: return 0;
|
||||
|
||||
//IE
|
||||
case 0x04000200: return regs.irq.enable >> 0;
|
||||
case 0x04000201: return regs.irq.enable >> 8;
|
||||
case 0x0400'0200: return regs.irq.enable.byte(0);
|
||||
case 0x0400'0201: return regs.irq.enable.byte(1);
|
||||
|
||||
//IF
|
||||
case 0x04000202: return regs.irq.flag >> 0;
|
||||
case 0x04000203: return regs.irq.flag >> 8;
|
||||
case 0x0400'0202: return regs.irq.flag.byte(0);
|
||||
case 0x0400'0203: return regs.irq.flag.byte(1);
|
||||
|
||||
//WAITCNT
|
||||
case 0x04000204: return regs.wait.control >> 0;
|
||||
case 0x04000205: return regs.wait.control >> 8;
|
||||
case 0x0400'0204: return (
|
||||
regs.wait.control.nwait[3] << 0
|
||||
| regs.wait.control.nwait[0] << 2
|
||||
| regs.wait.control.swait[0] << 4
|
||||
| regs.wait.control.nwait[1] << 5
|
||||
| regs.wait.control.swait[1] << 7
|
||||
);
|
||||
case 0x0400'0205: return (
|
||||
regs.wait.control.nwait[2] << 0
|
||||
| regs.wait.control.swait[2] << 2
|
||||
| regs.wait.control.phi << 3
|
||||
| regs.wait.control.prefetch << 6
|
||||
| regs.wait.control.gametype << 7
|
||||
);
|
||||
|
||||
//IME
|
||||
case 0x04000208: return regs.ime;
|
||||
case 0x04000209: return 0u;
|
||||
case 0x0400'0208: return regs.ime;
|
||||
case 0x0400'0209: return 0;
|
||||
|
||||
//POSTFLG + HALTCNT
|
||||
case 0x04000300: return regs.postboot;
|
||||
case 0x04000301: return 0u;
|
||||
case 0x0400'0300: return regs.postboot;
|
||||
case 0x0400'0301: return 0;
|
||||
|
||||
//MEMCNT_L
|
||||
case 0x04000800: return regs.memory.control >> 0;
|
||||
case 0x04000801: return regs.memory.control >> 8;
|
||||
case 0x0400'0800: return (
|
||||
regs.memory.control.disable << 0
|
||||
| regs.memory.control.unknown1 << 1
|
||||
| regs.memory.control.ewram << 5
|
||||
);
|
||||
case 0x0400'0801: return 0;
|
||||
|
||||
//MEMCNT_H
|
||||
case 0x04000802: return regs.memory.control >> 16;
|
||||
case 0x04000803: return regs.memory.control >> 24;
|
||||
case 0x0400'0802: return 0;
|
||||
case 0x0400'0803: return (
|
||||
regs.memory.control.ewramwait << 0
|
||||
| regs.memory.control.unknown2 << 4
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return 0u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto CPU::write(uint32 addr, uint8 byte) -> void {
|
||||
auto CPU::write(uint32 addr, uint8 data) -> void {
|
||||
auto dma = [&]() -> Registers::DMA& { return regs.dma[addr / 12 & 3]; };
|
||||
auto timer = [&]() -> Registers::Timer& { return regs.timer[addr.bits(2,3)]; };
|
||||
|
||||
switch(addr) {
|
||||
|
||||
//DMA0SAD
|
||||
//DMA1SAD
|
||||
//DMA2SAD
|
||||
//DMA3SAD
|
||||
case 0x040000b0: case 0x040000b1: case 0x040000b2: case 0x040000b3:
|
||||
case 0x040000bc: case 0x040000bd: case 0x040000be: case 0x040000bf:
|
||||
case 0x040000c8: case 0x040000c9: case 0x040000ca: case 0x040000cb:
|
||||
case 0x040000d4: case 0x040000d5: case 0x040000d6: case 0x040000d7: {
|
||||
auto& dma = regs.dma[(addr - 0x040000b0) / 12];
|
||||
unsigned shift = (addr & 3) * 8;
|
||||
dma.source = (dma.source & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//DMA0SAD, DMA1SAD, DMA2SAD, DMA3SAD
|
||||
case 0x0400'00b0: case 0x0400'00bc: case 0x0400'00c8: case 0x0400'00d4: dma().source.byte(0) = data; return;
|
||||
case 0x0400'00b1: case 0x0400'00bd: case 0x0400'00c9: case 0x0400'00d5: dma().source.byte(1) = data; return;
|
||||
case 0x0400'00b2: case 0x0400'00be: case 0x0400'00ca: case 0x0400'00d6: dma().source.byte(2) = data; return;
|
||||
case 0x0400'00b3: case 0x0400'00bf: case 0x0400'00cb: case 0x0400'00d7: dma().source.byte(3) = data; return;
|
||||
|
||||
//DMA0DAD
|
||||
//DMA1DAD
|
||||
//DMA2DAD
|
||||
//DMA3DAD
|
||||
case 0x040000b4: case 0x040000b5: case 0x040000b6: case 0x040000b7:
|
||||
case 0x040000c0: case 0x040000c1: case 0x040000c2: case 0x040000c3:
|
||||
case 0x040000cc: case 0x040000cd: case 0x040000ce: case 0x040000cf:
|
||||
case 0x040000d8: case 0x040000d9: case 0x040000da: case 0x040000db: {
|
||||
auto& dma = regs.dma[(addr - 0x040000b4) / 12];
|
||||
unsigned shift = (addr & 3) * 8;
|
||||
dma.target = (dma.target & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//DMA0DAD, DMA1DAD, DMA2DAD, DMA3DAD
|
||||
case 0x0400'00b4: case 0x0400'00c0: case 0x0400'00cc: case 0x0400'00d8: dma().target.byte(0) = data; return;
|
||||
case 0x0400'00b5: case 0x0400'00c1: case 0x0400'00cd: case 0x0400'00d9: dma().target.byte(1) = data; return;
|
||||
case 0x0400'00b6: case 0x0400'00c2: case 0x0400'00ce: case 0x0400'00da: dma().target.byte(2) = data; return;
|
||||
case 0x0400'00b7: case 0x0400'00c3: case 0x0400'00cf: case 0x0400'00db: dma().target.byte(3) = data; return;
|
||||
|
||||
//DMA0CNT_L
|
||||
//DMA1CNT_L
|
||||
//DMA2CNT_L
|
||||
//DMA3CNT_L
|
||||
case 0x040000b8: case 0x040000b9:
|
||||
case 0x040000c4: case 0x040000c5:
|
||||
case 0x040000d0: case 0x040000d1:
|
||||
case 0x040000dc: case 0x040000dd: {
|
||||
auto& dma = regs.dma[(addr - 0x040000b8) / 12];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
dma.length = (dma.length & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//DMA0CNT_L, DMA1CNT_L, DMA2CNT_L, DMA3CNT_L
|
||||
case 0x0400'00b8: case 0x0400'00c4: case 0x0400'00d0: case 0x0400'00dc: dma().length.byte(0) = data; return;
|
||||
case 0x0400'00b9: case 0x0400'00c5: case 0x0400'00d1: case 0x0400'00dd: dma().length.byte(1) = data; return;
|
||||
|
||||
//DMA0CNT_H
|
||||
//DMA1CNT_H
|
||||
//DMA2CNT_H
|
||||
//DMA3CNT_H
|
||||
case 0x040000ba: case 0x040000bb:
|
||||
case 0x040000c6: case 0x040000c7:
|
||||
case 0x040000d2: case 0x040000d3:
|
||||
case 0x040000de: case 0x040000df: {
|
||||
if(addr == 0x040000bb || addr == 0x040000c7 || addr == 0x040000d3) byte &= 0xf7; //gamepak DRQ valid for DMA3 only
|
||||
auto& dma = regs.dma[(addr - 0x040000ba) / 12];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
bool enable = dma.control.enable;
|
||||
dma.control = (dma.control & ~(255 << shift)) | (byte << shift);
|
||||
if(enable == 0 && dma.control.enable) {
|
||||
if(dma.control.timingmode == 0) dma.pending = true; //immediate transfer mode
|
||||
dma.run.target = dma.target;
|
||||
dma.run.source = dma.source;
|
||||
dma.run.length = dma.length;
|
||||
} else if(dma.control.enable == 0) {
|
||||
dma.pending = false;
|
||||
//DMA0CNT_H, DMA1CNT_H, DMA2CNT_H, DMA3CNT_H
|
||||
case 0x0400'00ba: case 0x0400'00c6: case 0x0400'00d2: case 0x0400'00de:
|
||||
dma().control.targetmode = data.bits(5,6);
|
||||
dma().control.sourcemode.bit(0) = data.bit (7);
|
||||
return;
|
||||
case 0x0400'00bb: case 0x0400'00c7: case 0x0400'00d3: case 0x0400'00df: {
|
||||
bool enable = dma().control.enable;
|
||||
if(addr != 0x0400'00df) data.bit(3) = 0; //gamepad DRQ valid for DMA3 only
|
||||
|
||||
dma().control.sourcemode.bit(1) = data.bit (0);
|
||||
dma().control.repeat = data.bit (1);
|
||||
dma().control.size = data.bit (2);
|
||||
dma().control.drq = data.bit (3);
|
||||
dma().control.timingmode = data.bits(4,5);
|
||||
dma().control.irq = data.bit (6);
|
||||
dma().control.enable = data.bit (7);
|
||||
|
||||
if(!enable && dma().control.enable) { //0->1 transition
|
||||
if(dma().control.timingmode == 0) dma().pending = true; //immediate transfer mode
|
||||
dma().run.target = dma().target;
|
||||
dma().run.source = dma().source;
|
||||
dma().run.length = dma().length;
|
||||
} else if(!dma().control.enable) {
|
||||
dma().pending = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//TM0CNT_L
|
||||
//TM1CNT_L
|
||||
//TM2CNT_L
|
||||
//TM3CNT_L
|
||||
case 0x04000100: case 0x04000101:
|
||||
case 0x04000104: case 0x04000105:
|
||||
case 0x04000108: case 0x04000109:
|
||||
case 0x0400010c: case 0x0400010d: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
timer.reload = (timer.reload & ~(255 << shift)) | (byte << shift);
|
||||
return;
|
||||
}
|
||||
//TM0CNT_L, TM1CNT_L, TM2CNT_L, TM3CNT_L
|
||||
case 0x0400'0100: case 0x0400'0104: case 0x0400'0108: case 0x0400'010c: timer().reload.byte(0) = data; return;
|
||||
case 0x0400'0101: case 0x0400'0105: case 0x0400'0109: case 0x0400'010d: timer().reload.byte(1) = data; return;
|
||||
|
||||
//TM0CNT_H
|
||||
//TM1CNT_H
|
||||
//TM2CNT_H
|
||||
//TM3CNT_H
|
||||
case 0x04000102:
|
||||
case 0x04000106:
|
||||
case 0x0400010a:
|
||||
case 0x0400010e: {
|
||||
auto& timer = regs.timer[(addr >> 2) & 3];
|
||||
bool enable = timer.control.enable;
|
||||
timer.control = byte;
|
||||
if(enable == 0 && timer.control.enable == 1) {
|
||||
timer.pending = true;
|
||||
//TM0CNT_H, TM1CNT_H, TM2CNT_H, TM3CNT_H
|
||||
case 0x0400'0102: case 0x0400'0106: case 0x0400'010a: case 0x0400'010e: {
|
||||
bool enable = timer().control.enable;
|
||||
|
||||
timer().control.frequency = data.bits(0,1);
|
||||
timer().control.cascade = data.bit (2);
|
||||
timer().control.irq = data.bit (6);
|
||||
timer().control.enable = data.bit (7);
|
||||
|
||||
if(!enable && timer().control.enable) { //0->1 transition
|
||||
timer().pending = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//SIOMULTI0 (SIODATA32_L)
|
||||
//SIOMULTI1 (SIODATA32_H)
|
||||
//SIOMULTI2
|
||||
//SIOMULTI3
|
||||
case 0x04000120: case 0x04000121:
|
||||
case 0x04000122: case 0x04000123:
|
||||
case 0x04000124: case 0x04000125:
|
||||
case 0x04000126: case 0x04000127: {
|
||||
player.write(byte, addr & 3);
|
||||
auto& data = regs.serial.data[(addr >> 1) & 3];
|
||||
unsigned shift = (addr & 1) * 8;
|
||||
data = (data & ~(255 << shift)) | (byte << shift);
|
||||
case 0x0400'0103: case 0x0400'0107: case 0x0400'010b: case 0x0400'010f:
|
||||
return;
|
||||
|
||||
//SIOMULTI0 (SIODATA32_L), SIOMULTI1 (SIODATA32_H), SIOMULTI2, SIOMULTI3
|
||||
case 0x0400'0120: case 0x0400'0122: case 0x0400'0124: case 0x0400'0126:
|
||||
player.write(addr.bits(0,1), data);
|
||||
regs.serial.data[addr.bits(1,2)].byte(0) = data;
|
||||
return;
|
||||
case 0x0400'0121: case 0x0400'0123: case 0x0400'0125: case 0x0400'0127:
|
||||
player.write(addr.bits(0,1), data);
|
||||
regs.serial.data[addr.bits(1,2)].byte(1) = data;
|
||||
return;
|
||||
}
|
||||
|
||||
//SIOCNT
|
||||
case 0x04000128: regs.serial.control = (regs.serial.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000129: regs.serial.control = (regs.serial.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0128:
|
||||
regs.serial.control.shiftclockselect = data.bit(0);
|
||||
regs.serial.control.shiftclockfrequency = data.bit(1);
|
||||
regs.serial.control.transferenablereceive = data.bit(2);
|
||||
regs.serial.control.transferenablesend = data.bit(3);
|
||||
regs.serial.control.startbit = data.bit(7);
|
||||
return;
|
||||
case 0x0400'0129:
|
||||
regs.serial.control.transferlength = data.bit(4);
|
||||
regs.serial.control.irqenable = data.bit(6);
|
||||
return;
|
||||
|
||||
//SIOMLT_SEND (SIODATA8)
|
||||
case 0x0400012a: regs.serial.data8 = byte; return;
|
||||
case 0x0400012b: return;
|
||||
case 0x0400'012a: regs.serial.data8 = data; return;
|
||||
case 0x0400'012b: return;
|
||||
|
||||
//KEYCNT
|
||||
case 0x04000132: regs.keypad.control = (regs.keypad.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000133: regs.keypad.control = (regs.keypad.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0132:
|
||||
regs.keypad.control.flag[0] = data.bit(0);
|
||||
regs.keypad.control.flag[1] = data.bit(1);
|
||||
regs.keypad.control.flag[2] = data.bit(2);
|
||||
regs.keypad.control.flag[3] = data.bit(3);
|
||||
regs.keypad.control.flag[4] = data.bit(4);
|
||||
regs.keypad.control.flag[5] = data.bit(5);
|
||||
regs.keypad.control.flag[6] = data.bit(6);
|
||||
regs.keypad.control.flag[7] = data.bit(7);
|
||||
return;
|
||||
case 0x0400'0133:
|
||||
regs.keypad.control.flag[8] = data.bit(0);
|
||||
regs.keypad.control.flag[9] = data.bit(1);
|
||||
regs.keypad.control.enable = data.bit(6);
|
||||
regs.keypad.control.condition = data.bit(7);
|
||||
return;
|
||||
|
||||
//RCNT
|
||||
case 0x04000134: regs.joybus.settings = (regs.joybus.settings & 0xff00) | (byte << 0); return;
|
||||
case 0x04000135: regs.joybus.settings = (regs.joybus.settings & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0134:
|
||||
regs.joybus.settings.sc = data.bit(0);
|
||||
regs.joybus.settings.sd = data.bit(1);
|
||||
regs.joybus.settings.si = data.bit(2);
|
||||
regs.joybus.settings.so = data.bit(3);
|
||||
regs.joybus.settings.scmode = data.bit(4);
|
||||
regs.joybus.settings.sdmode = data.bit(5);
|
||||
regs.joybus.settings.simode = data.bit(6);
|
||||
regs.joybus.settings.somode = data.bit(7);
|
||||
return;
|
||||
case 0x0400'0135:
|
||||
regs.joybus.settings.irqenable = data.bit (0);
|
||||
regs.joybus.settings.mode = data.bits(6,7);
|
||||
return;
|
||||
|
||||
//JOYCNT
|
||||
case 0x04000140: regs.joybus.control = (regs.joybus.control & 0xff00) | (byte << 0); return;
|
||||
case 0x04000141: regs.joybus.control = (regs.joybus.control & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0140:
|
||||
regs.joybus.control.resetsignal = data.bit(0);
|
||||
regs.joybus.control.receivecomplete = data.bit(1);
|
||||
regs.joybus.control.sendcomplete = data.bit(2);
|
||||
regs.joybus.control.irqenable = data.bit(6);
|
||||
return;
|
||||
case 0x0400'0141: return;
|
||||
case 0x0400'0142: return;
|
||||
case 0x0400'0143: return;
|
||||
|
||||
//JOY_RECV_L
|
||||
//JOY_RECV_H
|
||||
case 0x04000150: regs.joybus.receive = (regs.joybus.receive & 0xffffff00) | (byte << 0); return;
|
||||
case 0x04000151: regs.joybus.receive = (regs.joybus.receive & 0xffff00ff) | (byte << 8); return;
|
||||
case 0x04000152: regs.joybus.receive = (regs.joybus.receive & 0xff00ffff) | (byte << 16); return;
|
||||
case 0x04000153: regs.joybus.receive = (regs.joybus.receive & 0x00ffffff) | (byte << 24); return;
|
||||
case 0x0400'0150: regs.joybus.receive.byte(0) = data; return;
|
||||
case 0x0400'0151: regs.joybus.receive.byte(1) = data; return;
|
||||
case 0x0400'0152: regs.joybus.receive.byte(2) = data; return;
|
||||
case 0x0400'0153: regs.joybus.receive.byte(3) = data; return;
|
||||
|
||||
//JOY_TRANS_L
|
||||
//JOY_TRANS_H
|
||||
case 0x04000154: regs.joybus.transmit = (regs.joybus.transmit & 0xffffff00) | (byte << 0); return;
|
||||
case 0x04000155: regs.joybus.transmit = (regs.joybus.transmit & 0xffff00ff) | (byte << 8); return;
|
||||
case 0x04000156: regs.joybus.transmit = (regs.joybus.transmit & 0xff00ffff) | (byte << 16); return;
|
||||
case 0x04000157: regs.joybus.transmit = (regs.joybus.transmit & 0x00ffffff) | (byte << 24); return;
|
||||
case 0x0400'0154: regs.joybus.transmit.byte(0) = data; return;
|
||||
case 0x0400'0155: regs.joybus.transmit.byte(1) = data; return;
|
||||
case 0x0400'0156: regs.joybus.transmit.byte(2) = data; return;
|
||||
case 0x0400'0157: regs.joybus.transmit.byte(3) = data; return;
|
||||
|
||||
//JOYSTAT
|
||||
case 0x04000158: regs.joybus.status = (regs.joybus.status & 0xff00) | (byte << 0); return;
|
||||
case 0x04000159: regs.joybus.status = (regs.joybus.status & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0158:
|
||||
regs.joybus.status.receiveflag = data.bit (1);
|
||||
regs.joybus.status.sendflag = data.bit (3);
|
||||
regs.joybus.status.generalflag = data.bits(4,5);
|
||||
return;
|
||||
case 0x0400'0159: return;
|
||||
|
||||
//IE
|
||||
case 0x04000200: regs.irq.enable = (regs.irq.enable & 0xff00) | (byte << 0); return;
|
||||
case 0x04000201: regs.irq.enable = (regs.irq.enable & 0x00ff) | (byte << 8); return;
|
||||
case 0x0400'0200: regs.irq.enable.byte(0) = data; return;
|
||||
case 0x0400'0201: regs.irq.enable.byte(1) = data; return;
|
||||
|
||||
//IF
|
||||
case 0x04000202: regs.irq.flag = regs.irq.flag & ~(byte << 0); return;
|
||||
case 0x04000203: regs.irq.flag = regs.irq.flag & ~(byte << 8); return;
|
||||
case 0x0400'0202: regs.irq.flag.byte(0) = regs.irq.flag.byte(0) & ~data; return;
|
||||
case 0x0400'0203: regs.irq.flag.byte(1) = regs.irq.flag.byte(1) & ~data; return;
|
||||
|
||||
//WAITCNT
|
||||
case 0x04000204: regs.wait.control = (regs.wait.control & 0xff00) | ((byte & 0xff) << 0); return;
|
||||
case 0x04000205: regs.wait.control = (regs.wait.control & 0x00ff) | ((byte & 0x7f) << 8); return;
|
||||
case 0x0400'0204:
|
||||
regs.wait.control.swait[3] = data.bit (0); //todo: is this correct?
|
||||
regs.wait.control.nwait[3] = data.bits(0,1);
|
||||
regs.wait.control.nwait[0] = data.bits(2,3);
|
||||
regs.wait.control.swait[0] = data.bit (4);
|
||||
regs.wait.control.nwait[1] = data.bits(5,6);
|
||||
regs.wait.control.swait[1] = data.bit (7);
|
||||
return;
|
||||
case 0x0400'0205:
|
||||
regs.wait.control.nwait[2] = data.bits(0,1);
|
||||
regs.wait.control.swait[2] = data.bit (2);
|
||||
regs.wait.control.phi = data.bit (3);
|
||||
regs.wait.control.prefetch = data.bit (6);
|
||||
regs.wait.control.gametype = data.bit (7);
|
||||
return;
|
||||
|
||||
//IME
|
||||
case 0x04000208: regs.ime = byte >> 0; return;
|
||||
case 0x04000209: return;
|
||||
case 0x0400'0208: regs.ime = data.bit(0); return;
|
||||
case 0x0400'0209: return;
|
||||
|
||||
//POSTFLG, HALTCNT
|
||||
case 0x04000300: regs.postboot |= byte >> 0; return;
|
||||
case 0x04000301: regs.mode = byte & 0x80 ? Registers::Mode::Stop : Registers::Mode::Halt; return;
|
||||
case 0x0400'0300:
|
||||
if(data.bit(0)) regs.postboot = 1;
|
||||
return;
|
||||
case 0x0400'0301:
|
||||
regs.mode = data.bit(7) ? Registers::Mode::Stop : Registers::Mode::Halt;
|
||||
return;
|
||||
|
||||
//MEMCNT_L
|
||||
//MEMCNT_H
|
||||
case 0x04000800: regs.memory.control = (regs.memory.control & 0xffffff00) | (byte << 0); return;
|
||||
case 0x04000801: regs.memory.control = (regs.memory.control & 0xffff00ff) | (byte << 8); return;
|
||||
case 0x04000802: regs.memory.control = (regs.memory.control & 0xff00ffff) | (byte << 16); return;
|
||||
case 0x04000803: regs.memory.control = (regs.memory.control & 0x00ffffff) | (byte << 24); return;
|
||||
case 0x0400'0800:
|
||||
regs.memory.control.disable = data.bit (0);
|
||||
regs.memory.control.unknown1 = data.bits(1,3);
|
||||
regs.memory.control.ewram = data.bit (5);
|
||||
return;
|
||||
case 0x0400'0801: return;
|
||||
case 0x0400'0802: return;
|
||||
case 0x0400'0803:
|
||||
regs.memory.control.ewramwait = data.bits(0,3);
|
||||
regs.memory.control.unknown2 = data.bits(4,7);
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ auto CPU::prefetch_sync(uint32 addr) -> void {
|
||||
|
||||
prefetch.addr = addr;
|
||||
prefetch.load = addr;
|
||||
prefetch.wait = bus_wait(Half | Nonsequential, prefetch.load);
|
||||
prefetch.wait = busWait(Half | Nonsequential, prefetch.load);
|
||||
}
|
||||
|
||||
auto CPU::prefetch_step(uint clocks) -> void {
|
||||
@@ -14,7 +14,7 @@ auto CPU::prefetch_step(uint clocks) -> void {
|
||||
if(--prefetch.wait) continue;
|
||||
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.read(Half, prefetch.load);
|
||||
prefetch.load += 2;
|
||||
prefetch.wait = bus_wait(Half | Sequential, prefetch.load);
|
||||
prefetch.wait = busWait(Half | Sequential, prefetch.load);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,14 @@ auto CPU::prefetch_wait() -> void {
|
||||
if(!regs.wait.control.prefetch || active.dma || prefetch.full()) return;
|
||||
|
||||
prefetch_step(prefetch.wait);
|
||||
prefetch.wait = bus_wait(Half | Nonsequential, prefetch.load);
|
||||
prefetch.wait = busWait(Half | Nonsequential, prefetch.load);
|
||||
}
|
||||
|
||||
auto CPU::prefetch_read() -> uint16 {
|
||||
if(prefetch.empty()) prefetch_step(prefetch.wait);
|
||||
else prefetch_step(1);
|
||||
|
||||
if(prefetch.full()) prefetch.wait = bus_wait(Half | Sequential, prefetch.load);
|
||||
if(prefetch.full()) prefetch.wait = busWait(Half | Sequential, prefetch.load);
|
||||
|
||||
uint16 half = prefetch.slot[prefetch.addr >> 1 & 7];
|
||||
prefetch.addr += 2;
|
||||
|
@@ -1,244 +0,0 @@
|
||||
CPU::Registers::DMAControl::operator uint16() const {
|
||||
return (
|
||||
(targetmode << 5)
|
||||
| (sourcemode << 7)
|
||||
| (repeat << 9)
|
||||
| (size << 10)
|
||||
| (drq << 11)
|
||||
| (timingmode << 12)
|
||||
| (irq << 14)
|
||||
| (enable << 15)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::DMAControl::operator=(uint16 source) -> uint16 {
|
||||
targetmode = source >> 5;
|
||||
sourcemode = source >> 7;
|
||||
repeat = source >> 9;
|
||||
size = source >> 10;
|
||||
drq = source >> 11;
|
||||
timingmode = source >> 12;
|
||||
irq = source >> 14;
|
||||
enable = source >> 15;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::TimerControl::operator uint16() const {
|
||||
return (
|
||||
(frequency << 0)
|
||||
| (cascade << 2)
|
||||
| (irq << 6)
|
||||
| (enable << 7)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::TimerControl::operator=(uint16 source) -> uint16 {
|
||||
frequency = source >> 0;
|
||||
cascade = source >> 2;
|
||||
irq = source >> 6;
|
||||
enable = source >> 7;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::SerialControl::operator uint16() const {
|
||||
return (
|
||||
(shiftclockselect << 0)
|
||||
| (shiftclockfrequency << 1)
|
||||
| (transferenablereceive << 2)
|
||||
| (transferenablesend << 3)
|
||||
| (startbit << 7)
|
||||
| (transferlength << 12)
|
||||
| (irqenable << 14)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::SerialControl::operator=(uint16 source) -> uint16 {
|
||||
shiftclockselect = source >> 0;
|
||||
shiftclockfrequency = source >> 1;
|
||||
transferenablereceive = source >> 2;
|
||||
transferenablesend = source >> 3;
|
||||
startbit = source >> 7;
|
||||
transferlength = source >> 12;
|
||||
irqenable = source >> 14;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::KeypadControl::operator uint16() const {
|
||||
return (
|
||||
(flag[0] << 0)
|
||||
| (flag[1] << 1)
|
||||
| (flag[2] << 2)
|
||||
| (flag[3] << 3)
|
||||
| (flag[4] << 4)
|
||||
| (flag[5] << 5)
|
||||
| (flag[6] << 6)
|
||||
| (flag[7] << 7)
|
||||
| (flag[8] << 8)
|
||||
| (flag[9] << 9)
|
||||
| (enable << 14)
|
||||
| (condition << 15)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::KeypadControl::operator=(uint16 source) -> uint16 {
|
||||
flag[0] = source >> 0;
|
||||
flag[1] = source >> 1;
|
||||
flag[2] = source >> 2;
|
||||
flag[3] = source >> 3;
|
||||
flag[4] = source >> 4;
|
||||
flag[5] = source >> 5;
|
||||
flag[6] = source >> 6;
|
||||
flag[7] = source >> 7;
|
||||
flag[8] = source >> 8;
|
||||
flag[9] = source >> 9;
|
||||
enable = source >> 14;
|
||||
condition = source >> 15;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::JoybusSettings::operator uint16() const {
|
||||
return (
|
||||
(sc << 0)
|
||||
| (sd << 1)
|
||||
| (si << 2)
|
||||
| (so << 3)
|
||||
| (scmode << 4)
|
||||
| (sdmode << 5)
|
||||
| (simode << 6)
|
||||
| (somode << 7)
|
||||
| (irqenable << 8)
|
||||
| (mode << 14)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::JoybusSettings::operator=(uint16 source) -> uint16 {
|
||||
sc = source >> 0;
|
||||
sd = source >> 1;
|
||||
si = source >> 2;
|
||||
so = source >> 3;
|
||||
scmode = source >> 4;
|
||||
sdmode = source >> 5;
|
||||
simode = source >> 6;
|
||||
somode = source >> 7;
|
||||
irqenable = source >> 8;
|
||||
mode = source >> 14;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::JoybusControl::operator uint16() const {
|
||||
return (
|
||||
(resetsignal << 0)
|
||||
| (receivecomplete << 1)
|
||||
| (sendcomplete << 2)
|
||||
| (irqenable << 6)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::JoybusControl::operator=(uint16 source) -> uint16 {
|
||||
resetsignal = source >> 0;
|
||||
receivecomplete = source >> 1;
|
||||
sendcomplete = source >> 2;
|
||||
irqenable = source >> 6;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::JoybusStatus::operator uint16() const {
|
||||
return (
|
||||
(receiveflag << 1)
|
||||
| (sendflag << 3)
|
||||
| (generalflag << 4)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::JoybusStatus::operator=(uint16 source) -> uint16 {
|
||||
receiveflag = source >> 1;
|
||||
sendflag = source >> 3;
|
||||
generalflag = source >> 4;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::Interrupt::operator uint16() const {
|
||||
return (
|
||||
(vblank << 0)
|
||||
| (hblank << 1)
|
||||
| (vcoincidence << 2)
|
||||
| (timer[0] << 3)
|
||||
| (timer[1] << 4)
|
||||
| (timer[2] << 5)
|
||||
| (timer[3] << 6)
|
||||
| (serial << 7)
|
||||
| (dma[0] << 8)
|
||||
| (dma[1] << 9)
|
||||
| (dma[2] << 10)
|
||||
| (dma[3] << 11)
|
||||
| (keypad << 12)
|
||||
| (cartridge << 13)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::Interrupt::operator=(uint16 source) -> uint16 {
|
||||
vblank = source >> 0;
|
||||
hblank = source >> 1;
|
||||
vcoincidence = source >> 2;
|
||||
timer[0] = source >> 3;
|
||||
timer[1] = source >> 4;
|
||||
timer[2] = source >> 5;
|
||||
timer[3] = source >> 6;
|
||||
serial = source >> 7;
|
||||
dma[0] = source >> 8;
|
||||
dma[1] = source >> 9;
|
||||
dma[2] = source >> 10;
|
||||
dma[3] = source >> 11;
|
||||
keypad = source >> 12;
|
||||
cartridge = source >> 13;
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::WaitControl::operator uint16() const {
|
||||
return (
|
||||
(nwait[3] << 0)
|
||||
| (nwait[0] << 2)
|
||||
| (swait[0] << 4)
|
||||
| (nwait[1] << 5)
|
||||
| (swait[1] << 7)
|
||||
| (nwait[2] << 8)
|
||||
| (swait[2] << 10)
|
||||
| (phi << 11)
|
||||
| (prefetch << 14)
|
||||
| (gametype << 15)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::WaitControl::operator=(uint16 source) -> uint16 {
|
||||
nwait[3] = (source >> 0) & 3;
|
||||
nwait[0] = (source >> 2) & 3;
|
||||
swait[0] = (source >> 4) & 1;
|
||||
nwait[1] = (source >> 5) & 3;
|
||||
swait[1] = (source >> 7) & 1;
|
||||
nwait[2] = (source >> 8) & 3;
|
||||
swait[2] = (source >> 10) & 1;
|
||||
phi = (source >> 11) & 3;
|
||||
prefetch = (source >> 14) & 1;
|
||||
gametype = (source >> 15) & 1;
|
||||
swait[3] = nwait[3];
|
||||
return operator uint16();
|
||||
}
|
||||
|
||||
CPU::Registers::MemoryControl::operator uint32() const {
|
||||
return (
|
||||
(disable << 0)
|
||||
| (unknown1 << 1)
|
||||
| (ewram << 5)
|
||||
| (ewramwait << 24)
|
||||
| (unknown2 << 28)
|
||||
);
|
||||
}
|
||||
|
||||
auto CPU::Registers::MemoryControl::operator=(uint32 source) -> uint32 {
|
||||
disable = source >> 0;
|
||||
unknown1 = source >> 1;
|
||||
ewram = source >> 5;
|
||||
ewramwait = source >> 24;
|
||||
unknown2 = source >> 28;
|
||||
return operator uint32();
|
||||
}
|
@@ -1,185 +1,116 @@
|
||||
struct Registers {
|
||||
struct DMAControl {
|
||||
uint2 targetmode;
|
||||
uint2 sourcemode;
|
||||
uint1 repeat;
|
||||
uint1 size;
|
||||
uint1 drq;
|
||||
uint2 timingmode;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const DMAControl&) -> DMAControl& = delete;
|
||||
};
|
||||
|
||||
struct DMA {
|
||||
varuint source;
|
||||
varuint target;
|
||||
varuint length;
|
||||
VariadicNatural source;
|
||||
VariadicNatural target;
|
||||
VariadicNatural length;
|
||||
uint32 data;
|
||||
DMAControl control;
|
||||
struct Control {
|
||||
uint2 targetmode;
|
||||
uint2 sourcemode;
|
||||
uint1 repeat;
|
||||
uint1 size;
|
||||
uint1 drq;
|
||||
uint2 timingmode;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
} control;
|
||||
|
||||
//internal
|
||||
bool pending;
|
||||
struct Run {
|
||||
varuint target;
|
||||
varuint source;
|
||||
varuint length;
|
||||
VariadicNatural target;
|
||||
VariadicNatural source;
|
||||
VariadicNatural length;
|
||||
} run;
|
||||
} dma[4];
|
||||
|
||||
struct TimerControl {
|
||||
uint2 frequency;
|
||||
uint1 cascade;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const TimerControl&) -> TimerControl& = delete;
|
||||
};
|
||||
|
||||
struct Timer {
|
||||
uint16 period;
|
||||
uint16 reload;
|
||||
bool pending;
|
||||
TimerControl control;
|
||||
struct Control {
|
||||
uint2 frequency;
|
||||
uint1 cascade;
|
||||
uint1 irq;
|
||||
uint1 enable;
|
||||
} control;
|
||||
} timer[4];
|
||||
|
||||
struct SerialControl {
|
||||
uint1 shiftclockselect;
|
||||
uint1 shiftclockfrequency;
|
||||
uint1 transferenablereceive;
|
||||
uint1 transferenablesend;
|
||||
uint1 startbit;
|
||||
uint1 transferlength;
|
||||
uint1 irqenable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const SerialControl&) -> SerialControl& = delete;
|
||||
};
|
||||
|
||||
struct Serial {
|
||||
uint16 data[4];
|
||||
SerialControl control;
|
||||
struct Control {
|
||||
uint1 shiftclockselect;
|
||||
uint1 shiftclockfrequency;
|
||||
uint1 transferenablereceive;
|
||||
uint1 transferenablesend;
|
||||
uint1 startbit;
|
||||
uint1 transferlength;
|
||||
uint1 irqenable;
|
||||
} control;
|
||||
uint8 data8;
|
||||
} serial;
|
||||
|
||||
struct KeypadControl {
|
||||
uint1 flag[10];
|
||||
uint1 enable;
|
||||
uint1 condition;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const KeypadControl&) -> KeypadControl& = delete;
|
||||
};
|
||||
|
||||
struct Keypad {
|
||||
KeypadControl control;
|
||||
struct Control {
|
||||
uint1 flag[10];
|
||||
uint1 enable;
|
||||
uint1 condition;
|
||||
} control;
|
||||
} keypad;
|
||||
|
||||
struct JoybusSettings {
|
||||
uint1 sc;
|
||||
uint1 sd;
|
||||
uint1 si;
|
||||
uint1 so;
|
||||
uint1 scmode;
|
||||
uint1 sdmode;
|
||||
uint1 simode;
|
||||
uint1 somode;
|
||||
uint1 irqenable;
|
||||
uint2 mode;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const JoybusSettings&) -> JoybusSettings& = delete;
|
||||
};
|
||||
|
||||
struct JoybusControl {
|
||||
uint1 resetsignal;
|
||||
uint1 receivecomplete;
|
||||
uint1 sendcomplete;
|
||||
uint1 irqenable;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const JoybusControl&) -> JoybusControl& = delete;
|
||||
};
|
||||
|
||||
struct JoybusStatus {
|
||||
uint1 receiveflag;
|
||||
uint1 sendflag;
|
||||
uint2 generalflag;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const JoybusStatus&) -> JoybusStatus& = delete;
|
||||
};
|
||||
|
||||
struct Joybus {
|
||||
JoybusSettings settings;
|
||||
JoybusControl control;
|
||||
struct Settings {
|
||||
uint1 sc;
|
||||
uint1 sd;
|
||||
uint1 si;
|
||||
uint1 so;
|
||||
uint1 scmode;
|
||||
uint1 sdmode;
|
||||
uint1 simode;
|
||||
uint1 somode;
|
||||
uint1 irqenable;
|
||||
uint2 mode;
|
||||
} settings;
|
||||
struct Control {
|
||||
uint1 resetsignal;
|
||||
uint1 receivecomplete;
|
||||
uint1 sendcomplete;
|
||||
uint1 irqenable;
|
||||
} control;
|
||||
uint32 receive;
|
||||
uint32 transmit;
|
||||
JoybusStatus status;
|
||||
struct Status {
|
||||
uint1 receiveflag;
|
||||
uint1 sendflag;
|
||||
uint2 generalflag;
|
||||
} status;
|
||||
} joybus;
|
||||
|
||||
uint1 ime;
|
||||
|
||||
struct Interrupt {
|
||||
uint1 vblank;
|
||||
uint1 hblank;
|
||||
uint1 vcoincidence;
|
||||
uint1 timer[4];
|
||||
uint1 serial;
|
||||
uint1 dma[4];
|
||||
uint1 keypad;
|
||||
uint1 cartridge;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const Interrupt&) -> Interrupt& = delete;
|
||||
};
|
||||
|
||||
struct IRQ {
|
||||
Interrupt enable;
|
||||
Interrupt flag;
|
||||
uint16 enable;
|
||||
uint16 flag;
|
||||
} irq;
|
||||
|
||||
struct WaitControl {
|
||||
uint2 nwait[4];
|
||||
uint1 swait[4];
|
||||
uint2 phi;
|
||||
uint1 prefetch;
|
||||
uint1 gametype;
|
||||
|
||||
operator uint16() const;
|
||||
auto operator=(uint16 source) -> uint16;
|
||||
auto operator=(const WaitControl&) -> WaitControl& = delete;
|
||||
};
|
||||
|
||||
struct Wait {
|
||||
WaitControl control;
|
||||
struct Control {
|
||||
uint2 nwait[4];
|
||||
uint1 swait[4];
|
||||
uint2 phi;
|
||||
uint1 prefetch;
|
||||
uint1 gametype;
|
||||
} control;
|
||||
} wait;
|
||||
|
||||
struct MemoryControl {
|
||||
uint1 disable;
|
||||
uint3 unknown1;
|
||||
uint1 ewram;
|
||||
uint4 ewramwait;
|
||||
uint4 unknown2;
|
||||
|
||||
operator uint32() const;
|
||||
auto operator=(uint32 source) -> uint32;
|
||||
auto operator=(const MemoryControl&) -> MemoryControl& = delete;
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
MemoryControl control;
|
||||
struct Control {
|
||||
uint1 disable;
|
||||
uint3 unknown1;
|
||||
uint1 ewram;
|
||||
uint4 ewramwait;
|
||||
uint4 unknown2;
|
||||
} control;
|
||||
} memory;
|
||||
|
||||
uint1 postboot;
|
||||
|
@@ -72,23 +72,8 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
|
||||
s.integer(regs.ime);
|
||||
|
||||
s.integer(regs.irq.enable.vblank);
|
||||
s.integer(regs.irq.enable.hblank);
|
||||
s.integer(regs.irq.enable.vcoincidence);
|
||||
for(auto& flag : regs.irq.enable.timer) s.integer(flag);
|
||||
s.integer(regs.irq.enable.serial);
|
||||
for(auto& flag : regs.irq.enable.dma) s.integer(flag);
|
||||
s.integer(regs.irq.enable.keypad);
|
||||
s.integer(regs.irq.enable.cartridge);
|
||||
|
||||
s.integer(regs.irq.flag.vblank);
|
||||
s.integer(regs.irq.flag.hblank);
|
||||
s.integer(regs.irq.flag.vcoincidence);
|
||||
for(auto& flag : regs.irq.flag.timer) s.integer(flag);
|
||||
s.integer(regs.irq.flag.serial);
|
||||
for(auto& flag : regs.irq.flag.dma) s.integer(flag);
|
||||
s.integer(regs.irq.flag.keypad);
|
||||
s.integer(regs.irq.flag.cartridge);
|
||||
s.integer(regs.irq.enable);
|
||||
s.integer(regs.irq.flag);
|
||||
|
||||
for(auto& flag : regs.wait.control.nwait) s.integer(flag);
|
||||
for(auto& flag : regs.wait.control.swait) s.integer(flag);
|
||||
|
@@ -28,7 +28,7 @@ auto CPU::timer_increment(uint n) -> void {
|
||||
if(++timer.period == 0) {
|
||||
timer.period = timer.reload;
|
||||
|
||||
if(timer.control.irq) regs.irq.flag.timer[n] = 1;
|
||||
if(timer.control.irq) regs.irq.flag |= Interrupt::Timer0 << n;
|
||||
|
||||
if(apu.fifo[0].timer == n) timer_fifo_run(0);
|
||||
if(apu.fifo[1].timer == n) timer_fifo_run(1);
|
||||
|
@@ -1,22 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
//license: GPLv3
|
||||
//started: 2012-03-19
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <processor/arm/arm.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
namespace Info {
|
||||
static const string Name = "bgba";
|
||||
static const uint SerializerVersion = 3;
|
||||
static const uint SerializerVersion = 4;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bgba - Game Boy Advance emulator
|
||||
authors: byuu, Cydrak
|
||||
license: GPLv3
|
||||
project started: 2012-03-19
|
||||
*/
|
||||
|
||||
#include <libco/libco.h>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
@@ -39,7 +34,7 @@ namespace GameBoyAdvance {
|
||||
|
||||
auto create(auto (*entrypoint)() -> void, uint frequency) -> void {
|
||||
if(thread) co_delete(thread);
|
||||
thread = co_create(65536 * sizeof(void*), entrypoint);
|
||||
thread = co_create(65'536 * sizeof(void*), entrypoint);
|
||||
this->frequency = frequency;
|
||||
clock = 0;
|
||||
}
|
||||
@@ -62,7 +57,6 @@ namespace GameBoyAdvance {
|
||||
#include <gba/cpu/cpu.hpp>
|
||||
#include <gba/ppu/ppu.hpp>
|
||||
#include <gba/apu/apu.hpp>
|
||||
#include <gba/video/video.hpp>
|
||||
}
|
||||
|
||||
#include <gba/interface/interface.hpp>
|
||||
|
@@ -8,34 +8,35 @@ Settings settings;
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = false;
|
||||
|
||||
media.append({ID::GameBoyAdvance, "Game Boy Advance", "gba", true});
|
||||
|
||||
{ Device device{0, ID::Device, "Controller"};
|
||||
device.input.append({ 0, 0, "A" });
|
||||
device.input.append({ 1, 0, "B" });
|
||||
device.input.append({ 2, 0, "Select"});
|
||||
device.input.append({ 3, 0, "Start" });
|
||||
device.input.append({ 4, 0, "Right" });
|
||||
device.input.append({ 5, 0, "Left" });
|
||||
device.input.append({ 6, 0, "Up" });
|
||||
device.input.append({ 7, 0, "Down" });
|
||||
device.input.append({ 8, 0, "R" });
|
||||
device.input.append({ 9, 0, "L" });
|
||||
device.input.append({10, 2, "Rumble"});
|
||||
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3, 10};
|
||||
this->device.append(device);
|
||||
device.inputs.append({ 0, 0, "Up" });
|
||||
device.inputs.append({ 1, 0, "Down" });
|
||||
device.inputs.append({ 2, 0, "Left" });
|
||||
device.inputs.append({ 3, 0, "Right" });
|
||||
device.inputs.append({ 4, 0, "B" });
|
||||
device.inputs.append({ 5, 0, "A" });
|
||||
device.inputs.append({ 6, 0, "L" });
|
||||
device.inputs.append({ 7, 0, "R" });
|
||||
device.inputs.append({ 8, 0, "Select"});
|
||||
device.inputs.append({ 9, 0, "Start" });
|
||||
device.inputs.append({10, 2, "Rumble"});
|
||||
devices.append(device);
|
||||
}
|
||||
|
||||
port.append({0, "Device", {device[0]}});
|
||||
ports.append({0, "Device", {devices[0]}});
|
||||
}
|
||||
|
||||
auto Interface::manifest() -> string {
|
||||
@@ -50,12 +51,38 @@ auto Interface::videoFrequency() -> double {
|
||||
return 16777216.0 / (228.0 * 1232.0);
|
||||
}
|
||||
|
||||
auto Interface::videoColors() -> uint32 {
|
||||
return 1 << 15;
|
||||
}
|
||||
|
||||
auto Interface::videoColor(uint32 color) -> uint64 {
|
||||
uint R = color.bits( 0, 4);
|
||||
uint G = color.bits( 5, 9);
|
||||
uint B = color.bits(10,14);
|
||||
|
||||
uint64 r = image::normalize(R, 5, 16);
|
||||
uint64 g = image::normalize(G, 5, 16);
|
||||
uint64 b = image::normalize(B, 5, 16);
|
||||
|
||||
if(settings.colorEmulation) {
|
||||
double lcdGamma = 4.0, outGamma = 2.2;
|
||||
double lb = pow(B / 31.0, lcdGamma);
|
||||
double lg = pow(G / 31.0, lcdGamma);
|
||||
double lr = pow(R / 31.0, lcdGamma);
|
||||
r = pow(( 0 * lb + 50 * lg + 255 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
g = pow(( 30 * lb + 230 * lg + 10 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
b = pow((220 * lb + 10 * lg + 50 * lr) / 255, 1 / outGamma) * (0xffff * 255 / 280);
|
||||
}
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
}
|
||||
|
||||
auto Interface::audioFrequency() -> double {
|
||||
return 16777216.0 / 512.0;
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return cartridge.loaded();
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::group(uint id) -> uint {
|
||||
@@ -75,7 +102,7 @@ auto Interface::group(uint id) -> uint {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> void {
|
||||
cartridge.load();
|
||||
system.load();
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -90,7 +117,7 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::BIOS) {
|
||||
stream.read(bios.data, min(bios.size, stream.size()));
|
||||
stream.read((uint8_t*)bios.data, min(bios.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::Manifest) {
|
||||
@@ -98,39 +125,39 @@ auto Interface::load(uint id, const stream& stream) -> void {
|
||||
}
|
||||
|
||||
if(id == ID::MROM) {
|
||||
stream.read(cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.mrom.data, min(cartridge.mrom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::SRAM) {
|
||||
stream.read(cartridge.sram.data, min(cartridge.sram.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.sram.data, min(cartridge.sram.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::EEPROM) {
|
||||
stream.read(cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.eeprom.data, min(cartridge.eeprom.size, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::FLASH) {
|
||||
stream.read(cartridge.flash.data, min(cartridge.flash.size, stream.size()));
|
||||
stream.read((uint8_t*)cartridge.flash.data, min(cartridge.flash.size, stream.size()));
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::save(uint id, const stream& stream) -> void {
|
||||
if(id == ID::SRAM) {
|
||||
stream.write(cartridge.sram.data, cartridge.sram.size);
|
||||
stream.write((uint8_t*)cartridge.sram.data, cartridge.sram.size);
|
||||
}
|
||||
|
||||
if(id == ID::EEPROM) {
|
||||
stream.write(cartridge.eeprom.data, cartridge.eeprom.size);
|
||||
stream.write((uint8_t*)cartridge.eeprom.data, cartridge.eeprom.size);
|
||||
}
|
||||
|
||||
if(id == ID::FLASH) {
|
||||
stream.write(cartridge.flash.data, cartridge.flash.size);
|
||||
stream.write((uint8_t*)cartridge.flash.data, cartridge.flash.size);
|
||||
}
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
cartridge.unload();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
@@ -146,7 +173,7 @@ auto Interface::run() -> void {
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
system.runtosave();
|
||||
system.runToSave();
|
||||
return system.serialize();
|
||||
}
|
||||
|
||||
@@ -167,8 +194,18 @@ auto Interface::get(const string& name) -> any {
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Blur Emulation" && value.is<bool>()) return settings.blurEmulation = value.get<bool>(), true;
|
||||
if(name == "Color Emulation" && value.is<bool>()) return settings.colorEmulation = value.get<bool>(), true;
|
||||
if(name == "Blur Emulation" && value.is<bool>()) {
|
||||
settings.blurEmulation = value.get<bool>();
|
||||
system.configureVideoEffects();
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
system.configureVideoPalette();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -28,6 +28,8 @@ struct Interface : Emulator::Interface {
|
||||
auto manifest() -> string;
|
||||
auto title() -> string;
|
||||
auto videoFrequency() -> double;
|
||||
auto videoColors() -> uint32;
|
||||
auto videoColor(uint32 color) -> uint64;
|
||||
auto audioFrequency() -> double;
|
||||
|
||||
auto loaded() -> bool;
|
||||
@@ -50,7 +52,7 @@ struct Interface : Emulator::Interface {
|
||||
auto set(const string& name, const any& value) -> bool override;
|
||||
|
||||
private:
|
||||
vector<Device> device;
|
||||
vector<Device> devices;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
|
@@ -31,7 +31,26 @@ auto Player::frame() -> void {
|
||||
|
||||
if(!status.enable) return;
|
||||
|
||||
if(cpu.regs.joybus.settings == 0x0000 && cpu.regs.serial.control == 0x5088) {
|
||||
//todo: verify which settings are actually required
|
||||
//values were taken from observing GBP-compatible games
|
||||
if(!cpu.regs.joybus.settings.sc
|
||||
&& !cpu.regs.joybus.settings.sd
|
||||
&& !cpu.regs.joybus.settings.si
|
||||
&& !cpu.regs.joybus.settings.so
|
||||
&& !cpu.regs.joybus.settings.scmode
|
||||
&& !cpu.regs.joybus.settings.sdmode
|
||||
&& !cpu.regs.joybus.settings.simode
|
||||
&& !cpu.regs.joybus.settings.somode
|
||||
&& !cpu.regs.joybus.settings.irqenable
|
||||
&& !cpu.regs.joybus.settings.mode
|
||||
&& !cpu.regs.serial.control.shiftclockselect
|
||||
&& !cpu.regs.serial.control.shiftclockfrequency
|
||||
&& !cpu.regs.serial.control.transferenablereceive
|
||||
&& cpu.regs.serial.control.transferenablesend
|
||||
&& cpu.regs.serial.control.startbit
|
||||
&& cpu.regs.serial.control.transferlength
|
||||
&& cpu.regs.serial.control.irqenable
|
||||
) {
|
||||
status.packet = (status.packet + 1) % 17;
|
||||
switch(status.packet) {
|
||||
case 0: status.send = 0x0000494e; break;
|
||||
@@ -52,16 +71,16 @@ auto Player::frame() -> void {
|
||||
case 15: status.send = 0x30000003; break;
|
||||
case 16: status.send = 0x30000003; break;
|
||||
}
|
||||
cpu.regs.irq.flag.serial = true;
|
||||
cpu.regs.irq.flag |= CPU::Interrupt::Serial;
|
||||
}
|
||||
}
|
||||
|
||||
auto Player::keyinput() -> maybe<uint16> {
|
||||
if(status.logoDetected) {
|
||||
switch(status.logoCounter) {
|
||||
case 0: return 0x03ff;
|
||||
case 1: return 0x03ff;
|
||||
case 2: return 0x030f;
|
||||
case 0: return {0x03ff};
|
||||
case 1: return {0x03ff};
|
||||
case 2: return {0x030f};
|
||||
}
|
||||
}
|
||||
return nothing;
|
||||
@@ -72,7 +91,7 @@ auto Player::read() -> maybe<uint32> {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
auto Player::write(uint8 byte, uint2 addr) -> void {
|
||||
auto Player::write(uint2 addr, uint8 byte) -> void {
|
||||
if(!status.enable) return;
|
||||
|
||||
uint shift = addr << 3;
|
||||
|
@@ -16,7 +16,7 @@ struct Player {
|
||||
|
||||
auto keyinput() -> maybe<uint16>;
|
||||
auto read() -> maybe<uint32>;
|
||||
auto write(uint8 byte, uint2 addr) -> void;
|
||||
auto write(uint2 addr, uint8 byte) -> void;
|
||||
|
||||
auto serialize(serializer& s) -> void;
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user