auto sample(P&&... p) -> void {
+ double samples[sizeof...(P)] = {forward(p)...};
+ write(samples);
+ }
+
+ auto serialize(serializer&) -> void;
+
+private:
+ struct Channel {
+ vector filters;
+ vector nyquist;
+ DSP::Resampler::Cubic resampler;
+ };
+ vector channels;
+ double inputFrequency;
+ double outputFrequency;
+
+ friend class Audio;
+};
+
+extern Audio audio;
+
+}
+
+#undef double
diff --git a/emulator/audio/stream.cpp b/emulator/audio/stream.cpp
new file mode 100644
index 0000000..3fe659f
--- /dev/null
+++ b/emulator/audio/stream.cpp
@@ -0,0 +1,125 @@
+auto Stream::reset(uint channelCount, double inputFrequency, double outputFrequency) -> void {
+ channels.reset();
+ channels.resize(channelCount);
+
+ for(auto& channel : channels) {
+ channel.filters.reset();
+ }
+
+ setFrequency(inputFrequency, outputFrequency);
+}
+
+auto Stream::reset() -> void {
+ for(auto& channel : channels) {
+ channel.resampler.reset(this->inputFrequency, this->outputFrequency);
+ }
+}
+
+auto Stream::frequency() const -> double {
+ return inputFrequency;
+}
+
+auto Stream::setFrequency(double inputFrequency, maybe outputFrequency) -> void {
+ this->inputFrequency = inputFrequency;
+ if(outputFrequency) this->outputFrequency = outputFrequency();
+
+ for(auto& channel : channels) {
+ channel.nyquist.reset();
+ channel.resampler.reset(this->inputFrequency, this->outputFrequency);
+ }
+
+ if(this->inputFrequency >= this->outputFrequency * 2) {
+ //add a low-pass filter to prevent aliasing during resampling
+ double cutoffFrequency = min(25000.0, this->outputFrequency / 2.0 - 2000.0);
+ for(auto& channel : channels) {
+ uint passes = 3;
+ for(uint pass : range(passes)) {
+ DSP::IIR::Biquad filter;
+ double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
+ filter.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, this->inputFrequency, q);
+ channel.nyquist.append(filter);
+ }
+ }
+ }
+}
+
+auto Stream::addDCRemovalFilter() -> void {
+ return; //todo: test to ensure this is desirable before enabling
+ for(auto& channel : channels) {
+ Filter filter{Filter::Mode::DCRemoval, Filter::Type::None, Filter::Order::None};
+ channel.filters.append(filter);
+ }
+}
+
+auto Stream::addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
+ for(auto& channel : channels) {
+ for(uint pass : range(passes)) {
+ if(order == Filter::Order::First) {
+ Filter filter{Filter::Mode::OnePole, Filter::Type::LowPass, Filter::Order::First};
+ filter.onePole.reset(DSP::IIR::OnePole::Type::LowPass, cutoffFrequency, inputFrequency);
+ channel.filters.append(filter);
+ }
+ if(order == Filter::Order::Second) {
+ Filter filter{Filter::Mode::Biquad, Filter::Type::LowPass, Filter::Order::Second};
+ double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
+ filter.biquad.reset(DSP::IIR::Biquad::Type::LowPass, cutoffFrequency, inputFrequency, q);
+ channel.filters.append(filter);
+ }
+ }
+ }
+}
+
+auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes) -> void {
+ for(auto& channel : channels) {
+ for(uint pass : range(passes)) {
+ if(order == Filter::Order::First) {
+ Filter filter{Filter::Mode::OnePole, Filter::Type::HighPass, Filter::Order::First};
+ filter.onePole.reset(DSP::IIR::OnePole::Type::HighPass, cutoffFrequency, inputFrequency);
+ channel.filters.append(filter);
+ }
+ if(order == Filter::Order::Second) {
+ Filter filter{Filter::Mode::Biquad, Filter::Type::HighPass, Filter::Order::Second};
+ double q = DSP::IIR::Biquad::butterworth(passes * 2, pass);
+ filter.biquad.reset(DSP::IIR::Biquad::Type::HighPass, cutoffFrequency, inputFrequency, q);
+ channel.filters.append(filter);
+ }
+ }
+ }
+}
+
+auto Stream::pending() const -> uint {
+ if(!channels) return 0;
+ return channels[0].resampler.pending();
+}
+
+auto Stream::read(double samples[]) -> uint {
+ for(uint c : range(channels.size())) samples[c] = channels[c].resampler.read();
+ return channels.size();
+}
+
+auto Stream::write(const double samples[]) -> void {
+ for(auto c : range(channels.size())) {
+ double sample = samples[c] + 1e-25; //constant offset used to suppress denormals
+ for(auto& filter : channels[c].filters) {
+ switch(filter.mode) {
+ case Filter::Mode::DCRemoval: sample = filter.dcRemoval.process(sample); break;
+ case Filter::Mode::OnePole: sample = filter.onePole.process(sample); break;
+ case Filter::Mode::Biquad: sample = filter.biquad.process(sample); break;
+ }
+ }
+ for(auto& filter : channels[c].nyquist) {
+ sample = filter.process(sample);
+ }
+ channels[c].resampler.write(sample);
+ }
+
+ audio.process();
+}
+
+auto Stream::serialize(serializer& s) -> void {
+ for(auto& channel : channels) {
+ channel.resampler.serialize(s);
+ }
+ s.real(inputFrequency);
+ s.real(outputFrequency);
+}
diff --git a/emulator/cheat.hpp b/emulator/cheat.hpp
new file mode 100644
index 0000000..20f194b
--- /dev/null
+++ b/emulator/cheat.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+namespace Emulator {
+
+struct Cheat {
+ struct Code {
+ auto operator==(const Code& code) const -> bool {
+ if(address != code.address) return false;
+ if(data != code.data) return false;
+ if((bool)compare != (bool)code.compare) return false;
+ if(compare && code.compare && compare() != code.compare()) return false;
+ return true;
+ }
+
+ uint address;
+ uint data;
+ maybe compare;
+ bool enable;
+ uint restore;
+ };
+
+ explicit operator bool() const {
+ return codes.size() > 0;
+ }
+
+ auto reset() -> void {
+ codes.reset();
+ }
+
+ auto append(uint address, uint data, maybe compare = {}) -> void {
+ codes.append({address, data, compare});
+ }
+
+ auto assign(const vector& list) -> void {
+ reset();
+ for(auto& entry : list) {
+ for(auto code : entry.split("+")) {
+ auto part = code.transform("=?", "//").split("/");
+ if(part.size() == 2) append(part[0].hex(), part[1].hex());
+ if(part.size() == 3) append(part[0].hex(), part[2].hex(), part[1].hex());
+ }
+ }
+ }
+
+ auto find(uint address, uint compare) -> maybe {
+ for(auto& code : codes) {
+ if(code.address == address && (!code.compare || code.compare() == compare)) {
+ return code.data;
+ }
+ }
+ return nothing;
+ }
+
+ vector codes;
+};
+
+}
diff --git a/emulator/emulator.cpp b/emulator/emulator.cpp
new file mode 100644
index 0000000..ce4b705
--- /dev/null
+++ b/emulator/emulator.cpp
@@ -0,0 +1,8 @@
+#include
+#include
+
+namespace Emulator {
+
+Platform* platform = nullptr;
+
+}
diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp
new file mode 100644
index 0000000..cb17296
--- /dev/null
+++ b/emulator/emulator.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace nall;
+
+#include
+#include
+#include
+#include
+
+namespace Emulator {
+ static const string Name = "bsnes";
+ static const string Version = "115";
+ static const string Copyright = "byuu";
+ static const string License = "GPLv3";
+ static const string Website = "https://byuu.org";
+
+ //incremented only when serialization format changes
+ static const string SerializerVersion = "115";
+
+ namespace Constants {
+ namespace Colorburst {
+ static constexpr double NTSC = 315.0 / 88.0 * 1'000'000.0;
+ static constexpr double PAL = 283.75 * 15'625.0 + 25.0;
+ }
+ }
+
+ //nall/vfs shorthand constants for open(), load()
+ namespace File {
+ static const auto Read = vfs::file::mode::read;
+ static const auto Write = vfs::file::mode::write;
+ static const auto Optional = false;
+ static const auto Required = true;
+ };
+}
+
+#include "platform.hpp"
+#include "interface.hpp"
+#include "game.hpp"
diff --git a/emulator/game.hpp b/emulator/game.hpp
new file mode 100644
index 0000000..cad2141
--- /dev/null
+++ b/emulator/game.hpp
@@ -0,0 +1,112 @@
+#pragma once
+
+namespace Emulator {
+
+struct Game {
+ struct Memory;
+ struct Oscillator;
+
+ inline auto load(string_view) -> void;
+ inline auto memory(Markup::Node) -> maybe;
+ inline auto oscillator(natural = 0) -> maybe;
+
+ struct Memory {
+ Memory() = default;
+ inline Memory(Markup::Node);
+ explicit operator bool() const { return (bool)type; }
+ inline auto name() const -> string;
+
+ string type;
+ natural size;
+ string content;
+ string manufacturer;
+ string architecture;
+ string identifier;
+ boolean nonVolatile;
+ };
+
+ struct Oscillator {
+ Oscillator() = default;
+ inline Oscillator(Markup::Node);
+ explicit operator bool() const { return frequency; }
+
+ natural frequency;
+ };
+
+ Markup::Node document;
+ string sha256;
+ string label;
+ string name;
+ string title;
+ string region;
+ string revision;
+ string board;
+ vector memoryList;
+ vector oscillatorList;
+};
+
+auto Game::load(string_view text) -> void {
+ document = BML::unserialize(text);
+
+ sha256 = document["game/sha256"].text();
+ label = document["game/label"].text();
+ name = document["game/name"].text();
+ title = document["game/title"].text();
+ region = document["game/region"].text();
+ revision = document["game/revision"].text();
+ board = document["game/board"].text();
+
+ for(auto node : document.find("game/board/memory")) {
+ memoryList.append(Memory{node});
+ }
+
+ for(auto node : document.find("game/board/oscillator")) {
+ oscillatorList.append(Oscillator{node});
+ }
+}
+
+auto Game::memory(Markup::Node node) -> maybe {
+ if(!node) return nothing;
+ for(auto& memory : memoryList) {
+ auto type = node["type"].text();
+ auto size = node["size"].natural();
+ auto content = node["content"].text();
+ auto manufacturer = node["manufacturer"].text();
+ auto architecture = node["architecture"].text();
+ auto identifier = node["identifier"].text();
+ if(type && type != memory.type) continue;
+ if(size && size != memory.size) continue;
+ if(content && content != memory.content) continue;
+ if(manufacturer && manufacturer != memory.manufacturer) continue;
+ if(architecture && architecture != memory.architecture) continue;
+ if(identifier && identifier != memory.identifier) continue;
+ return memory;
+ }
+ return nothing;
+}
+
+auto Game::oscillator(natural index) -> maybe {
+ if(index < oscillatorList.size()) return oscillatorList[index];
+ return nothing;
+}
+
+Game::Memory::Memory(Markup::Node node) {
+ type = node["type"].text();
+ size = node["size"].natural();
+ content = node["content"].text();
+ manufacturer = node["manufacturer"].text();
+ architecture = node["architecture"].text();
+ identifier = node["identifier"].text();
+ nonVolatile = !(bool)node["volatile"];
+}
+
+auto Game::Memory::name() const -> string {
+ if(architecture) return string{architecture, ".", content, ".", type}.downcase();
+ return string{content, ".", type}.downcase();
+}
+
+Game::Oscillator::Oscillator(Markup::Node node) {
+ frequency = node["frequency"].natural();
+}
+
+}
diff --git a/emulator/interface.hpp b/emulator/interface.hpp
new file mode 100644
index 0000000..f21399f
--- /dev/null
+++ b/emulator/interface.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+namespace Emulator {
+
+struct Interface {
+ struct Information {
+ string manufacturer;
+ string name;
+ string extension;
+ bool resettable = false;
+ };
+
+ struct Display {
+ struct Type { enum : uint {
+ CRT,
+ LCD,
+ };};
+ uint id = 0;
+ string name;
+ uint type = 0;
+ uint colors = 0;
+ uint width = 0;
+ uint height = 0;
+ uint internalWidth = 0;
+ uint internalHeight = 0;
+ double aspectCorrection = 0;
+ };
+
+ struct Port {
+ uint id;
+ string name;
+ };
+
+ struct Device {
+ uint id;
+ string name;
+ };
+
+ struct Input {
+ struct Type { enum : uint {
+ Hat,
+ Button,
+ Trigger,
+ Control,
+ Axis,
+ Rumble,
+ };};
+
+ uint type;
+ string name;
+ };
+
+ //information
+ virtual auto information() -> Information { return {}; }
+
+ virtual auto display() -> Display { return {}; }
+ virtual auto color(uint32 color) -> uint64 { return 0; }
+
+ //game interface
+ virtual auto loaded() -> bool { return false; }
+ virtual auto hashes() -> vector { return {}; }
+ virtual auto manifests() -> vector { return {}; }
+ virtual auto titles() -> vector { return {}; }
+ virtual auto title() -> string { return {}; }
+ virtual auto load() -> bool { return false; }
+ virtual auto save() -> void {}
+ virtual auto unload() -> void {}
+
+ //system interface
+ virtual auto ports() -> vector { return {}; }
+ virtual auto devices(uint port) -> vector { return {}; }
+ virtual auto inputs(uint device) -> vector { return {}; }
+ virtual auto connected(uint port) -> uint { return 0; }
+ virtual auto connect(uint port, uint device) -> void {}
+ virtual auto power() -> void {}
+ virtual auto reset() -> void {}
+ virtual auto run() -> void {}
+
+ //time functions
+ virtual auto rtc() -> bool { return false; }
+ virtual auto synchronize(uint64 timestamp = 0) -> void {}
+
+ //state functions
+ virtual auto serialize(bool synchronize = true) -> serializer { return {}; }
+ virtual auto unserialize(serializer&) -> bool { return false; }
+
+ //cheat functions
+ virtual auto read(uint24 address) -> uint8 { return 0; }
+ virtual auto cheats(const vector& = {}) -> void {}
+
+ //configuration
+ virtual auto configuration() -> string { return {}; }
+ virtual auto configuration(string name) -> string { return {}; }
+ virtual auto configure(string configuration = "") -> bool { return false; }
+ virtual auto configure(string name, string value) -> bool { return false; }
+
+ //settings
+ virtual auto cap(const string& name) -> bool { return false; }
+ virtual auto get(const string& name) -> any { return {}; }
+ virtual auto set(const string& name, const any& value) -> bool { return false; }
+
+ virtual auto frameSkip() -> uint { return 0; }
+ virtual auto setFrameSkip(uint frameSkip) -> void {}
+
+ virtual auto runAhead() -> bool { return false; }
+ virtual auto setRunAhead(bool runAhead) -> void {}
+};
+
+}
diff --git a/emulator/memory/memory.hpp b/emulator/memory/memory.hpp
new file mode 100644
index 0000000..8e6ce4b
--- /dev/null
+++ b/emulator/memory/memory.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+namespace Emulator::Memory {
+
+inline auto mirror(uint address, uint size) -> uint {
+ if(size == 0) return 0;
+ uint base = 0;
+ uint mask = 1 << 31;
+ while(address >= size) {
+ while(!(address & mask)) mask >>= 1;
+ address -= mask;
+ if(size > mask) {
+ size -= mask;
+ base += mask;
+ }
+ mask >>= 1;
+ }
+ return base + address;
+}
+
+inline auto reduce(uint address, uint mask) -> uint {
+ while(mask) {
+ uint bits = (mask & -mask) - 1;
+ address = address >> 1 & ~bits | address & bits;
+ mask = (mask & mask - 1) >> 1;
+ }
+ return address;
+}
+
+}
diff --git a/emulator/memory/readable.hpp b/emulator/memory/readable.hpp
new file mode 100644
index 0000000..688a2df
--- /dev/null
+++ b/emulator/memory/readable.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include
+
+namespace Emulator::Memory {
+
+template
+struct Readable {
+ ~Readable() { reset(); }
+
+ inline auto reset() -> void {
+ delete[] self.data;
+ self.data = nullptr;
+ self.size = 0;
+ self.mask = 0;
+ }
+
+ inline auto allocate(uint size, T fill = ~0ull) -> void {
+ if(!size) return reset();
+ delete[] self.data;
+ self.size = size;
+ self.mask = bit::round(self.size) - 1;
+ self.data = new T[self.mask + 1];
+ memory::fill(self.data, self.mask + 1, fill);
+ }
+
+ inline auto load(shared_pointer fp) -> void {
+ fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
+ for(uint address = self.size; address <= self.mask; address++) {
+ self.data[address] = self.data[mirror(address, self.size)];
+ }
+ }
+
+ inline auto save(shared_pointer fp) -> void {
+ fp->write(self.data, self.size * sizeof(T));
+ }
+
+ explicit operator bool() const { return (bool)self.data; }
+ inline auto data() const -> const T* { return self.data; }
+ inline auto size() const -> uint { return self.size; }
+ inline auto mask() const -> uint { return self.mask; }
+
+ inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
+ inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
+ inline auto write(uint address, T data) const -> void {}
+
+ auto serialize(serializer& s) -> void {
+ const uint size = self.size;
+ s.integer(self.size);
+ s.integer(self.mask);
+ if(self.size != size) allocate(self.size);
+ s.array(self.data, self.size);
+ }
+
+private:
+ struct {
+ T* data = nullptr;
+ uint size = 0;
+ uint mask = 0;
+ } self;
+};
+
+}
diff --git a/emulator/memory/writable.hpp b/emulator/memory/writable.hpp
new file mode 100644
index 0000000..8ae5bd9
--- /dev/null
+++ b/emulator/memory/writable.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include
+
+namespace Emulator::Memory {
+
+template
+struct Writable {
+ ~Writable() { reset(); }
+
+ inline auto reset() -> void {
+ delete[] self.data;
+ self.data = nullptr;
+ self.size = 0;
+ self.mask = 0;
+ }
+
+ inline auto allocate(uint size, T fill = ~0ull) -> void {
+ if(!size) return reset();
+ delete[] self.data;
+ self.size = size;
+ self.mask = bit::round(self.size) - 1;
+ self.data = new T[self.mask + 1];
+ memory::fill(self.data, self.mask + 1, fill);
+ }
+
+ inline auto load(shared_pointer fp) -> void {
+ fp->read(self.data, min(fp->size(), self.size * sizeof(T)));
+ for(uint address = self.size; address <= self.mask; address++) {
+ self.data[address] = self.data[mirror(address, self.size)];
+ }
+ }
+
+ inline auto save(shared_pointer fp) -> void {
+ fp->write(self.data, self.size * sizeof(T));
+ }
+
+ explicit operator bool() const { return (bool)self.data; }
+ inline auto data() -> T* { return self.data; }
+ inline auto data() const -> const T* { return self.data; }
+ inline auto size() const -> uint { return self.size; }
+ inline auto mask() const -> uint { return self.mask; }
+
+ inline auto operator[](uint address) -> T& { return self.data[address & self.mask]; }
+ inline auto operator[](uint address) const -> T { return self.data[address & self.mask]; }
+ inline auto read(uint address) const -> T { return self.data[address & self.mask]; }
+ inline auto write(uint address, T data) -> void { self.data[address & self.mask] = data; }
+
+ auto serialize(serializer& s) -> void {
+ const uint size = self.size;
+ s.integer(self.size);
+ s.integer(self.mask);
+ if(self.size != size) allocate(self.size);
+ s.array(self.data, self.size);
+ }
+
+private:
+ struct {
+ T* data = nullptr;
+ uint size = 0;
+ uint mask = 0;
+ } self;
+};
+
+}
diff --git a/emulator/platform.hpp b/emulator/platform.hpp
new file mode 100644
index 0000000..8eae0c0
--- /dev/null
+++ b/emulator/platform.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+namespace Emulator {
+
+struct Platform {
+ struct Load {
+ Load() = default;
+ Load(uint pathID, string option = "") : valid(true), pathID(pathID), option(option) {}
+ explicit operator bool() const { return valid; }
+
+ bool valid = false;
+ uint pathID = 0;
+ string option;
+ };
+
+ virtual auto path(uint id) -> string { return ""; }
+ virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> shared_pointer { return {}; }
+ virtual auto load(uint id, string name, string type, vector options = {}) -> Load { return {}; }
+ virtual auto videoFrame(const uint16* data, uint pitch, uint width, uint height, uint scale) -> void {}
+ virtual auto audioFrame(const double* samples, uint channels) -> void {}
+ virtual auto inputPoll(uint port, uint device, uint input) -> int16 { return 0; }
+ virtual auto inputRumble(uint port, uint device, uint input, bool enable) -> void {}
+ virtual auto dipSettings(Markup::Node node) -> uint { return 0; }
+ virtual auto notify(string text) -> void {}
+};
+
+extern Platform* platform;
+
+}
diff --git a/emulator/random.hpp b/emulator/random.hpp
new file mode 100644
index 0000000..b622887
--- /dev/null
+++ b/emulator/random.hpp
@@ -0,0 +1,96 @@
+#pragma once
+
+namespace Emulator {
+
+struct Random {
+ enum class Entropy : uint { None, Low, High };
+
+ auto operator()() -> uint64 {
+ return random();
+ }
+
+ auto entropy(Entropy entropy) -> void {
+ _entropy = entropy;
+ seed();
+ }
+
+ auto seed(maybe seed = nothing, maybe sequence = nothing) -> void {
+ if(!seed) seed = (uint32)clock();
+ if(!sequence) sequence = 0;
+
+ _state = 0;
+ _increment = sequence() << 1 | 1;
+ step();
+ _state += seed();
+ step();
+ }
+
+ auto random() -> uint64 {
+ if(_entropy == Entropy::None) return 0;
+ return (uint64)step() << 32 | (uint64)step() << 0;
+ }
+
+ auto bias(uint64 bias) -> uint64 {
+ if(_entropy == Entropy::None) return bias;
+ return random();
+ }
+
+ auto bound(uint64 bound) -> uint64 {
+ uint64 threshold = -bound % bound;
+ while(true) {
+ uint64 result = random();
+ if(result >= threshold) return result % bound;
+ }
+ }
+
+ auto array(uint8* data, uint32 size) -> void {
+ if(_entropy == Entropy::None) {
+ memory::fill(data, size);
+ return;
+ }
+
+ if(_entropy == Entropy::High) {
+ for(uint32 address : range(size)) {
+ data[address] = random();
+ }
+ return;
+ }
+
+ //Entropy::Low
+ uint lobit = random() & 3;
+ uint hibit = (lobit + 8 + (random() & 3)) & 15;
+ uint lovalue = random() & 255;
+ uint hivalue = random() & 255;
+ if((random() & 3) == 0) lovalue = 0;
+ if((random() & 1) == 0) hivalue = ~lovalue;
+
+ for(uint32 address : range(size)) {
+ uint8 value = (address & 1ull << lobit) ? lovalue : hivalue;
+ if((address & 1ull << hibit)) value = ~value;
+ if((random() & 511) == 0) value ^= 1 << (random() & 7);
+ if((random() & 2047) == 0) value ^= 1 << (random() & 7);
+ data[address] = value;
+ }
+ }
+
+ auto serialize(serializer& s) -> void {
+ s.integer((uint&)_entropy);
+ s.integer(_state);
+ s.integer(_increment);
+ }
+
+private:
+ auto step() -> uint32 {
+ uint64 state = _state;
+ _state = state * 6364136223846793005ull + _increment;
+ uint32 xorshift = (state >> 18 ^ state) >> 27;
+ uint32 rotate = state >> 59;
+ return xorshift >> rotate | xorshift << (-rotate & 31);
+ }
+
+ Entropy _entropy = Entropy::High;
+ uint64 _state;
+ uint64 _increment;
+};
+
+}
diff --git a/emulator/types.hpp b/emulator/types.hpp
new file mode 100644
index 0000000..6c1fcda
--- /dev/null
+++ b/emulator/types.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+using int1 = nall::Integer< 1>;
+using int2 = nall::Integer< 2>;
+using int3 = nall::Integer< 3>;
+using int4 = nall::Integer< 4>;
+using int5 = nall::Integer< 5>;
+using int6 = nall::Integer< 6>;
+using int7 = nall::Integer< 7>;
+using int8 = int8_t;
+using int9 = nall::Integer< 9>;
+using int10 = nall::Integer<10>;
+using int11 = nall::Integer<11>;
+using int12 = nall::Integer<12>;
+using int13 = nall::Integer<13>;
+using int14 = nall::Integer<14>;
+using int15 = nall::Integer<15>;
+using int16 = int16_t;
+using int17 = nall::Integer<17>;
+using int18 = nall::Integer<18>;
+using int19 = nall::Integer<19>;
+using int20 = nall::Integer<20>;
+using int21 = nall::Integer<21>;
+using int22 = nall::Integer<22>;
+using int23 = nall::Integer<23>;
+using int24 = nall::Integer<24>;
+using int25 = nall::Integer<25>;
+using int26 = nall::Integer<26>;
+using int27 = nall::Integer<27>;
+using int28 = nall::Integer<28>;
+using int29 = nall::Integer<29>;
+using int30 = nall::Integer<30>;
+using int31 = nall::Integer<31>;
+using int32 = int32_t;
+using int48 = nall::Integer<48>; //Cx4
+using int64 = int64_t;
+
+using uint1 = nall::Natural< 1>;
+using uint2 = nall::Natural< 2>;
+using uint3 = nall::Natural< 3>;
+using uint4 = nall::Natural< 4>;
+using uint5 = nall::Natural< 5>;
+using uint6 = nall::Natural< 6>;
+using uint7 = nall::Natural< 7>;
+using uint8 = uint8_t;
+using uint9 = nall::Natural< 9>;
+using uint10 = nall::Natural<10>;
+using uint11 = nall::Natural<11>;
+using uint12 = nall::Natural<12>;
+using uint13 = nall::Natural<13>;
+using uint14 = nall::Natural<14>;
+using uint15 = nall::Natural<15>;
+using uint16 = uint16_t;
+using uint17 = nall::Natural<17>;
+using uint18 = nall::Natural<18>;
+using uint19 = nall::Natural<19>;
+using uint20 = nall::Natural<20>;
+using uint21 = nall::Natural<21>;
+using uint22 = nall::Natural<22>;
+using uint23 = nall::Natural<23>;
+using uint24 = nall::Natural<24>;
+using uint25 = nall::Natural<25>;
+using uint26 = nall::Natural<26>;
+using uint27 = nall::Natural<27>;
+using uint28 = nall::Natural<28>;
+using uint29 = nall::Natural<29>;
+using uint30 = nall::Natural<30>;
+using uint31 = nall::Natural<31>;
+using uint32 = uint32_t;
+using uint40 = nall::Natural<40>; //SA1
+using uint48 = nall::Natural<48>; //Cx4
+using uint64 = uint64_t;
diff --git a/filter/2xsai.cpp b/filter/2xsai.cpp
new file mode 100644
index 0000000..9cc43d1
--- /dev/null
+++ b/filter/2xsai.cpp
@@ -0,0 +1,25 @@
+namespace Filter::_2xSaI {
+
+auto size(uint& width, uint& height) -> void {
+ width *= 2;
+ height *= 2;
+}
+
+uint32_t temp[512 * 480];
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ for(unsigned y = 0; y < height; y++) {
+ const uint16_t *line_in = (const uint16_t*)(((const uint8_t*)input) + pitch * y);
+ uint32_t *line_out = temp + y * width;
+ for(unsigned x = 0; x < width; x++) {
+ line_out[x] = colortable[line_in[x]];
+ }
+ }
+
+ _2xSaI32((unsigned char*)temp, width * sizeof(uint32_t), 0, (unsigned char*)output, outpitch, width, height);
+}
+
+}
diff --git a/filter/filter.cpp b/filter/filter.cpp
new file mode 100644
index 0000000..469b9b7
--- /dev/null
+++ b/filter/filter.cpp
@@ -0,0 +1,25 @@
+#include
+
+#undef register
+#define register
+#include "sai/sai.cpp"
+
+uint32_t* colortable;
+#include "snes_ntsc/snes_ntsc.h"
+#include "snes_ntsc/snes_ntsc.c"
+
+#include "none.cpp"
+#include "scanlines-light.cpp"
+#include "scanlines-dark.cpp"
+#include "scanlines-black.cpp"
+#include "pixellate2x.cpp"
+#include "scale2x.cpp"
+#include "2xsai.cpp"
+#include "super-2xsai.cpp"
+#include "super-eagle.cpp"
+#include "lq2x.cpp"
+#include "hq2x.cpp"
+#include "ntsc-rf.cpp"
+#include "ntsc-composite.cpp"
+#include "ntsc-svideo.cpp"
+#include "ntsc-rgb.cpp"
diff --git a/filter/filter.hpp b/filter/filter.hpp
new file mode 100644
index 0000000..ac6b199
--- /dev/null
+++ b/filter/filter.hpp
@@ -0,0 +1,129 @@
+#pragma once
+
+#include
+
+namespace Filter {
+ using Size = auto (*)(uint& width, uint& height) -> void;
+ using Render = auto (*)(uint32_t* palette, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height) -> void;
+}
+
+namespace Filter::None {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::ScanlinesLight {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::ScanlinesDark {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::ScanlinesBlack {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::Pixellate2x {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::Scale2x {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::_2xSaI {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::Super2xSaI {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::SuperEagle {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::LQ2x {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::HQ2x {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::NTSC_RF {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::NTSC_Composite {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::NTSC_SVideo {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
+
+namespace Filter::NTSC_RGB {
+ auto size(uint& width, uint& height) -> void;
+ auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+ ) -> void;
+}
diff --git a/filter/hq2x.cpp b/filter/hq2x.cpp
new file mode 100644
index 0000000..8b3691b
--- /dev/null
+++ b/filter/hq2x.cpp
@@ -0,0 +1,200 @@
+namespace Filter::HQ2x {
+
+enum {
+ diff_offset = (0x440 << 21) + (0x207 << 11) + 0x407,
+ diff_mask = (0x380 << 21) + (0x1f0 << 11) + 0x3f0,
+};
+
+uint32_t *yuvTable;
+uint8_t rotate[256];
+
+const uint8_t hqTable[256] = {
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
+ 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 12, 12, 5, 3, 1, 12,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
+ 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 16, 12, 5, 3, 1, 14,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 12, 12, 5, 19, 16, 12,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 1, 12, 5, 19, 1, 14,
+ 4, 4, 6, 2, 4, 4, 6, 18, 5, 3, 16, 12, 5, 19, 1, 14,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 13, 5, 3, 1, 14,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 13,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 12,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 14,
+ 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 1, 12, 5, 3, 1, 14,
+};
+
+static void initialize() {
+ static bool initialized = false;
+ if(initialized == true) return;
+ initialized = true;
+
+ yuvTable = new uint32_t[32768];
+
+ for(unsigned i = 0; i < 32768; i++) {
+ uint8_t R = (i >> 0) & 31;
+ uint8_t G = (i >> 5) & 31;
+ uint8_t B = (i >> 10) & 31;
+
+ //bgr555->bgr888
+ double r = (R << 3) | (R >> 2);
+ double g = (G << 3) | (G >> 2);
+ double b = (B << 3) | (B >> 2);
+
+ //bgr888->yuv
+ double y = (r + g + b) * (0.25f * (63.5f / 48.0f));
+ double u = ((r - b) * 0.25f + 128.0f) * (7.5f / 7.0f);
+ double v = ((g * 2.0f - r - b) * 0.125f + 128.0f) * (7.5f / 6.0f);
+
+ yuvTable[i] = ((unsigned)y << 21) + ((unsigned)u << 11) + ((unsigned)v);
+ }
+
+ //counter-clockwise rotation table; one revolution:
+ //123 369 12346789
+ //4.6 -> 2.8 =
+ //789 147 36928147
+ for(unsigned n = 0; n < 256; n++) {
+ rotate[n] = ((n >> 2) & 0x11) | ((n << 2) & 0x88)
+ | ((n & 0x01) << 5) | ((n & 0x08) << 3)
+ | ((n & 0x10) >> 3) | ((n & 0x80) >> 5);
+ }
+}
+
+static void terminate() {
+ delete[] yuvTable;
+}
+
+static bool same(uint16_t x, uint16_t y) {
+ return !((yuvTable[x] - yuvTable[y] + diff_offset) & diff_mask);
+}
+
+static bool diff(uint32_t x, uint16_t y) {
+ return ((x - yuvTable[y]) & diff_mask);
+}
+
+static void grow(uint32_t &n) { n |= n << 16; n &= 0x03e07c1f; }
+static uint16_t pack(uint32_t n) { n &= 0x03e07c1f; return n | (n >> 16); }
+
+static uint16_t blend1(uint32_t A, uint32_t B) {
+ grow(A); grow(B);
+ return pack((A * 3 + B) >> 2);
+}
+
+static uint16_t blend2(uint32_t A, uint32_t B, uint32_t C) {
+ grow(A); grow(B); grow(C);
+ return pack((A * 2 + B + C) >> 2);
+}
+
+static uint16_t blend3(uint32_t A, uint32_t B, uint32_t C) {
+ grow(A); grow(B); grow(C);
+ return pack((A * 5 + B * 2 + C) >> 3);
+}
+
+static uint16_t blend4(uint32_t A, uint32_t B, uint32_t C) {
+ grow(A); grow(B); grow(C);
+ return pack((A * 6 + B + C) >> 3);
+}
+
+static uint16_t blend5(uint32_t A, uint32_t B, uint32_t C) {
+ grow(A); grow(B); grow(C);
+ return pack((A * 2 + (B + C) * 3) >> 3);
+}
+
+static uint16_t blend6(uint32_t A, uint32_t B, uint32_t C) {
+ grow(A); grow(B); grow(C);
+ return pack((A * 14 + B + C) >> 4);
+}
+
+static uint16_t blend(unsigned rule, uint16_t E, uint16_t A, uint16_t B, uint16_t D, uint16_t F, uint16_t H) {
+ switch(rule) { default:
+ case 0: return E;
+ case 1: return blend1(E, A);
+ case 2: return blend1(E, D);
+ case 3: return blend1(E, B);
+ case 4: return blend2(E, D, B);
+ case 5: return blend2(E, A, B);
+ case 6: return blend2(E, A, D);
+ case 7: return blend3(E, B, D);
+ case 8: return blend3(E, D, B);
+ case 9: return blend4(E, D, B);
+ case 10: return blend5(E, D, B);
+ case 11: return blend6(E, D, B);
+ case 12: return same(B, D) ? blend2(E, D, B) : E;
+ case 13: return same(B, D) ? blend5(E, D, B) : E;
+ case 14: return same(B, D) ? blend6(E, D, B) : E;
+ case 15: return same(B, D) ? blend2(E, D, B) : blend1(E, A);
+ case 16: return same(B, D) ? blend4(E, D, B) : blend1(E, A);
+ case 17: return same(B, D) ? blend5(E, D, B) : blend1(E, A);
+ case 18: return same(B, F) ? blend3(E, B, D) : blend1(E, D);
+ case 19: return same(D, H) ? blend3(E, D, B) : blend1(E, B);
+ }
+}
+
+auto size(uint& width, uint& height) -> void {
+ width *= 2;
+ height *= 2;
+}
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ initialize();
+
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ for(uint y = 0; y < height; y++) {
+ const uint16_t* in = input + y * pitch;
+ uint32_t* out0 = output + y * outpitch * 2;
+ uint32_t* out1 = output + y * outpitch * 2 + outpitch;
+
+ int prevline = (y == 0 ? 0 : pitch);
+ int nextline = (y == height - 1 ? 0 : pitch);
+
+ in++;
+ *out0++ = 0; *out0++ = 0;
+ *out1++ = 0; *out1++ = 0;
+
+ for(unsigned x = 1; x < width - 1; x++) {
+ uint16_t A = *(in - prevline - 1);
+ uint16_t B = *(in - prevline + 0);
+ uint16_t C = *(in - prevline + 1);
+ uint16_t D = *(in - 1);
+ uint16_t E = *(in + 0);
+ uint16_t F = *(in + 1);
+ uint16_t G = *(in + nextline - 1);
+ uint16_t H = *(in + nextline + 0);
+ uint16_t I = *(in + nextline + 1);
+ uint32_t e = yuvTable[E] + diff_offset;
+
+ uint8_t pattern;
+ pattern = diff(e, A) << 0;
+ pattern |= diff(e, B) << 1;
+ pattern |= diff(e, C) << 2;
+ pattern |= diff(e, D) << 3;
+ pattern |= diff(e, F) << 4;
+ pattern |= diff(e, G) << 5;
+ pattern |= diff(e, H) << 6;
+ pattern |= diff(e, I) << 7;
+
+ *(out0 + 0) = colortable[blend(hqTable[pattern], E, A, B, D, F, H)]; pattern = rotate[pattern];
+ *(out0 + 1) = colortable[blend(hqTable[pattern], E, C, F, B, H, D)]; pattern = rotate[pattern];
+ *(out1 + 1) = colortable[blend(hqTable[pattern], E, I, H, F, D, B)]; pattern = rotate[pattern];
+ *(out1 + 0) = colortable[blend(hqTable[pattern], E, G, D, H, B, F)];
+
+ in++;
+ out0 += 2;
+ out1 += 2;
+ }
+
+ in++;
+ *out0++ = 0; *out0++ = 0;
+ *out1++ = 0; *out1++ = 0;
+ }
+}
+
+}
diff --git a/filter/lq2x.cpp b/filter/lq2x.cpp
new file mode 100644
index 0000000..31b90e0
--- /dev/null
+++ b/filter/lq2x.cpp
@@ -0,0 +1,46 @@
+namespace Filter::LQ2x {
+
+auto size(uint& width, uint& height) -> void {
+ width *= 2;
+ height *= 2;
+}
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ for(uint y = 0; y < height; y++) {
+ const uint16_t* in = input + y * pitch;
+ uint32_t* out0 = output + y * outpitch * 2;
+ uint32_t* out1 = output + y * outpitch * 2 + outpitch;
+
+ int prevline = (y == 0 ? 0 : pitch);
+ int nextline = (y == height - 1 ? 0 : pitch);
+
+ for(uint x = 0; x < width; x++) {
+ uint16_t A = *(in - prevline);
+ uint16_t B = (x > 0) ? *(in - 1) : *in;
+ uint16_t C = *in;
+ uint16_t D = (x < width - 1) ? *(in + 1) : *in;
+ uint16_t E = *(in++ + nextline);
+ uint32_t c = colortable[C];
+
+ if(A != E && B != D) {
+ *out0++ = (A == B ? colortable[C + A - ((C ^ A) & 0x0421) >> 1] : c);
+ *out0++ = (A == D ? colortable[C + A - ((C ^ A) & 0x0421) >> 1] : c);
+ *out1++ = (E == B ? colortable[C + E - ((C ^ E) & 0x0421) >> 1] : c);
+ *out1++ = (E == D ? colortable[C + E - ((C ^ E) & 0x0421) >> 1] : c);
+ } else {
+ *out0++ = c;
+ *out0++ = c;
+ *out1++ = c;
+ *out1++ = c;
+ }
+ }
+ }
+}
+
+}
diff --git a/filter/none.cpp b/filter/none.cpp
new file mode 100644
index 0000000..ce148ce
--- /dev/null
+++ b/filter/none.cpp
@@ -0,0 +1,24 @@
+namespace Filter::None {
+
+auto size(uint& width, uint& height) -> void {
+ width = width;
+ height = height;
+}
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ for(uint y = 0; y < height; y++) {
+ const uint16_t* in = input + y * pitch;
+ uint32_t* out = output + y * outpitch;
+ for(uint x = 0; x < width; x++) {
+ *out++ = colortable[*in++];
+ }
+ }
+}
+
+}
diff --git a/filter/ntsc-composite.cpp b/filter/ntsc-composite.cpp
new file mode 100644
index 0000000..fa171c7
--- /dev/null
+++ b/filter/ntsc-composite.cpp
@@ -0,0 +1,50 @@
+namespace Filter::NTSC_Composite {
+
+struct snes_ntsc_t *ntsc;
+snes_ntsc_setup_t setup;
+int burst;
+int burst_toggle;
+
+void initialize() {
+ static bool initialized = false;
+ if(initialized == true) return;
+ initialized = true;
+
+ ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
+ setup = snes_ntsc_composite;
+ setup.merge_fields = 1;
+ snes_ntsc_init(ntsc, &setup);
+
+ burst = 0;
+ burst_toggle = (setup.merge_fields ? 0 : 1);
+}
+
+void terminate() {
+ if(ntsc) free(ntsc);
+}
+
+auto size(uint& width, uint& height) -> void {
+ width = SNES_NTSC_OUT_WIDTH(256);
+ height = height;
+}
+
+auto render(
+ uint32_t* colortable_, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ initialize();
+ colortable = colortable_;
+
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ if(width <= 256) {
+ snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ } else {
+ snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ }
+
+ burst ^= burst_toggle;
+}
+
+}
diff --git a/filter/ntsc-rf.cpp b/filter/ntsc-rf.cpp
new file mode 100644
index 0000000..675a57c
--- /dev/null
+++ b/filter/ntsc-rf.cpp
@@ -0,0 +1,50 @@
+namespace Filter::NTSC_RF {
+
+struct snes_ntsc_t *ntsc;
+snes_ntsc_setup_t setup;
+int burst;
+int burst_toggle;
+
+void initialize() {
+ static bool initialized = false;
+ if(initialized == true) return;
+ initialized = true;
+
+ ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
+ setup = snes_ntsc_composite;
+ setup.merge_fields = 0;
+ snes_ntsc_init(ntsc, &setup);
+
+ burst = 0;
+ burst_toggle = (setup.merge_fields ? 0 : 1);
+}
+
+void terminate() {
+ if(ntsc) free(ntsc);
+}
+
+auto size(uint& width, uint& height) -> void {
+ width = SNES_NTSC_OUT_WIDTH(256);
+ height = height;
+}
+
+auto render(
+ uint32_t* colortable_, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ initialize();
+ colortable = colortable_;
+
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ if(width <= 256) {
+ snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ } else {
+ snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ }
+
+ burst ^= burst_toggle;
+}
+
+}
diff --git a/filter/ntsc-rgb.cpp b/filter/ntsc-rgb.cpp
new file mode 100644
index 0000000..effd79d
--- /dev/null
+++ b/filter/ntsc-rgb.cpp
@@ -0,0 +1,50 @@
+namespace Filter::NTSC_RGB {
+
+struct snes_ntsc_t *ntsc;
+snes_ntsc_setup_t setup;
+int burst;
+int burst_toggle;
+
+void initialize() {
+ static bool initialized = false;
+ if(initialized == true) return;
+ initialized = true;
+
+ ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
+ setup = snes_ntsc_rgb;
+ setup.merge_fields = 1;
+ snes_ntsc_init(ntsc, &setup);
+
+ burst = 0;
+ burst_toggle = (setup.merge_fields ? 0 : 1);
+}
+
+void terminate() {
+ if(ntsc) free(ntsc);
+}
+
+auto size(uint& width, uint& height) -> void {
+ width = SNES_NTSC_OUT_WIDTH(256);
+ height = height;
+}
+
+auto render(
+ uint32_t* colortable_, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ initialize();
+ colortable = colortable_;
+
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ if(width <= 256) {
+ snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ } else {
+ snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ }
+
+ burst ^= burst_toggle;
+}
+
+}
diff --git a/filter/ntsc-svideo.cpp b/filter/ntsc-svideo.cpp
new file mode 100644
index 0000000..e4045b0
--- /dev/null
+++ b/filter/ntsc-svideo.cpp
@@ -0,0 +1,50 @@
+namespace Filter::NTSC_SVideo {
+
+struct snes_ntsc_t *ntsc;
+snes_ntsc_setup_t setup;
+int burst;
+int burst_toggle;
+
+void initialize() {
+ static bool initialized = false;
+ if(initialized == true) return;
+ initialized = true;
+
+ ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc);
+ setup = snes_ntsc_svideo;
+ setup.merge_fields = 1;
+ snes_ntsc_init(ntsc, &setup);
+
+ burst = 0;
+ burst_toggle = (setup.merge_fields ? 0 : 1);
+}
+
+void terminate() {
+ if(ntsc) free(ntsc);
+}
+
+auto size(uint& width, uint& height) -> void {
+ width = SNES_NTSC_OUT_WIDTH(256);
+ height = height;
+}
+
+auto render(
+ uint32_t* colortable_, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ initialize();
+ colortable = colortable_;
+
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ if(width <= 256) {
+ snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ } else {
+ snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2);
+ }
+
+ burst ^= burst_toggle;
+}
+
+}
diff --git a/filter/pixellate2x.cpp b/filter/pixellate2x.cpp
new file mode 100644
index 0000000..ac18fb2
--- /dev/null
+++ b/filter/pixellate2x.cpp
@@ -0,0 +1,40 @@
+namespace Filter::Pixellate2x {
+
+auto size(uint& width, uint& height) -> void {
+ width = (width <= 256) ? width * 2 : width;
+ height = (height <= 240) ? height * 2 : height;
+}
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ uint32_t *out0 = output;
+ uint32_t *out1 = output + outpitch;
+
+ for(unsigned y = 0; y < height; y++) {
+ for(unsigned x = 0; x < width; x++) {
+ uint32_t p = colortable[*input++];
+
+ *out0++ = p;
+ if(height <= 240) *out1++ = p;
+ if(width > 256) continue;
+
+ *out0++ = p;
+ if(height <= 240) *out1++ = p;
+ }
+
+ input += pitch - width;
+ if(height <= 240) {
+ out0 += outpitch + outpitch - 512;
+ out1 += outpitch + outpitch - 512;
+ } else {
+ out0 += outpitch - 512;
+ }
+ }
+}
+
+}
diff --git a/filter/sai/sai.cpp b/filter/sai/sai.cpp
new file mode 100644
index 0000000..463326d
--- /dev/null
+++ b/filter/sai/sai.cpp
@@ -0,0 +1,1175 @@
+#if defined(ENDIAN_MSB)
+ #define WORDS_BIGENDIAN
+#endif
+
+static uint32_t colorMask = 0xFEFEFE;
+static uint32_t lowPixelMask = 0x010101;
+static uint32_t qcolorMask = 0xFCFCFC;
+static uint32_t qlowpixelMask = 0x030303;
+static uint32_t redblueMask = 0xFF00FF;
+static uint32_t greenMask = 0xFF00;
+
+uint32_t qRGB_COLOR_MASK[2] = { 0xFEFEFE, 0xFEFEFE };
+
+static inline int GetResult1 (uint32_t A, uint32_t B, uint32_t C, uint32_t D,
+ uint32_t /* E */)
+{
+ int x = 0;
+ int y = 0;
+ int r = 0;
+
+ if (A == C)
+ x += 1;
+ else if (B == C)
+ y += 1;
+ if (A == D)
+ x += 1;
+ else if (B == D)
+ y += 1;
+ if (x <= 1)
+ r += 1;
+ if (y <= 1)
+ r -= 1;
+ return r;
+}
+
+static inline int GetResult2 (uint32_t A, uint32_t B, uint32_t C, uint32_t D,
+ uint32_t /* E */)
+{
+ int x = 0;
+ int y = 0;
+ int r = 0;
+
+ if (A == C)
+ x += 1;
+ else if (B == C)
+ y += 1;
+ if (A == D)
+ x += 1;
+ else if (B == D)
+ y += 1;
+ if (x <= 1)
+ r -= 1;
+ if (y <= 1)
+ r += 1;
+ return r;
+}
+
+static inline int GetResult (uint32_t A, uint32_t B, uint32_t C, uint32_t D)
+{
+ int x = 0;
+ int y = 0;
+ int r = 0;
+
+ if (A == C)
+ x += 1;
+ else if (B == C)
+ y += 1;
+ if (A == D)
+ x += 1;
+ else if (B == D)
+ y += 1;
+ if (x <= 1)
+ r += 1;
+ if (y <= 1)
+ r -= 1;
+ return r;
+}
+
+static inline uint32_t INTERPOLATE (uint32_t A, uint32_t B)
+{
+ if (A != B) {
+ return (((A & colorMask) >> 1) + ((B & colorMask) >> 1) +
+ (A & B & lowPixelMask));
+ } else
+ return A;
+}
+
+static inline uint32_t Q_INTERPOLATE (uint32_t A, uint32_t B, uint32_t C, uint32_t D)
+{
+ register uint32_t x = ((A & qcolorMask) >> 2) +
+ ((B & qcolorMask) >> 2) +
+ ((C & qcolorMask) >> 2) + ((D & qcolorMask) >> 2);
+ register uint32_t y = (A & qlowpixelMask) +
+ (B & qlowpixelMask) + (C & qlowpixelMask) + (D & qlowpixelMask);
+
+ y = (y >> 2) & qlowpixelMask;
+ return x + y;
+}
+
+static inline int GetResult1_32 (uint32_t A, uint32_t B, uint32_t C, uint32_t D,
+ uint32_t /* E */)
+{
+ int x = 0;
+ int y = 0;
+ int r = 0;
+
+ if (A == C)
+ x += 1;
+ else if (B == C)
+ y += 1;
+ if (A == D)
+ x += 1;
+ else if (B == D)
+ y += 1;
+ if (x <= 1)
+ r += 1;
+ if (y <= 1)
+ r -= 1;
+ return r;
+}
+
+static inline int GetResult2_32 (uint32_t A, uint32_t B, uint32_t C, uint32_t D,
+ uint32_t /* E */)
+{
+ int x = 0;
+ int y = 0;
+ int r = 0;
+
+ if (A == C)
+ x += 1;
+ else if (B == C)
+ y += 1;
+ if (A == D)
+ x += 1;
+ else if (B == D)
+ y += 1;
+ if (x <= 1)
+ r -= 1;
+ if (y <= 1)
+ r += 1;
+ return r;
+}
+
+#define BLUE_MASK565 0x001F001F
+#define RED_MASK565 0xF800F800
+#define GREEN_MASK565 0x07E007E0
+
+#define BLUE_MASK555 0x001F001F
+#define RED_MASK555 0x7C007C00
+#define GREEN_MASK555 0x03E003E0
+
+void Super2xSaI (uint8_t *srcPtr, uint32_t srcPitch,
+ uint8_t *deltaPtr, uint8_t *dstPtr, uint32_t dstPitch,
+ int width, int height)
+{
+ uint16_t *bP;
+ uint8_t *dP;
+ uint32_t inc_bP;
+ uint32_t Nextline = srcPitch >> 1;
+ {
+ inc_bP = 1;
+
+ for (; height; height--) {
+ bP = (uint16_t *) srcPtr;
+ dP = (uint8_t *) dstPtr;
+
+ for (uint32_t finish = width; finish; finish -= inc_bP) {
+ uint32_t color4, color5, color6;
+ uint32_t color1, color2, color3;
+ uint32_t colorA0, colorA1, colorA2, colorA3,
+ colorB0, colorB1, colorB2, colorB3, colorS1, colorS2;
+ uint32_t product1a, product1b, product2a, product2b;
+
+ //--------------------------------------- B1 B2
+ // 4 5 6 S2
+ // 1 2 3 S1
+ // A1 A2
+
+ colorB0 = *(bP - Nextline - 1);
+ colorB1 = *(bP - Nextline);
+ colorB2 = *(bP - Nextline + 1);
+ colorB3 = *(bP - Nextline + 2);
+
+ color4 = *(bP - 1);
+ color5 = *(bP);
+ color6 = *(bP + 1);
+ colorS2 = *(bP + 2);
+
+ color1 = *(bP + Nextline - 1);
+ color2 = *(bP + Nextline);
+ color3 = *(bP + Nextline + 1);
+ colorS1 = *(bP + Nextline + 2);
+
+ colorA0 = *(bP + Nextline + Nextline - 1);
+ colorA1 = *(bP + Nextline + Nextline);
+ colorA2 = *(bP + Nextline + Nextline + 1);
+ colorA3 = *(bP + Nextline + Nextline + 2);
+
+ //--------------------------------------
+ if (color2 == color6 && color5 != color3) {
+ product2b = product1b = color2;
+ } else if (color5 == color3 && color2 != color6) {
+ product2b = product1b = color5;
+ } else if (color5 == color3 && color2 == color6) {
+ register int r = 0;
+
+ r += GetResult (color6, color5, color1, colorA1);
+ r += GetResult (color6, color5, color4, colorB1);
+ r += GetResult (color6, color5, colorA2, colorS1);
+ r += GetResult (color6, color5, colorB2, colorS2);
+
+ if (r > 0)
+ product2b = product1b = color6;
+ else if (r < 0)
+ product2b = product1b = color5;
+ else {
+ product2b = product1b = INTERPOLATE (color5, color6);
+ }
+ } else {
+ if (color6 == color3 && color3 == colorA1
+ && color2 != colorA2 && color3 != colorA0)
+ product2b =
+ Q_INTERPOLATE (color3, color3, color3, color2);
+ else if (color5 == color2 && color2 == colorA2
+ && colorA1 != color3 && color2 != colorA3)
+ product2b =
+ Q_INTERPOLATE (color2, color2, color2, color3);
+ else
+ product2b = INTERPOLATE (color2, color3);
+
+ if (color6 == color3 && color6 == colorB1
+ && color5 != colorB2 && color6 != colorB0)
+ product1b =
+ Q_INTERPOLATE (color6, color6, color6, color5);
+ else if (color5 == color2 && color5 == colorB2
+ && colorB1 != color6 && color5 != colorB3)
+ product1b =
+ Q_INTERPOLATE (color6, color5, color5, color5);
+ else
+ product1b = INTERPOLATE (color5, color6);
+ }
+
+ if (color5 == color3 && color2 != color6 && color4 == color5
+ && color5 != colorA2)
+ product2a = INTERPOLATE (color2, color5);
+ else
+ if (color5 == color1 && color6 == color5
+ && color4 != color2 && color5 != colorA0)
+ product2a = INTERPOLATE (color2, color5);
+ else
+ product2a = color2;
+
+ if (color2 == color6 && color5 != color3 && color1 == color2
+ && color2 != colorB2)
+ product1a = INTERPOLATE (color2, color5);
+ else
+ if (color4 == color2 && color3 == color2
+ && color1 != color5 && color2 != colorB0)
+ product1a = INTERPOLATE (color2, color5);
+ else
+ product1a = color5;
+
+#ifdef WORDS_BIGENDIAN
+ product1a = (product1a << 16) | product1b;
+ product2a = (product2a << 16) | product2b;
+#else
+ product1a = product1a | (product1b << 16);
+ product2a = product2a | (product2b << 16);
+#endif
+
+ *((uint32_t *) dP) = product1a;
+ *((uint32_t *) (dP + dstPitch)) = product2a;
+
+ bP += inc_bP;
+ dP += sizeof (uint32_t);
+ } // end of for ( finish= width etc..)
+
+ srcPtr += srcPitch;
+ dstPtr += dstPitch << 1;
+ deltaPtr += srcPitch;
+ } // endof: for (; height; height--)
+ }
+}
+
+void Super2xSaI32 (uint8_t *srcPtr, uint32_t srcPitch,
+ uint8_t * /* deltaPtr */, uint8_t *dstPtr, uint32_t dstPitch,
+ int width, int height)
+{
+ uint32_t *bP;
+ uint32_t *dP;
+ uint32_t inc_bP;
+ uint32_t Nextline = srcPitch >> 2;
+ inc_bP = 1;
+
+ for (; height; height--) {
+ bP = (uint32_t *) srcPtr;
+ dP = (uint32_t *) dstPtr;
+
+ for (uint32_t finish = width; finish; finish -= inc_bP) {
+ uint32_t color4, color5, color6;
+ uint32_t color1, color2, color3;
+ uint32_t colorA0, colorA1, colorA2, colorA3,
+ colorB0, colorB1, colorB2, colorB3, colorS1, colorS2;
+ uint32_t product1a, product1b, product2a, product2b;
+
+ //--------------------------------------- B1 B2
+ // 4 5 6 S2
+ // 1 2 3 S1
+ // A1 A2
+
+ colorB0 = *(bP - Nextline - 1);
+ colorB1 = *(bP - Nextline);
+ colorB2 = *(bP - Nextline + 1);
+ colorB3 = *(bP - Nextline + 2);
+
+ color4 = *(bP - 1);
+ color5 = *(bP);
+ color6 = *(bP + 1);
+ colorS2 = *(bP + 2);
+
+ color1 = *(bP + Nextline - 1);
+ color2 = *(bP + Nextline);
+ color3 = *(bP + Nextline + 1);
+ colorS1 = *(bP + Nextline + 2);
+
+ colorA0 = *(bP + Nextline + Nextline - 1);
+ colorA1 = *(bP + Nextline + Nextline);
+ colorA2 = *(bP + Nextline + Nextline + 1);
+ colorA3 = *(bP + Nextline + Nextline + 2);
+
+ //--------------------------------------
+ if (color2 == color6 && color5 != color3) {
+ product2b = product1b = color2;
+ } else if (color5 == color3 && color2 != color6) {
+ product2b = product1b = color5;
+ } else if (color5 == color3 && color2 == color6) {
+ register int r = 0;
+
+ r += GetResult (color6, color5, color1, colorA1);
+ r += GetResult (color6, color5, color4, colorB1);
+ r += GetResult (color6, color5, colorA2, colorS1);
+ r += GetResult (color6, color5, colorB2, colorS2);
+
+ if (r > 0)
+ product2b = product1b = color6;
+ else if (r < 0)
+ product2b = product1b = color5;
+ else {
+ product2b = product1b = INTERPOLATE (color5, color6);
+ }
+ } else {
+ if (color6 == color3 && color3 == colorA1
+ && color2 != colorA2 && color3 != colorA0)
+ product2b =
+ Q_INTERPOLATE (color3, color3, color3, color2);
+ else if (color5 == color2 && color2 == colorA2
+ && colorA1 != color3 && color2 != colorA3)
+ product2b =
+ Q_INTERPOLATE (color2, color2, color2, color3);
+ else
+ product2b = INTERPOLATE (color2, color3);
+
+ if (color6 == color3 && color6 == colorB1
+ && color5 != colorB2 && color6 != colorB0)
+ product1b =
+ Q_INTERPOLATE (color6, color6, color6, color5);
+ else if (color5 == color2 && color5 == colorB2
+ && colorB1 != color6 && color5 != colorB3)
+ product1b =
+ Q_INTERPOLATE (color6, color5, color5, color5);
+ else
+ product1b = INTERPOLATE (color5, color6);
+ }
+
+ if (color5 == color3 && color2 != color6 && color4 == color5
+ && color5 != colorA2)
+ product2a = INTERPOLATE (color2, color5);
+ else
+ if (color5 == color1 && color6 == color5
+ && color4 != color2 && color5 != colorA0)
+ product2a = INTERPOLATE (color2, color5);
+ else
+ product2a = color2;
+
+ if (color2 == color6 && color5 != color3 && color1 == color2
+ && color2 != colorB2)
+ product1a = INTERPOLATE (color2, color5);
+ else
+ if (color4 == color2 && color3 == color2
+ && color1 != color5 && color2 != colorB0)
+ product1a = INTERPOLATE (color2, color5);
+ else
+ product1a = color5;
+ *(dP) = product1a;
+ *(dP+1) = product1b;
+ *(dP + (dstPitch >> 2)) = product2a;
+ *(dP + (dstPitch >> 2) + 1) = product2b;
+
+ bP += inc_bP;
+ dP += 2;
+ } // end of for ( finish= width etc..)
+
+ srcPtr += srcPitch;
+ dstPtr += dstPitch << 1;
+ // deltaPtr += srcPitch;
+ } // endof: for (; height; height--)
+}
+
+void SuperEagle (uint8_t *srcPtr, uint32_t srcPitch, uint8_t *deltaPtr,
+ uint8_t *dstPtr, uint32_t dstPitch, int width, int height)
+{
+ uint8_t *dP;
+ uint16_t *bP;
+ uint16_t *xP;
+ uint32_t inc_bP;
+
+ {
+ inc_bP = 1;
+
+ uint32_t Nextline = srcPitch >> 1;
+
+ for (; height; height--) {
+ bP = (uint16_t *) srcPtr;
+ xP = (uint16_t *) deltaPtr;
+ dP = dstPtr;
+ for (uint32_t finish = width; finish; finish -= inc_bP) {
+ uint32_t color4, color5, color6;
+ uint32_t color1, color2, color3;
+ uint32_t colorA1, colorA2, colorB1, colorB2, colorS1, colorS2;
+ uint32_t product1a, product1b, product2a, product2b;
+
+ colorB1 = *(bP - Nextline);
+ colorB2 = *(bP - Nextline + 1);
+
+ color4 = *(bP - 1);
+ color5 = *(bP);
+ color6 = *(bP + 1);
+ colorS2 = *(bP + 2);
+
+ color1 = *(bP + Nextline - 1);
+ color2 = *(bP + Nextline);
+ color3 = *(bP + Nextline + 1);
+ colorS1 = *(bP + Nextline + 2);
+
+ colorA1 = *(bP + Nextline + Nextline);
+ colorA2 = *(bP + Nextline + Nextline + 1);
+
+ // --------------------------------------
+ if (color2 == color6 && color5 != color3) {
+ product1b = product2a = color2;
+ if ((color1 == color2) || (color6 == colorB2)) {
+ product1a = INTERPOLATE (color2, color5);
+ product1a = INTERPOLATE (color2, product1a);
+ // product1a = color2;
+ } else {
+ product1a = INTERPOLATE (color5, color6);
+ }
+
+ if ((color6 == colorS2) || (color2 == colorA1)) {
+ product2b = INTERPOLATE (color2, color3);
+ product2b = INTERPOLATE (color2, product2b);
+ // product2b = color2;
+ } else {
+ product2b = INTERPOLATE (color2, color3);
+ }
+ } else if (color5 == color3 && color2 != color6) {
+ product2b = product1a = color5;
+
+ if ((colorB1 == color5) || (color3 == colorS1)) {
+ product1b = INTERPOLATE (color5, color6);
+ product1b = INTERPOLATE (color5, product1b);
+ // product1b = color5;
+ } else {
+ product1b = INTERPOLATE (color5, color6);
+ }
+
+ if ((color3 == colorA2) || (color4 == color5)) {
+ product2a = INTERPOLATE (color5, color2);
+ product2a = INTERPOLATE (color5, product2a);
+ // product2a = color5;
+ } else {
+ product2a = INTERPOLATE (color2, color3);
+ }
+
+ } else if (color5 == color3 && color2 == color6) {
+ register int r = 0;
+
+ r += GetResult (color6, color5, color1, colorA1);
+ r += GetResult (color6, color5, color4, colorB1);
+ r += GetResult (color6, color5, colorA2, colorS1);
+ r += GetResult (color6, color5, colorB2, colorS2);
+
+ if (r > 0) {
+ product1b = product2a = color2;
+ product1a = product2b = INTERPOLATE (color5, color6);
+ } else if (r < 0) {
+ product2b = product1a = color5;
+ product1b = product2a = INTERPOLATE (color5, color6);
+ } else {
+ product2b = product1a = color5;
+ product1b = product2a = color2;
+ }
+ } else {
+ product2b = product1a = INTERPOLATE (color2, color6);
+ product2b =
+ Q_INTERPOLATE (color3, color3, color3, product2b);
+ product1a =
+ Q_INTERPOLATE (color5, color5, color5, product1a);
+
+ product2a = product1b = INTERPOLATE (color5, color3);
+ product2a =
+ Q_INTERPOLATE (color2, color2, color2, product2a);
+ product1b =
+ Q_INTERPOLATE (color6, color6, color6, product1b);
+
+ // product1a = color5;
+ // product1b = color6;
+ // product2a = color2;
+ // product2b = color3;
+ }
+#ifdef WORDS_BIGENDIAN
+ product1a = (product1a << 16) | product1b;
+ product2a = (product2a << 16) | product2b;
+#else
+ product1a = product1a | (product1b << 16);
+ product2a = product2a | (product2b << 16);
+#endif
+
+ *((uint32_t *) dP) = product1a;
+ *((uint32_t *) (dP + dstPitch)) = product2a;
+ *xP = color5;
+
+ bP += inc_bP;
+ xP += inc_bP;
+ dP += sizeof (uint32_t);
+ } // end of for ( finish= width etc..)
+
+ srcPtr += srcPitch;
+ dstPtr += dstPitch << 1;
+ deltaPtr += srcPitch;
+ } // endof: for (height; height; height--)
+ }
+}
+
+void SuperEagle32 (uint8_t *srcPtr, uint32_t srcPitch, uint8_t */*deltaPtr*/,
+ uint8_t *dstPtr, uint32_t dstPitch, int width, int height)
+{
+ uint32_t *dP;
+ uint32_t *bP;
+ //uint32_t *xP;
+ uint32_t inc_bP;
+
+ inc_bP = 1;
+
+ uint32_t Nextline = srcPitch >> 2;
+
+ for (; height; height--) {
+ bP = (uint32_t *) srcPtr;
+ //xP = (uint32_t *) deltaPtr;
+ dP = (uint32_t *)dstPtr;
+ for (uint32_t finish = width; finish; finish -= inc_bP) {
+ uint32_t color4, color5, color6;
+ uint32_t color1, color2, color3;
+ uint32_t colorA1, colorA2, colorB1, colorB2, colorS1, colorS2;
+ uint32_t product1a, product1b, product2a, product2b;
+
+ colorB1 = *(bP - Nextline);
+ colorB2 = *(bP - Nextline + 1);
+
+ color4 = *(bP - 1);
+ color5 = *(bP);
+ color6 = *(bP + 1);
+ colorS2 = *(bP + 2);
+
+ color1 = *(bP + Nextline - 1);
+ color2 = *(bP + Nextline);
+ color3 = *(bP + Nextline + 1);
+ colorS1 = *(bP + Nextline + 2);
+
+ colorA1 = *(bP + Nextline + Nextline);
+ colorA2 = *(bP + Nextline + Nextline + 1);
+
+ // --------------------------------------
+ if (color2 == color6 && color5 != color3) {
+ product1b = product2a = color2;
+ if ((color1 == color2) || (color6 == colorB2)) {
+ product1a = INTERPOLATE (color2, color5);
+ product1a = INTERPOLATE (color2, product1a);
+ // product1a = color2;
+ } else {
+ product1a = INTERPOLATE (color5, color6);
+ }
+
+ if ((color6 == colorS2) || (color2 == colorA1)) {
+ product2b = INTERPOLATE (color2, color3);
+ product2b = INTERPOLATE (color2, product2b);
+ // product2b = color2;
+ } else {
+ product2b = INTERPOLATE (color2, color3);
+ }
+ } else if (color5 == color3 && color2 != color6) {
+ product2b = product1a = color5;
+
+ if ((colorB1 == color5) || (color3 == colorS1)) {
+ product1b = INTERPOLATE (color5, color6);
+ product1b = INTERPOLATE (color5, product1b);
+ // product1b = color5;
+ } else {
+ product1b = INTERPOLATE (color5, color6);
+ }
+
+ if ((color3 == colorA2) || (color4 == color5)) {
+ product2a = INTERPOLATE (color5, color2);
+ product2a = INTERPOLATE (color5, product2a);
+ // product2a = color5;
+ } else {
+ product2a = INTERPOLATE (color2, color3);
+ }
+
+ } else if (color5 == color3 && color2 == color6) {
+ register int r = 0;
+
+ r += GetResult (color6, color5, color1, colorA1);
+ r += GetResult (color6, color5, color4, colorB1);
+ r += GetResult (color6, color5, colorA2, colorS1);
+ r += GetResult (color6, color5, colorB2, colorS2);
+
+ if (r > 0) {
+ product1b = product2a = color2;
+ product1a = product2b = INTERPOLATE (color5, color6);
+ } else if (r < 0) {
+ product2b = product1a = color5;
+ product1b = product2a = INTERPOLATE (color5, color6);
+ } else {
+ product2b = product1a = color5;
+ product1b = product2a = color2;
+ }
+ } else {
+ product2b = product1a = INTERPOLATE (color2, color6);
+ product2b =
+ Q_INTERPOLATE (color3, color3, color3, product2b);
+ product1a =
+ Q_INTERPOLATE (color5, color5, color5, product1a);
+
+ product2a = product1b = INTERPOLATE (color5, color3);
+ product2a =
+ Q_INTERPOLATE (color2, color2, color2, product2a);
+ product1b =
+ Q_INTERPOLATE (color6, color6, color6, product1b);
+
+ // product1a = color5;
+ // product1b = color6;
+ // product2a = color2;
+ // product2b = color3;
+ }
+ *(dP) = product1a;
+ *(dP+1) = product1b;
+ *(dP + (dstPitch >> 2)) = product2a;
+ *(dP + (dstPitch >> 2) +1) = product2b;
+ //*xP = color5;
+
+ bP += inc_bP;
+ //xP += inc_bP;
+ dP += 2;
+ } // end of for ( finish= width etc..)
+
+ srcPtr += srcPitch;
+ dstPtr += dstPitch << 1;
+ //deltaPtr += srcPitch;
+ } // endof: for (height; height; height--)
+}
+
+void _2xSaI (uint8_t *srcPtr, uint32_t srcPitch, uint8_t *deltaPtr,
+ uint8_t *dstPtr, uint32_t dstPitch, int width, int height)
+{
+ uint8_t *dP;
+ uint16_t *bP;
+ uint32_t inc_bP;
+
+ {
+ inc_bP = 1;
+
+ uint32_t Nextline = srcPitch >> 1;
+
+ for (; height; height--) {
+ bP = (uint16_t *) srcPtr;
+ dP = dstPtr;
+
+ for (uint32_t finish = width; finish; finish -= inc_bP) {
+
+ register uint32_t colorA, colorB;
+ uint32_t colorC, colorD,
+ colorE, colorF, colorG, colorH,
+ colorI, colorJ, colorK, colorL,
+
+ colorM, colorN, colorO, colorP;
+ uint32_t product, product1, product2;
+
+ //---------------------------------------
+ // Map of the pixels: I|E F|J
+ // G|A B|K
+ // H|C D|L
+ // M|N O|P
+ colorI = *(bP - Nextline - 1);
+ colorE = *(bP - Nextline);
+ colorF = *(bP - Nextline + 1);
+ colorJ = *(bP - Nextline + 2);
+
+ colorG = *(bP - 1);
+ colorA = *(bP);
+ colorB = *(bP + 1);
+ colorK = *(bP + 2);
+
+ colorH = *(bP + Nextline - 1);
+ colorC = *(bP + Nextline);
+ colorD = *(bP + Nextline + 1);
+ colorL = *(bP + Nextline + 2);
+
+ colorM = *(bP + Nextline + Nextline - 1);
+ colorN = *(bP + Nextline + Nextline);
+ colorO = *(bP + Nextline + Nextline + 1);
+ colorP = *(bP + Nextline + Nextline + 2);
+
+ if ((colorA == colorD) && (colorB != colorC)) {
+ if (((colorA == colorE) && (colorB == colorL)) ||
+ ((colorA == colorC) && (colorA == colorF)
+ && (colorB != colorE) && (colorB == colorJ))) {
+ product = colorA;
+ } else {
+ product = INTERPOLATE (colorA, colorB);
+ }
+
+ if (((colorA == colorG) && (colorC == colorO)) ||
+ ((colorA == colorB) && (colorA == colorH)
+ && (colorG != colorC) && (colorC == colorM))) {
+ product1 = colorA;
+ } else {
+ product1 = INTERPOLATE (colorA, colorC);
+ }
+ product2 = colorA;
+ } else if ((colorB == colorC) && (colorA != colorD)) {
+ if (((colorB == colorF) && (colorA == colorH)) ||
+ ((colorB == colorE) && (colorB == colorD)
+ && (colorA != colorF) && (colorA == colorI))) {
+ product = colorB;
+ } else {
+ product = INTERPOLATE (colorA, colorB);
+ }
+
+ if (((colorC == colorH) && (colorA == colorF)) ||
+ ((colorC == colorG) && (colorC == colorD)
+ && (colorA != colorH) && (colorA == colorI))) {
+ product1 = colorC;
+ } else {
+ product1 = INTERPOLATE (colorA, colorC);
+ }
+ product2 = colorB;
+ } else if ((colorA == colorD) && (colorB == colorC)) {
+ if (colorA == colorB) {
+ product = colorA;
+ product1 = colorA;
+ product2 = colorA;
+ } else {
+ register int r = 0;
+
+ product1 = INTERPOLATE (colorA, colorC);
+ product = INTERPOLATE (colorA, colorB);
+
+ r +=
+ GetResult1 (colorA, colorB, colorG, colorE,
+ colorI);
+ r +=
+ GetResult2 (colorB, colorA, colorK, colorF,
+ colorJ);
+ r +=
+ GetResult2 (colorB, colorA, colorH, colorN,
+ colorM);
+ r +=
+ GetResult1 (colorA, colorB, colorL, colorO,
+ colorP);
+
+ if (r > 0)
+ product2 = colorA;
+ else if (r < 0)
+ product2 = colorB;
+ else {
+ product2 =
+ Q_INTERPOLATE (colorA, colorB, colorC,
+ colorD);
+ }
+ }
+ } else {
+ product2 = Q_INTERPOLATE (colorA, colorB, colorC, colorD);
+
+ if ((colorA == colorC) && (colorA == colorF)
+ && (colorB != colorE) && (colorB == colorJ)) {
+ product = colorA;
+ } else if ((colorB == colorE) && (colorB == colorD)
+ && (colorA != colorF) && (colorA == colorI)) {
+ product = colorB;
+ } else {
+ product = INTERPOLATE (colorA, colorB);
+ }
+
+ if ((colorA == colorB) && (colorA == colorH)
+ && (colorG != colorC) && (colorC == colorM)) {
+ product1 = colorA;
+ } else if ((colorC == colorG) && (colorC == colorD)
+ && (colorA != colorH) && (colorA == colorI)) {
+ product1 = colorC;
+ } else {
+ product1 = INTERPOLATE (colorA, colorC);
+ }
+ }
+
+#ifdef WORDS_BIGENDIAN
+ product = (colorA << 16) | product ;
+ product1 = (product1 << 16) | product2 ;
+#else
+ product = colorA | (product << 16);
+ product1 = product1 | (product2 << 16);
+#endif
+ *((int32_t *) dP) = product;
+ *((uint32_t *) (dP + dstPitch)) = product1;
+
+ bP += inc_bP;
+ dP += sizeof (uint32_t);
+ } // end of for ( finish= width etc..)
+
+ srcPtr += srcPitch;
+ dstPtr += dstPitch << 1;
+ deltaPtr += srcPitch;
+ } // endof: for (height; height; height--)
+ }
+}
+
+void _2xSaI32 (uint8_t *srcPtr, uint32_t srcPitch, uint8_t * /* deltaPtr */,
+ uint8_t *dstPtr, uint32_t dstPitch, int width, int height)
+{
+ uint32_t *dP;
+ uint32_t *bP;
+ uint32_t inc_bP = 1;
+
+ uint32_t Nextline = srcPitch >> 2;
+
+ for (; height; height--) {
+ bP = (uint32_t *) srcPtr;
+ dP = (uint32_t *) dstPtr;
+
+ for (uint32_t finish = width; finish; finish -= inc_bP) {
+ register uint32_t colorA, colorB;
+ uint32_t colorC, colorD,
+ colorE, colorF, colorG, colorH,
+ colorI, colorJ, colorK, colorL,
+
+ colorM, colorN, colorO, colorP;
+ uint32_t product, product1, product2;
+
+ //---------------------------------------
+ // Map of the pixels: I|E F|J
+ // G|A B|K
+ // H|C D|L
+ // M|N O|P
+ colorI = *(bP - Nextline - 1);
+ colorE = *(bP - Nextline);
+ colorF = *(bP - Nextline + 1);
+ colorJ = *(bP - Nextline + 2);
+
+ colorG = *(bP - 1);
+ colorA = *(bP);
+ colorB = *(bP + 1);
+ colorK = *(bP + 2);
+
+ colorH = *(bP + Nextline - 1);
+ colorC = *(bP + Nextline);
+ colorD = *(bP + Nextline + 1);
+ colorL = *(bP + Nextline + 2);
+
+ colorM = *(bP + Nextline + Nextline - 1);
+ colorN = *(bP + Nextline + Nextline);
+ colorO = *(bP + Nextline + Nextline + 1);
+ colorP = *(bP + Nextline + Nextline + 2);
+
+ if ((colorA == colorD) && (colorB != colorC)) {
+ if (((colorA == colorE) && (colorB == colorL)) ||
+ ((colorA == colorC) && (colorA == colorF)
+ && (colorB != colorE) && (colorB == colorJ))) {
+ product = colorA;
+ } else {
+ product = INTERPOLATE (colorA, colorB);
+ }
+
+ if (((colorA == colorG) && (colorC == colorO)) ||
+ ((colorA == colorB) && (colorA == colorH)
+ && (colorG != colorC) && (colorC == colorM))) {
+ product1 = colorA;
+ } else {
+ product1 = INTERPOLATE (colorA, colorC);
+ }
+ product2 = colorA;
+ } else if ((colorB == colorC) && (colorA != colorD)) {
+ if (((colorB == colorF) && (colorA == colorH)) ||
+ ((colorB == colorE) && (colorB == colorD)
+ && (colorA != colorF) && (colorA == colorI))) {
+ product = colorB;
+ } else {
+ product = INTERPOLATE (colorA, colorB);
+ }
+
+ if (((colorC == colorH) && (colorA == colorF)) ||
+ ((colorC == colorG) && (colorC == colorD)
+ && (colorA != colorH) && (colorA == colorI))) {
+ product1 = colorC;
+ } else {
+ product1 = INTERPOLATE (colorA, colorC);
+ }
+ product2 = colorB;
+ } else if ((colorA == colorD) && (colorB == colorC)) {
+ if (colorA == colorB) {
+ product = colorA;
+ product1 = colorA;
+ product2 = colorA;
+ } else {
+ register int r = 0;
+
+ product1 = INTERPOLATE (colorA, colorC);
+ product = INTERPOLATE (colorA, colorB);
+
+ r +=
+ GetResult1 (colorA, colorB, colorG, colorE,
+ colorI);
+ r +=
+ GetResult2 (colorB, colorA, colorK, colorF,
+ colorJ);
+ r +=
+ GetResult2 (colorB, colorA, colorH, colorN,
+ colorM);
+ r +=
+ GetResult1 (colorA, colorB, colorL, colorO,
+ colorP);
+
+ if (r > 0)
+ product2 = colorA;
+ else if (r < 0)
+ product2 = colorB;
+ else {
+ product2 =
+ Q_INTERPOLATE (colorA, colorB, colorC,
+ colorD);
+ }
+ }
+ } else {
+ product2 = Q_INTERPOLATE (colorA, colorB, colorC, colorD);
+
+ if ((colorA == colorC) && (colorA == colorF)
+ && (colorB != colorE) && (colorB == colorJ)) {
+ product = colorA;
+ } else if ((colorB == colorE) && (colorB == colorD)
+ && (colorA != colorF) && (colorA == colorI)) {
+ product = colorB;
+ } else {
+ product = INTERPOLATE (colorA, colorB);
+ }
+
+ if ((colorA == colorB) && (colorA == colorH)
+ && (colorG != colorC) && (colorC == colorM)) {
+ product1 = colorA;
+ } else if ((colorC == colorG) && (colorC == colorD)
+ && (colorA != colorH) && (colorA == colorI)) {
+ product1 = colorC;
+ } else {
+ product1 = INTERPOLATE (colorA, colorC);
+ }
+ }
+ *(dP) = colorA;
+ *(dP + 1) = product;
+ *(dP + (dstPitch >> 2)) = product1;
+ *(dP + (dstPitch >> 2) + 1) = product2;
+
+ bP += inc_bP;
+ dP += 2;
+ } // end of for ( finish= width etc..)
+
+ srcPtr += srcPitch;
+ dstPtr += dstPitch << 1;
+ // deltaPtr += srcPitch;
+ } // endof: for (height; height; height--)
+}
+
+static uint32_t Bilinear (uint32_t A, uint32_t B, uint32_t x)
+{
+ unsigned long areaA, areaB;
+ unsigned long result;
+
+ if (A == B)
+ return A;
+
+ areaB = (x >> 11) & 0x1f; // reduce 16 bit fraction to 5 bits
+ areaA = 0x20 - areaB;
+
+ A = (A & redblueMask) | ((A & greenMask) << 16);
+ B = (B & redblueMask) | ((B & greenMask) << 16);
+
+ result = ((areaA * A) + (areaB * B)) >> 5;
+
+ return (result & redblueMask) | ((result >> 16) & greenMask);
+}
+
+static uint32_t Bilinear4 (uint32_t A, uint32_t B, uint32_t C, uint32_t D, uint32_t x,
+ uint32_t y)
+{
+ unsigned long areaA, areaB, areaC, areaD;
+ unsigned long result, xy;
+
+ x = (x >> 11) & 0x1f;
+ y = (y >> 11) & 0x1f;
+ xy = (x * y) >> 5;
+
+ A = (A & redblueMask) | ((A & greenMask) << 16);
+ B = (B & redblueMask) | ((B & greenMask) << 16);
+ C = (C & redblueMask) | ((C & greenMask) << 16);
+ D = (D & redblueMask) | ((D & greenMask) << 16);
+
+ areaA = 0x20 + xy - x - y;
+ areaB = x - xy;
+ areaC = y - xy;
+ areaD = xy;
+
+ result = ((areaA * A) + (areaB * B) + (areaC * C) + (areaD * D)) >> 5;
+
+ return (result & redblueMask) | ((result >> 16) & greenMask);
+}
+
+void Scale_2xSaI (uint8_t *srcPtr, uint32_t srcPitch, uint8_t * /* deltaPtr */,
+ uint8_t *dstPtr, uint32_t dstPitch,
+ uint32_t dstWidth, uint32_t dstHeight, int width, int height)
+{
+ uint8_t *dP;
+ uint16_t *bP;
+
+ uint32_t w;
+ uint32_t h;
+ uint32_t dw;
+ uint32_t dh;
+ uint32_t hfinish;
+ uint32_t wfinish;
+
+ uint32_t Nextline = srcPitch >> 1;
+
+ wfinish = (width - 1) << 16; // convert to fixed point
+ dw = wfinish / (dstWidth - 1);
+ hfinish = (height - 1) << 16; // convert to fixed point
+ dh = hfinish / (dstHeight - 1);
+
+ for (h = 0; h < hfinish; h += dh) {
+ uint32_t y1, y2;
+
+ y1 = h & 0xffff; // fraction part of fixed point
+ bP = (uint16_t *) (srcPtr + ((h >> 16) * srcPitch));
+ dP = dstPtr;
+ y2 = 0x10000 - y1;
+
+ w = 0;
+
+ for (; w < wfinish;) {
+ uint32_t A, B, C, D;
+ uint32_t E, F, G, H;
+ uint32_t I, J, K, L;
+ uint32_t x1, x2, a1, f1, f2;
+ uint32_t position, product1;
+
+ position = w >> 16;
+ A = bP[position]; // current pixel
+ B = bP[position + 1]; // next pixel
+ C = bP[position + Nextline];
+ D = bP[position + Nextline + 1];
+ E = bP[position - Nextline];
+ F = bP[position - Nextline + 1];
+ G = bP[position - 1];
+ H = bP[position + Nextline - 1];
+ I = bP[position + 2];
+ J = bP[position + Nextline + 2];
+ K = bP[position + Nextline + Nextline];
+ L = bP[position + Nextline + Nextline + 1];
+
+ x1 = w & 0xffff; // fraction part of fixed point
+ x2 = 0x10000 - x1;
+
+ /*0*/
+ if (A == B && C == D && A == C)
+ product1 = A;
+ else /*1*/ if (A == D && B != C) {
+ f1 = (x1 >> 1) + (0x10000 >> 2);
+ f2 = (y1 >> 1) + (0x10000 >> 2);
+ if (y1 <= f1 && A == J && A != E) // close to B
+ {
+ a1 = f1 - y1;
+ product1 = Bilinear (A, B, a1);
+ } else if (y1 >= f1 && A == G && A != L) // close to C
+ {
+ a1 = y1 - f1;
+ product1 = Bilinear (A, C, a1);
+ }
+ else if (x1 >= f2 && A == E && A != J) // close to B
+ {
+ a1 = x1 - f2;
+ product1 = Bilinear (A, B, a1);
+ }
+ else if (x1 <= f2 && A == L && A != G) // close to C
+ {
+ a1 = f2 - x1;
+ product1 = Bilinear (A, C, a1);
+ }
+ else if (y1 >= x1) // close to C
+ {
+ a1 = y1 - x1;
+ product1 = Bilinear (A, C, a1);
+ }
+ else if (y1 <= x1) // close to B
+ {
+ a1 = x1 - y1;
+ product1 = Bilinear (A, B, a1);
+ }
+ }
+ else
+ /*2*/
+ if (B == C && A != D)
+ {
+ f1 = (x1 >> 1) + (0x10000 >> 2);
+ f2 = (y1 >> 1) + (0x10000 >> 2);
+ if (y2 >= f1 && B == H && B != F) // close to A
+ {
+ a1 = y2 - f1;
+ product1 = Bilinear (B, A, a1);
+ }
+ else if (y2 <= f1 && B == I && B != K) // close to D
+ {
+ a1 = f1 - y2;
+ product1 = Bilinear (B, D, a1);
+ }
+ else if (x2 >= f2 && B == F && B != H) // close to A
+ {
+ a1 = x2 - f2;
+ product1 = Bilinear (B, A, a1);
+ }
+ else if (x2 <= f2 && B == K && B != I) // close to D
+ {
+ a1 = f2 - x2;
+ product1 = Bilinear (B, D, a1);
+ }
+ else if (y2 >= x1) // close to A
+ {
+ a1 = y2 - x1;
+ product1 = Bilinear (B, A, a1);
+ }
+ else if (y2 <= x1) // close to D
+ {
+ a1 = x1 - y2;
+ product1 = Bilinear (B, D, a1);
+ }
+ }
+ /*3*/
+ else
+ {
+ product1 = Bilinear4 (A, B, C, D, x1, y1);
+ }
+
+ //end First Pixel
+ *(uint32_t *) dP = product1;
+ dP += 2;
+ w += dw;
+ }
+ dstPtr += dstPitch;
+ }
+}
diff --git a/filter/scale2x.cpp b/filter/scale2x.cpp
new file mode 100644
index 0000000..6a0dc6a
--- /dev/null
+++ b/filter/scale2x.cpp
@@ -0,0 +1,46 @@
+namespace Filter::Scale2x {
+
+auto size(uint& width, uint& height) -> void {
+ width *= 2;
+ height *= 2;
+}
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ for(uint y = 0; y < height; y++) {
+ const uint16_t* in = input + y * pitch;
+ uint32_t* out0 = output + y * outpitch * 2;
+ uint32_t* out1 = output + y * outpitch * 2 + outpitch;
+
+ int prevline = (y == 0 ? 0 : pitch);
+ int nextline = (y == height - 1 ? 0 : pitch);
+
+ for(unsigned x = 0; x < width; x++) {
+ uint16_t A = *(in - prevline);
+ uint16_t B = (x > 0) ? *(in - 1) : *in;
+ uint16_t C = *in;
+ uint16_t D = (x < width - 1) ? *(in + 1) : *in;
+ uint16_t E = *(in++ + nextline);
+ uint32_t c = colortable[C];
+
+ if(A != E && B != D) {
+ *out0++ = (A == B ? colortable[A] : c);
+ *out0++ = (A == D ? colortable[A] : c);
+ *out1++ = (E == B ? colortable[E] : c);
+ *out1++ = (E == D ? colortable[E] : c);
+ } else {
+ *out0++ = c;
+ *out0++ = c;
+ *out1++ = c;
+ *out1++ = c;
+ }
+ }
+ }
+}
+
+}
diff --git a/filter/scanlines-black.cpp b/filter/scanlines-black.cpp
new file mode 100644
index 0000000..d007a5e
--- /dev/null
+++ b/filter/scanlines-black.cpp
@@ -0,0 +1,28 @@
+namespace Filter::ScanlinesBlack {
+
+auto size(uint& width, uint& height) -> void {
+ width = width;
+ height = height * 2;
+}
+
+auto render(
+ uint32_t* palette, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ for(unsigned y = 0; y < height; y++) {
+ const uint16_t *in = input + y * pitch;
+ uint32_t *out0 = output + y * outpitch * 2;
+ uint32_t *out1 = output + y * outpitch * 2 + outpitch;
+
+ for(unsigned x = 0; x < width; x++) {
+ uint16_t color = *in++;
+ *out0++ = palette[color];
+ *out1++ = 0;
+ }
+ }
+}
+
+}
diff --git a/filter/scanlines-dark.cpp b/filter/scanlines-dark.cpp
new file mode 100644
index 0000000..e41bb36
--- /dev/null
+++ b/filter/scanlines-dark.cpp
@@ -0,0 +1,48 @@
+namespace Filter::ScanlinesDark {
+
+uint16_t adjust[32768];
+
+void initialize() {
+ static bool initialized = false;
+ if(initialized == true) return;
+ initialized = true;
+
+ for(unsigned i = 0; i < 32768; i++) {
+ uint8_t r = (i >> 10) & 31;
+ uint8_t g = (i >> 5) & 31;
+ uint8_t b = (i >> 0) & 31;
+ r *= 0.333;
+ g *= 0.333;
+ b *= 0.333;
+ adjust[i] = (r << 10) + (g << 5) + (b << 0);
+ }
+}
+
+auto size(uint& width, uint& height) -> void {
+ width = width;
+ height = height * 2;
+}
+
+auto render(
+ uint32_t* palette, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ initialize();
+
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ for(unsigned y = 0; y < height; y++) {
+ const uint16_t *in = input + y * pitch;
+ uint32_t *out0 = output + y * outpitch * 2;
+ uint32_t *out1 = output + y * outpitch * 2 + outpitch;
+
+ for(unsigned x = 0; x < width; x++) {
+ uint16_t color = *in++;
+ *out0++ = palette[color];
+ *out1++ = palette[adjust[color]];
+ }
+ }
+}
+
+}
diff --git a/filter/scanlines-light.cpp b/filter/scanlines-light.cpp
new file mode 100644
index 0000000..aefcd3a
--- /dev/null
+++ b/filter/scanlines-light.cpp
@@ -0,0 +1,48 @@
+namespace Filter::ScanlinesLight {
+
+uint16_t adjust[32768];
+
+void initialize() {
+ static bool initialized = false;
+ if(initialized == true) return;
+ initialized = true;
+
+ for(unsigned i = 0; i < 32768; i++) {
+ uint8_t r = (i >> 10) & 31;
+ uint8_t g = (i >> 5) & 31;
+ uint8_t b = (i >> 0) & 31;
+ r *= 0.666;
+ g *= 0.666;
+ b *= 0.666;
+ adjust[i] = (r << 10) + (g << 5) + (b << 0);
+ }
+}
+
+auto size(uint& width, uint& height) -> void {
+ width = width;
+ height = height * 2;
+}
+
+auto render(
+ uint32_t* palette, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ initialize();
+
+ pitch >>= 1;
+ outpitch >>= 2;
+
+ for(unsigned y = 0; y < height; y++) {
+ const uint16_t *in = input + y * pitch;
+ uint32_t *out0 = output + y * outpitch * 2;
+ uint32_t *out1 = output + y * outpitch * 2 + outpitch;
+
+ for(unsigned x = 0; x < width; x++) {
+ uint16_t color = *in++;
+ *out0++ = palette[color];
+ *out1++ = palette[adjust[color]];
+ }
+ }
+}
+
+}
diff --git a/filter/snes_ntsc/snes_ntsc.c b/filter/snes_ntsc/snes_ntsc.c
new file mode 100755
index 0000000..f622baf
--- /dev/null
+++ b/filter/snes_ntsc/snes_ntsc.c
@@ -0,0 +1,251 @@
+/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
+
+#include "snes_ntsc.h"
+
+/* Copyright (C) 2006-2007 Shay Green. This module is free software; you
+can redistribute it and/or modify it under the terms of the GNU Lesser
+General Public License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version. This
+module is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details. You should have received a copy of the GNU Lesser General Public
+License along with this module; if not, write to the Free Software Foundation,
+Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
+
+snes_ntsc_setup_t const snes_ntsc_monochrome = { 0,-1, 0, 0,.2, 0,.2,-.2,-.2,-1, 1, 0, 0 };
+snes_ntsc_setup_t const snes_ntsc_composite = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 };
+snes_ntsc_setup_t const snes_ntsc_svideo = { 0, 0, 0, 0,.2, 0,.2, -1, -1, 0, 1, 0, 0 };
+snes_ntsc_setup_t const snes_ntsc_rgb = { 0, 0, 0, 0,.2, 0,.7, -1, -1,-1, 1, 0, 0 };
+
+#define alignment_count 3
+#define burst_count 3
+#define rescale_in 8
+#define rescale_out 7
+
+#define artifacts_mid 1.0f
+#define fringing_mid 1.0f
+#define std_decoder_hue 0
+
+#define rgb_bits 7 /* half normal range to allow for doubled hires pixels */
+#define gamma_size 32
+
+#include "snes_ntsc_impl.h"
+
+/* 3 input pixels -> 8 composite samples */
+pixel_info_t const snes_ntsc_pixels [alignment_count] = {
+ { PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } },
+ { PIXEL_OFFSET( -2, -7 ), { .3333f, 1, 1, .3333f } },
+ { PIXEL_OFFSET( 0, -5 ), { 0, .6667f, 1, 1 } },
+};
+
+static void merge_kernel_fields( snes_ntsc_rgb_t* io )
+{
+ int n;
+ for ( n = burst_size; n; --n )
+ {
+ snes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias;
+ snes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias;
+ snes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias;
+ /* merge colors without losing precision */
+ io [burst_size * 0] =
+ ((p0 + p1 - ((p0 ^ p1) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
+ io [burst_size * 1] =
+ ((p1 + p2 - ((p1 ^ p2) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
+ io [burst_size * 2] =
+ ((p2 + p0 - ((p2 ^ p0) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias;
+ ++io;
+ }
+}
+
+static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out )
+{
+ int n;
+ for ( n = burst_count; n; --n )
+ {
+ unsigned i;
+ for ( i = 0; i < rgb_kernel_size / 2; i++ )
+ {
+ snes_ntsc_rgb_t error = color -
+ out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] -
+ out [i + 7] - out [i + 5 +14] - out [i + 3 +28];
+ DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 );
+ }
+ out += alignment_count * rgb_kernel_size;
+ }
+}
+
+void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup )
+{
+ int merge_fields;
+ int entry;
+ init_t impl;
+ if ( !setup )
+ setup = &snes_ntsc_composite;
+ init( &impl, setup );
+
+ merge_fields = setup->merge_fields;
+ if ( setup->artifacts <= -1 && setup->fringing <= -1 )
+ merge_fields = 1;
+
+ for ( entry = 0; entry < snes_ntsc_palette_size; entry++ )
+ {
+ /* Reduce number of significant bits of source color. Clearing the
+ low bits of R and B were least notictable. Modifying green was too
+ noticeable. */
+ int ir = entry >> 8 & 0x1E;
+ int ig = entry >> 4 & 0x1F;
+ int ib = entry << 1 & 0x1E;
+
+ #if SNES_NTSC_BSNES_COLORTBL
+ if ( setup->bsnes_colortbl )
+ {
+ int bgr15 = (ib << 10) | (ig << 5) | ir;
+ unsigned long rgb16 = setup->bsnes_colortbl [bgr15];
+ ir = rgb16 >> 11 & 0x1E;
+ ig = rgb16 >> 6 & 0x1F;
+ ib = rgb16 & 0x1E;
+ }
+ #endif
+
+ {
+ float rr = impl.to_float [ir];
+ float gg = impl.to_float [ig];
+ float bb = impl.to_float [ib];
+
+ float y, i, q = RGB_TO_YIQ( rr, gg, bb, y, i );
+
+ int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g );
+ snes_ntsc_rgb_t rgb = PACK_RGB( r, g, b );
+
+ snes_ntsc_rgb_t* out = ntsc->table [entry];
+ gen_kernel( &impl, y, i, q, out );
+ if ( merge_fields )
+ merge_kernel_fields( out );
+ correct_errors( rgb, out );
+ }
+ }
+}
+
+#ifndef SNES_NTSC_NO_BLITTERS
+
+void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
+ int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
+{
+ int chunk_count = (in_width - 1) / snes_ntsc_in_chunk;
+ for ( ; in_height; --in_height )
+ {
+ SNES_NTSC_IN_T const* line_in = input;
+ SNES_NTSC_BEGIN_ROW( ntsc, burst_phase,
+ snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) );
+ snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
+ int n;
+ ++line_in;
+
+ for ( n = chunk_count; n; --n )
+ {
+ /* order of input and output pixels must not be altered */
+ SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
+ SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
+ SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
+ SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
+
+ line_in += 3;
+ line_out += 7;
+ }
+
+ /* finish final pixels */
+ SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
+ SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
+ SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
+ SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
+
+ burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
+ input += in_row_width;
+ rgb_out = (char*) rgb_out + out_pitch;
+ }
+}
+
+void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width,
+ int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch )
+{
+ int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2);
+ for ( ; in_height; --in_height )
+ {
+ SNES_NTSC_IN_T const* line_in = input;
+ SNES_NTSC_HIRES_ROW( ntsc, burst_phase,
+ snes_ntsc_black, snes_ntsc_black, snes_ntsc_black,
+ SNES_NTSC_ADJ_IN( line_in [0] ),
+ SNES_NTSC_ADJ_IN( line_in [1] ) );
+ snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out;
+ int n;
+ line_in += 2;
+
+ for ( n = chunk_count; n; --n )
+ {
+ /* twice as many input pixels per chunk */
+ SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) );
+ SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) );
+ SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) );
+ SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) );
+ SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) );
+ SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) );
+ SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
+
+ line_in += 6;
+ line_out += 7;
+ }
+
+ SNES_NTSC_COLOR_IN( 0, snes_ntsc_black );
+ SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 1, snes_ntsc_black );
+ SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 2, snes_ntsc_black );
+ SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 3, snes_ntsc_black );
+ SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 4, snes_ntsc_black );
+ SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH );
+
+ SNES_NTSC_COLOR_IN( 5, snes_ntsc_black );
+ SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH );
+ SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH );
+
+ burst_phase = (burst_phase + 1) % snes_ntsc_burst_count;
+ input += in_row_width;
+ rgb_out = (char*) rgb_out + out_pitch;
+ }
+}
+
+#endif
diff --git a/filter/snes_ntsc/snes_ntsc.h b/filter/snes_ntsc/snes_ntsc.h
new file mode 100755
index 0000000..fff97ec
--- /dev/null
+++ b/filter/snes_ntsc/snes_ntsc.h
@@ -0,0 +1,228 @@
+/* SNES NTSC video filter */
+
+/* snes_ntsc 0.2.2 */
+#ifndef SNES_NTSC_H
+#define SNES_NTSC_H
+
+#include "snes_ntsc_config.h"
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
+in parenthesis and should remain fairly stable in future versions. */
+typedef struct snes_ntsc_setup_t
+{
+ /* Basic parameters */
+ double hue; /* -1 = -180 degrees +1 = +180 degrees */
+ double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */
+ double contrast; /* -1 = dark (0.5) +1 = light (1.5) */
+ double brightness; /* -1 = dark (0.5) +1 = light (1.5) */
+ double sharpness; /* edge contrast enhancement/blurring */
+
+ /* Advanced parameters */
+ double gamma; /* -1 = dark (1.5) +1 = light (0.5) */
+ double resolution; /* image resolution */
+ double artifacts; /* artifacts caused by color changes */
+ double fringing; /* color artifacts caused by brightness changes */
+ double bleed; /* color bleed (color resolution reduction) */
+ int merge_fields; /* if 1, merges even and odd fields together to reduce flicker */
+ float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */
+
+ unsigned long const* bsnes_colortbl; /* undocumented; set to 0 */
+} snes_ntsc_setup_t;
+
+/* Video format presets */
+extern snes_ntsc_setup_t const snes_ntsc_composite; /* color bleeding + artifacts */
+extern snes_ntsc_setup_t const snes_ntsc_svideo; /* color bleeding only */
+extern snes_ntsc_setup_t const snes_ntsc_rgb; /* crisp image */
+extern snes_ntsc_setup_t const snes_ntsc_monochrome;/* desaturated + artifacts */
+
+/* Initializes and adjusts parameters. Can be called multiple times on the same
+snes_ntsc_t object. Can pass NULL for either parameter. */
+typedef struct snes_ntsc_t snes_ntsc_t;
+void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup );
+
+/* Filters one or more rows of pixels. Input pixel format is set by SNES_NTSC_IN_FORMAT
+and output RGB depth is set by SNES_NTSC_OUT_DEPTH. Both default to 16-bit RGB.
+In_row_width is the number of pixels to get to the next input row. Out_pitch
+is the number of *bytes* to get to the next output row. */
+void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input,
+ long in_row_width, int burst_phase, int in_width, int in_height,
+ void* rgb_out, long out_pitch );
+
+void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input,
+ long in_row_width, int burst_phase, int in_width, int in_height,
+ void* rgb_out, long out_pitch );
+
+/* Number of output pixels written by low-res blitter for given input width. Width
+might be rounded down slightly; use SNES_NTSC_IN_WIDTH() on result to find rounded
+value. Guaranteed not to round 256 down at all. */
+#define SNES_NTSC_OUT_WIDTH( in_width ) \
+ ((((in_width) - 1) / snes_ntsc_in_chunk + 1) * snes_ntsc_out_chunk)
+
+/* Number of low-res input pixels that will fit within given output width. Might be
+rounded down slightly; use SNES_NTSC_OUT_WIDTH() on result to find rounded
+value. */
+#define SNES_NTSC_IN_WIDTH( out_width ) \
+ (((out_width) / snes_ntsc_out_chunk - 1) * snes_ntsc_in_chunk + 1)
+
+
+/* Interface for user-defined custom blitters */
+
+enum { snes_ntsc_in_chunk = 3 }; /* number of input pixels read per chunk */
+enum { snes_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */
+enum { snes_ntsc_black = 0 }; /* palette index for black */
+enum { snes_ntsc_burst_count = 3 }; /* burst phase cycles through 0, 1, and 2 */
+
+/* Begins outputting row and starts three pixels. First pixel will be cut off a bit.
+Use snes_ntsc_black for unused pixels. Declares variables, so must be before first
+statement in a block (unless you're using C++). */
+#define SNES_NTSC_BEGIN_ROW( ntsc, burst, pixel0, pixel1, pixel2 ) \
+ char const* ktable = \
+ (char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\
+ SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, SNES_NTSC_IN_FORMAT, ktable )
+
+/* Begins input pixel */
+#define SNES_NTSC_COLOR_IN( index, color ) \
+ SNES_NTSC_COLOR_IN_( index, color, SNES_NTSC_IN_FORMAT, ktable )
+
+/* Generates output pixel. Bits can be 24, 16, 15, 14, 32 (treated as 24), or 0:
+24: RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8 RGB)
+16: RRRRRGGG GGGBBBBB (5-6-5 RGB)
+15: RRRRRGG GGGBBBBB (5-5-5 RGB)
+14: BBBBBGG GGGRRRRR (5-5-5 BGR, native SNES format)
+ 0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */
+#define SNES_NTSC_RGB_OUT( index, rgb_out, bits ) \
+ SNES_NTSC_RGB_OUT_14_( index, rgb_out, bits, 1 )
+
+/* Hires equivalents */
+#define SNES_NTSC_HIRES_ROW( ntsc, burst, pixel1, pixel2, pixel3, pixel4, pixel5 ) \
+ char const* ktable = \
+ (char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\
+ unsigned const snes_ntsc_pixel1_ = (pixel1);\
+ snes_ntsc_rgb_t const* kernel1 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel1_ );\
+ unsigned const snes_ntsc_pixel2_ = (pixel2);\
+ snes_ntsc_rgb_t const* kernel2 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel2_ );\
+ unsigned const snes_ntsc_pixel3_ = (pixel3);\
+ snes_ntsc_rgb_t const* kernel3 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel3_ );\
+ unsigned const snes_ntsc_pixel4_ = (pixel4);\
+ snes_ntsc_rgb_t const* kernel4 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel4_ );\
+ unsigned const snes_ntsc_pixel5_ = (pixel5);\
+ snes_ntsc_rgb_t const* kernel5 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel5_ );\
+ snes_ntsc_rgb_t const* kernel0 = kernel1;\
+ snes_ntsc_rgb_t const* kernelx0;\
+ snes_ntsc_rgb_t const* kernelx1 = kernel1;\
+ snes_ntsc_rgb_t const* kernelx2 = kernel1;\
+ snes_ntsc_rgb_t const* kernelx3 = kernel1;\
+ snes_ntsc_rgb_t const* kernelx4 = kernel1;\
+ snes_ntsc_rgb_t const* kernelx5 = kernel1
+
+#define SNES_NTSC_HIRES_OUT( x, rgb_out, bits ) {\
+ snes_ntsc_rgb_t raw_ =\
+ kernel0 [ x ] + kernel2 [(x+5)%7+14] + kernel4 [(x+3)%7+28] +\
+ kernelx0 [(x+7)%7+7] + kernelx2 [(x+5)%7+21] + kernelx4 [(x+3)%7+35] +\
+ kernel1 [(x+6)%7 ] + kernel3 [(x+4)%7+14] + kernel5 [(x+2)%7+28] +\
+ kernelx1 [(x+6)%7+7] + kernelx3 [(x+4)%7+21] + kernelx5 [(x+2)%7+35];\
+ SNES_NTSC_CLAMP_( raw_, 0 );\
+ SNES_NTSC_RGB_OUT_( rgb_out, (bits), 0 );\
+}
+
+
+/* private */
+enum { snes_ntsc_entry_size = 128 };
+enum { snes_ntsc_palette_size = 0x2000 };
+typedef unsigned long snes_ntsc_rgb_t;
+struct snes_ntsc_t {
+ snes_ntsc_rgb_t table [snes_ntsc_palette_size] [snes_ntsc_entry_size];
+};
+enum { snes_ntsc_burst_size = snes_ntsc_entry_size / snes_ntsc_burst_count };
+
+#define SNES_NTSC_RGB16( ktable, n ) \
+ (snes_ntsc_rgb_t const*) (ktable + ((n & 0x001E) | (n >> 1 & 0x03E0) | (n >> 2 & 0x3C00)) * \
+ (snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t)))
+
+#define SNES_NTSC_BGR15( ktable, n ) \
+ (snes_ntsc_rgb_t const*) (ktable + ((n << 9 & 0x3C00) | (n & 0x03E0) | (n >> 10 & 0x001E)) * \
+ (snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t)))
+
+/* common 3->7 ntsc macros */
+#define SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, ENTRY, table ) \
+ unsigned const snes_ntsc_pixel0_ = (pixel0);\
+ snes_ntsc_rgb_t const* kernel0 = ENTRY( table, snes_ntsc_pixel0_ );\
+ unsigned const snes_ntsc_pixel1_ = (pixel1);\
+ snes_ntsc_rgb_t const* kernel1 = ENTRY( table, snes_ntsc_pixel1_ );\
+ unsigned const snes_ntsc_pixel2_ = (pixel2);\
+ snes_ntsc_rgb_t const* kernel2 = ENTRY( table, snes_ntsc_pixel2_ );\
+ snes_ntsc_rgb_t const* kernelx0;\
+ snes_ntsc_rgb_t const* kernelx1 = kernel0;\
+ snes_ntsc_rgb_t const* kernelx2 = kernel0
+
+#define SNES_NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\
+ snes_ntsc_rgb_t raw_ =\
+ kernel0 [x ] + kernel1 [(x+12)%7+14] + kernel2 [(x+10)%7+28] +\
+ kernelx0 [(x+7)%14] + kernelx1 [(x+ 5)%7+21] + kernelx2 [(x+ 3)%7+35];\
+ SNES_NTSC_CLAMP_( raw_, shift );\
+ SNES_NTSC_RGB_OUT_( rgb_out, bits, shift );\
+}
+
+/* common ntsc macros */
+#define snes_ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1))
+#define snes_ntsc_clamp_mask (snes_ntsc_rgb_builder * 3 / 2)
+#define snes_ntsc_clamp_add (snes_ntsc_rgb_builder * 0x101)
+#define SNES_NTSC_CLAMP_( io, shift ) {\
+ snes_ntsc_rgb_t sub = (io) >> (9-(shift)) & snes_ntsc_clamp_mask;\
+ snes_ntsc_rgb_t clamp = snes_ntsc_clamp_add - sub;\
+ io |= clamp;\
+ clamp -= sub;\
+ io &= clamp;\
+}
+
+#define SNES_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\
+ unsigned color_;\
+ kernelx##index = kernel##index;\
+ kernel##index = (color_ = (color), ENTRY( table, color_ ));\
+}
+
+/* x is always zero except in snes_ntsc library */
+/* original routine */
+/*
+#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\
+ if ( bits == 16 )\
+ rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\
+ if ( bits == 24 || bits == 32 )\
+ rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\
+ if ( bits == 15 )\
+ rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\
+ if ( bits == 14 )\
+ rgb_out = (raw_>>(24-x)& 0x001F)|(raw_>>(9-x)&0x03E0)|(raw_<<(6+x)&0x7C00);\
+ if ( bits == 0 )\
+ rgb_out = raw_ << x;\
+}
+*/
+
+/* custom bsnes routine -- hooks into bsnes colortable */
+#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\
+ if ( bits == 16 ) {\
+ rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\
+ rgb_out = ((rgb_out&0xf800)>>11)|((rgb_out&0x07c0)>>1)|((rgb_out&0x001f)<<10);\
+ rgb_out = colortable[rgb_out];\
+ } else if ( bits == 24 || bits == 32 ) {\
+ rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\
+ rgb_out = ((rgb_out&0xf80000)>>19)|((rgb_out&0x00f800)>>6)|((rgb_out&0x0000f8)<<7);\
+ rgb_out = colortable[rgb_out];\
+ } else if ( bits == 15 ) {\
+ rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\
+ rgb_out = ((rgb_out&0x7c00)>>10)|((rgb_out&0x03e0))|((rgb_out&0x001f)<<10);\
+ rgb_out = colortable[rgb_out];\
+ } else {\
+ rgb_out = raw_ << x;\
+ }\
+}
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif
diff --git a/filter/snes_ntsc/snes_ntsc_config.h b/filter/snes_ntsc/snes_ntsc_config.h
new file mode 100755
index 0000000..7ab94c2
--- /dev/null
+++ b/filter/snes_ntsc/snes_ntsc_config.h
@@ -0,0 +1,26 @@
+/* Configure library by modifying this file */
+
+#ifndef SNES_NTSC_CONFIG_H
+#define SNES_NTSC_CONFIG_H
+
+/* Format of source pixels */
+/* #define SNES_NTSC_IN_FORMAT SNES_NTSC_RGB16 */
+#define SNES_NTSC_IN_FORMAT SNES_NTSC_BGR15
+
+/* The following affect the built-in blitter only; a custom blitter can
+handle things however it wants. */
+
+/* Bits per pixel of output. Can be 15, 16, 32, or 24 (same as 32). */
+#define SNES_NTSC_OUT_DEPTH 32
+
+/* Type of input pixel values */
+#define SNES_NTSC_IN_T unsigned short
+
+/* Each raw pixel input value is passed through this. You might want to mask
+the pixel index if you use the high bits as flags, etc. */
+#define SNES_NTSC_ADJ_IN( in ) in
+
+/* For each pixel, this is the basic operation:
+output_color = SNES_NTSC_ADJ_IN( SNES_NTSC_IN_T ) */
+
+#endif
diff --git a/filter/snes_ntsc/snes_ntsc_impl.h b/filter/snes_ntsc/snes_ntsc_impl.h
new file mode 100755
index 0000000..1d7adc7
--- /dev/null
+++ b/filter/snes_ntsc/snes_ntsc_impl.h
@@ -0,0 +1,439 @@
+/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */
+
+/* Common implementation of NTSC filters */
+
+#include
+#include
+
+/* Copyright (C) 2006 Shay Green. This module is free software; you
+can redistribute it and/or modify it under the terms of the GNU Lesser
+General Public License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version. This
+module is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+details. You should have received a copy of the GNU Lesser General Public
+License along with this module; if not, write to the Free Software Foundation,
+Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
+
+#define DISABLE_CORRECTION 0
+
+#undef PI
+#define PI 3.14159265358979323846f
+
+#ifndef LUMA_CUTOFF
+ #define LUMA_CUTOFF 0.20
+#endif
+#ifndef gamma_size
+ #define gamma_size 1
+#endif
+#ifndef rgb_bits
+ #define rgb_bits 8
+#endif
+#ifndef artifacts_max
+ #define artifacts_max (artifacts_mid * 1.5f)
+#endif
+#ifndef fringing_max
+ #define fringing_max (fringing_mid * 2)
+#endif
+#ifndef STD_HUE_CONDITION
+ #define STD_HUE_CONDITION( setup ) 1
+#endif
+
+#define ext_decoder_hue (std_decoder_hue + 15)
+#define rgb_unit (1 << rgb_bits)
+#define rgb_offset (rgb_unit * 2 + 0.5f)
+
+enum { burst_size = snes_ntsc_entry_size / burst_count };
+enum { kernel_half = 16 };
+enum { kernel_size = kernel_half * 2 + 1 };
+
+typedef struct init_t
+{
+ float to_rgb [burst_count * 6];
+ float to_float [gamma_size];
+ float contrast;
+ float brightness;
+ float artifacts;
+ float fringing;
+ float kernel [rescale_out * kernel_size * 2];
+} init_t;
+
+#define ROTATE_IQ( i, q, sin_b, cos_b ) {\
+ float t;\
+ t = i * cos_b - q * sin_b;\
+ q = i * sin_b + q * cos_b;\
+ i = t;\
+}
+
+static void init_filters( init_t* impl, snes_ntsc_setup_t const* setup )
+{
+#if rescale_out > 1
+ float kernels [kernel_size * 2];
+#else
+ float* const kernels = impl->kernel;
+#endif
+
+ /* generate luma (y) filter using sinc kernel */
+ {
+ /* sinc with rolloff (dsf) */
+ float const rolloff = 1 + (float) setup->sharpness * (float) 0.032;
+ float const maxh = 32;
+ float const pow_a_n = (float) pow( rolloff, maxh );
+ float sum;
+ int i;
+ /* quadratic mapping to reduce negative (blurring) range */
+ float to_angle = (float) setup->resolution + 1;
+ to_angle = PI / maxh * (float) LUMA_CUTOFF * (to_angle * to_angle + 1);
+
+ kernels [kernel_size * 3 / 2] = maxh; /* default center value */
+ for ( i = 0; i < kernel_half * 2 + 1; i++ )
+ {
+ int x = i - kernel_half;
+ float angle = x * to_angle;
+ /* instability occurs at center point with rolloff very close to 1.0 */
+ if ( x || pow_a_n > (float) 1.056 || pow_a_n < (float) 0.981 )
+ {
+ float rolloff_cos_a = rolloff * (float) cos( angle );
+ float num = 1 - rolloff_cos_a -
+ pow_a_n * (float) cos( maxh * angle ) +
+ pow_a_n * rolloff * (float) cos( (maxh - 1) * angle );
+ float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
+ float dsf = num / den;
+ kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - (float) 0.5;
+ }
+ }
+
+ /* apply blackman window and find sum */
+ sum = 0;
+ for ( i = 0; i < kernel_half * 2 + 1; i++ )
+ {
+ float x = PI * 2 / (kernel_half * 2) * i;
+ float blackman = 0.42f - 0.5f * (float) cos( x ) + 0.08f * (float) cos( x * 2 );
+ sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman);
+ }
+
+ /* normalize kernel */
+ sum = 1.0f / sum;
+ for ( i = 0; i < kernel_half * 2 + 1; i++ )
+ {
+ int x = kernel_size * 3 / 2 - kernel_half + i;
+ kernels [x] *= sum;
+ assert( kernels [x] == kernels [x] ); /* catch numerical instability */
+ }
+ }
+
+ /* generate chroma (iq) filter using gaussian kernel */
+ {
+ float const cutoff_factor = -0.03125f;
+ float cutoff = (float) setup->bleed;
+ int i;
+
+ if ( cutoff < 0 )
+ {
+ /* keep extreme value accessible only near upper end of scale (1.0) */
+ cutoff *= cutoff;
+ cutoff *= cutoff;
+ cutoff *= cutoff;
+ cutoff *= -30.0f / 0.65f;
+ }
+ cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff;
+
+ for ( i = -kernel_half; i <= kernel_half; i++ )
+ kernels [kernel_size / 2 + i] = (float) exp( i * i * cutoff );
+
+ /* normalize even and odd phases separately */
+ for ( i = 0; i < 2; i++ )
+ {
+ float sum = 0;
+ int x;
+ for ( x = i; x < kernel_size; x += 2 )
+ sum += kernels [x];
+
+ sum = 1.0f / sum;
+ for ( x = i; x < kernel_size; x += 2 )
+ {
+ kernels [x] *= sum;
+ assert( kernels [x] == kernels [x] ); /* catch numerical instability */
+ }
+ }
+ }
+
+ /*
+ printf( "luma:\n" );
+ for ( i = kernel_size; i < kernel_size * 2; i++ )
+ printf( "%f\n", kernels [i] );
+ printf( "chroma:\n" );
+ for ( i = 0; i < kernel_size; i++ )
+ printf( "%f\n", kernels [i] );
+ */
+
+ /* generate linear rescale kernels */
+ #if rescale_out > 1
+ {
+ float weight = 1.0f;
+ float* out = impl->kernel;
+ int n = rescale_out;
+ do
+ {
+ float remain = 0;
+ int i;
+ weight -= 1.0f / rescale_in;
+ for ( i = 0; i < kernel_size * 2; i++ )
+ {
+ float cur = kernels [i];
+ float m = cur * weight;
+ *out++ = m + remain;
+ remain = cur - m;
+ }
+ }
+ while ( --n );
+ }
+ #endif
+}
+
+static float const default_decoder [6] =
+ { 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f };
+
+static void init( init_t* impl, snes_ntsc_setup_t const* setup )
+{
+ impl->brightness = (float) setup->brightness * (0.5f * rgb_unit) + rgb_offset;
+ impl->contrast = (float) setup->contrast * (0.5f * rgb_unit) + rgb_unit;
+ #ifdef default_palette_contrast
+ if ( !setup->palette )
+ impl->contrast *= default_palette_contrast;
+ #endif
+
+ impl->artifacts = (float) setup->artifacts;
+ if ( impl->artifacts > 0 )
+ impl->artifacts *= artifacts_max - artifacts_mid;
+ impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid;
+
+ impl->fringing = (float) setup->fringing;
+ if ( impl->fringing > 0 )
+ impl->fringing *= fringing_max - fringing_mid;
+ impl->fringing = impl->fringing * fringing_mid + fringing_mid;
+
+ init_filters( impl, setup );
+
+ /* generate gamma table */
+ if ( gamma_size > 1 )
+ {
+ float const to_float = 1.0f / (gamma_size - (gamma_size > 1));
+ float const gamma = 1.1333f - (float) setup->gamma * 0.5f;
+ /* match common PC's 2.2 gamma to TV's 2.65 gamma */
+ int i;
+ for ( i = 0; i < gamma_size; i++ )
+ impl->to_float [i] =
+ (float) pow( i * to_float, gamma ) * impl->contrast + impl->brightness;
+ }
+
+ /* setup decoder matricies */
+ {
+ float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue;
+ float sat = (float) setup->saturation + 1;
+ float const* decoder = setup->decoder_matrix;
+ if ( !decoder )
+ {
+ decoder = default_decoder;
+ if ( STD_HUE_CONDITION( setup ) )
+ hue += PI / 180 * (std_decoder_hue - ext_decoder_hue);
+ }
+
+ {
+ float s = (float) sin( hue ) * sat;
+ float c = (float) cos( hue ) * sat;
+ float* out = impl->to_rgb;
+ int n;
+
+ n = burst_count;
+ do
+ {
+ float const* in = decoder;
+ int n = 3;
+ do
+ {
+ float i = *in++;
+ float q = *in++;
+ *out++ = i * c - q * s;
+ *out++ = i * s + q * c;
+ }
+ while ( --n );
+ if ( burst_count <= 1 )
+ break;
+ ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */
+ }
+ while ( --n );
+ }
+ }
+}
+
+/* kernel generation */
+
+#define RGB_TO_YIQ( r, g, b, y, i ) (\
+ (y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\
+ (i = (r) * 0.596f - (g) * 0.275f - (b) * 0.321f),\
+ ((r) * 0.212f - (g) * 0.523f + (b) * 0.311f)\
+)
+
+#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\
+ r = (type) (y + to_rgb [0] * i + to_rgb [1] * q),\
+ g = (type) (y + to_rgb [2] * i + to_rgb [3] * q),\
+ (type) (y + to_rgb [4] * i + to_rgb [5] * q)\
+)
+
+#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1)
+
+enum { rgb_kernel_size = burst_size / alignment_count };
+enum { rgb_bias = rgb_unit * 2 * snes_ntsc_rgb_builder };
+
+typedef struct pixel_info_t
+{
+ int offset;
+ float negate;
+ float kernel [4];
+} pixel_info_t;
+
+#if rescale_in > 1
+ #define PIXEL_OFFSET_( ntsc, scaled ) \
+ (kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \
+ (kernel_size * 2 * scaled))
+
+ #define PIXEL_OFFSET( ntsc, scaled ) \
+ PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\
+ (((scaled) + rescale_out * 10) % rescale_out) ),\
+ (1.0f - (((ntsc) + 100) & 2))
+#else
+ #define PIXEL_OFFSET( ntsc, scaled ) \
+ (kernel_size / 2 + (ntsc) - (scaled)),\
+ (1.0f - (((ntsc) + 100) & 2))
+#endif
+
+extern pixel_info_t const snes_ntsc_pixels [alignment_count];
+
+/* Generate pixel at all burst phases and column alignments */
+static void gen_kernel( init_t* impl, float y, float i, float q, snes_ntsc_rgb_t* out )
+{
+ /* generate for each scanline burst phase */
+ float const* to_rgb = impl->to_rgb;
+ int burst_remain = burst_count;
+ y -= rgb_offset;
+ do
+ {
+ /* Encode yiq into *two* composite signals (to allow control over artifacting).
+ Convolve these with kernels which: filter respective components, apply
+ sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack
+ into integer. Based on algorithm by NewRisingSun. */
+ pixel_info_t const* pixel = snes_ntsc_pixels;
+ int alignment_remain = alignment_count;
+ do
+ {
+ /* negate is -1 when composite starts at odd multiple of 2 */
+ float const yy = y * impl->fringing * pixel->negate;
+ float const ic0 = (i + yy) * pixel->kernel [0];
+ float const qc1 = (q + yy) * pixel->kernel [1];
+ float const ic2 = (i - yy) * pixel->kernel [2];
+ float const qc3 = (q - yy) * pixel->kernel [3];
+
+ float const factor = impl->artifacts * pixel->negate;
+ float const ii = i * factor;
+ float const yc0 = (y + ii) * pixel->kernel [0];
+ float const yc2 = (y - ii) * pixel->kernel [2];
+
+ float const qq = q * factor;
+ float const yc1 = (y + qq) * pixel->kernel [1];
+ float const yc3 = (y - qq) * pixel->kernel [3];
+
+ float const* k = &impl->kernel [pixel->offset];
+ int n;
+ ++pixel;
+ for ( n = rgb_kernel_size; n; --n )
+ {
+ float i = k[0]*ic0 + k[2]*ic2;
+ float q = k[1]*qc1 + k[3]*qc3;
+ float y = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 +
+ k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset;
+ if ( rescale_out <= 1 )
+ k--;
+ else if ( k < &impl->kernel [kernel_size * 2 * (rescale_out - 1)] )
+ k += kernel_size * 2 - 1;
+ else
+ k -= kernel_size * 2 * (rescale_out - 1) + 2;
+ {
+ int r, g, b = YIQ_TO_RGB( y, i, q, to_rgb, int, r, g );
+ *out++ = PACK_RGB( r, g, b ) - rgb_bias;
+ }
+ }
+ }
+ while ( alignment_count > 1 && --alignment_remain );
+
+ if ( burst_count <= 1 )
+ break;
+
+ to_rgb += 6;
+
+ ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */
+ }
+ while ( --burst_remain );
+}
+
+static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out );
+
+#if DISABLE_CORRECTION
+ #define CORRECT_ERROR( a ) { out [i] += rgb_bias; }
+ #define DISTRIBUTE_ERROR( a, b, c ) { out [i] += rgb_bias; }
+#else
+ #define CORRECT_ERROR( a ) { out [a] += error; }
+ #define DISTRIBUTE_ERROR( a, b, c ) {\
+ snes_ntsc_rgb_t fourth = (error + 2 * snes_ntsc_rgb_builder) >> 2;\
+ fourth &= (rgb_bias >> 1) - snes_ntsc_rgb_builder;\
+ fourth -= rgb_bias >> 2;\
+ out [a] += fourth;\
+ out [b] += fourth;\
+ out [c] += fourth;\
+ out [i] += error - (fourth * 3);\
+ }
+#endif
+
+#define RGB_PALETTE_OUT( rgb, out_ )\
+{\
+ unsigned char* out = (out_);\
+ snes_ntsc_rgb_t clamped = (rgb);\
+ SNES_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\
+ out [0] = (unsigned char) (clamped >> 21);\
+ out [1] = (unsigned char) (clamped >> 11);\
+ out [2] = (unsigned char) (clamped >> 1);\
+}
+
+/* blitter related */
+
+#ifndef restrict
+ #if defined (__GNUC__)
+ #define restrict __restrict__
+ #elif defined (_MSC_VER) && _MSC_VER > 1300
+ #define restrict __restrict
+ #else
+ /* no support for restricted pointers */
+ #define restrict
+ #endif
+#endif
+
+#include
+
+#if SNES_NTSC_OUT_DEPTH <= 16
+ #if USHRT_MAX == 0xFFFF
+ typedef unsigned short snes_ntsc_out_t;
+ #else
+ #error "Need 16-bit int type"
+ #endif
+
+#else
+ #if UINT_MAX == 0xFFFFFFFF
+ typedef unsigned int snes_ntsc_out_t;
+ #elif ULONG_MAX == 0xFFFFFFFF
+ typedef unsigned long snes_ntsc_out_t;
+ #else
+ #error "Need 32-bit int type"
+ #endif
+
+#endif
diff --git a/filter/super-2xsai.cpp b/filter/super-2xsai.cpp
new file mode 100644
index 0000000..c760f8b
--- /dev/null
+++ b/filter/super-2xsai.cpp
@@ -0,0 +1,25 @@
+namespace Filter::Super2xSaI {
+
+auto size(uint& width, uint& height) -> void {
+ width *= 2;
+ height *= 2;
+}
+
+uint32_t temp[512 * 480];
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ for(unsigned y = 0; y < height; y++) {
+ const uint16_t *line_in = (const uint16_t*)(((const uint8_t*)input) + pitch * y);
+ uint32_t *line_out = temp + y * width;
+ for(unsigned x = 0; x < width; x++) {
+ line_out[x] = colortable[line_in[x]];
+ }
+ }
+
+ Super2xSaI32((unsigned char*)temp, width * sizeof(uint32_t), 0, (unsigned char*)output, outpitch, width, height);
+}
+
+}
diff --git a/filter/super-eagle.cpp b/filter/super-eagle.cpp
new file mode 100644
index 0000000..90eb875
--- /dev/null
+++ b/filter/super-eagle.cpp
@@ -0,0 +1,25 @@
+namespace Filter::SuperEagle {
+
+auto size(uint& width, uint& height) -> void {
+ width *= 2;
+ height *= 2;
+}
+
+uint32_t temp[512 * 480];
+
+auto render(
+ uint32_t* colortable, uint32_t* output, uint outpitch,
+ const uint16_t* input, uint pitch, uint width, uint height
+) -> void {
+ for(unsigned y = 0; y < height; y++) {
+ const uint16_t *line_in = (const uint16_t*)(((const uint8_t*)input) + pitch * y);
+ uint32_t *line_out = temp + y * width;
+ for(unsigned x = 0; x < width; x++) {
+ line_out[x] = colortable[line_in[x]];
+ }
+ }
+
+ SuperEagle32((unsigned char*)temp, width * sizeof(uint32_t), 0, (unsigned char*)output, outpitch, width, height);
+}
+
+}
diff --git a/gb/Core/apu.c b/gb/Core/apu.c
new file mode 100644
index 0000000..9afa5c9
--- /dev/null
+++ b/gb/Core/apu.c
@@ -0,0 +1,1040 @@
+#include
+#include
+#include
+#include
+#include "gb.h"
+
+#define likely(x) __builtin_expect((x), 1)
+#define unlikely(x) __builtin_expect((x), 0)
+
+static const uint8_t duties[] = {
+ 0, 0, 0, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1, 1, 0,
+};
+
+static void refresh_channel(GB_gameboy_t *gb, unsigned index, unsigned cycles_offset)
+{
+ unsigned multiplier = gb->apu_output.cycles_since_render + cycles_offset - gb->apu_output.last_update[index];
+ gb->apu_output.summed_samples[index].left += gb->apu_output.current_sample[index].left * multiplier;
+ gb->apu_output.summed_samples[index].right += gb->apu_output.current_sample[index].right * multiplier;
+ gb->apu_output.last_update[index] = gb->apu_output.cycles_since_render + cycles_offset;
+}
+
+bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
+{
+ if (gb->model >= GB_MODEL_AGB) {
+ /* On the AGB, mixing is done digitally, so there are no per-channel
+ DACs. Instead, all channels are summed digital regardless of
+ whatever the DAC state would be on a CGB or earlier model. */
+ return true;
+ }
+
+ switch (index) {
+ case GB_SQUARE_1:
+ return gb->io_registers[GB_IO_NR12] & 0xF8;
+
+ case GB_SQUARE_2:
+ return gb->io_registers[GB_IO_NR22] & 0xF8;
+
+ case GB_WAVE:
+ return gb->apu.wave_channel.enable;
+
+ case GB_NOISE:
+ return gb->io_registers[GB_IO_NR42] & 0xF8;
+ }
+
+ return false;
+}
+
+static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index)
+{
+ if (!gb->apu.is_active[index]) return 0;
+
+ switch (index) {
+ case GB_SQUARE_1:
+ return gb->apu.square_channels[GB_SQUARE_1].current_volume;
+ case GB_SQUARE_2:
+ return gb->apu.square_channels[GB_SQUARE_2].current_volume;
+ case GB_WAVE:
+ return 0;
+ case GB_NOISE:
+ return gb->apu.noise_channel.current_volume;
+ }
+ return 0;
+}
+
+static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset)
+{
+ if (gb->model >= GB_MODEL_AGB) {
+ /* On the AGB, because no analog mixing is done, the behavior of NR51 is a bit different.
+ A channel that is not connected to a terminal is idenitcal to a connected channel
+ playing PCM sample 0. */
+ gb->apu.samples[index] = value;
+
+ if (gb->apu_output.sample_rate) {
+ unsigned right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1;
+ unsigned left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
+
+ if (index == GB_WAVE) {
+ /* For some reason, channel 3 is inverted on the AGB */
+ value ^= 0xF;
+ }
+
+ GB_sample_t output;
+ uint8_t bias = agb_bias_for_channel(gb, index);
+
+ if (gb->io_registers[GB_IO_NR51] & (1 << index)) {
+ output.right = (0xf - value * 2 + bias) * right_volume;
+ }
+ else {
+ output.right = 0xf * right_volume;
+ }
+
+ if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
+ output.left = (0xf - value * 2 + bias) * left_volume;
+ }
+ else {
+ output.left = 0xf * left_volume;
+ }
+
+ if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) {
+ refresh_channel(gb, index, cycles_offset);
+ gb->apu_output.current_sample[index] = output;
+ }
+ }
+
+ return;
+ }
+
+ if (!GB_apu_is_DAC_enabled(gb, index)) {
+ value = gb->apu.samples[index];
+ }
+ else {
+ gb->apu.samples[index] = value;
+ }
+
+ if (gb->apu_output.sample_rate) {
+ unsigned right_volume = 0;
+ if (gb->io_registers[GB_IO_NR51] & (1 << index)) {
+ right_volume = (gb->io_registers[GB_IO_NR50] & 7) + 1;
+ }
+ unsigned left_volume = 0;
+ if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
+ left_volume = ((gb->io_registers[GB_IO_NR50] >> 4) & 7) + 1;
+ }
+ GB_sample_t output = {(0xf - value * 2) * left_volume, (0xf - value * 2) * right_volume};
+ if (*(uint32_t *)&(gb->apu_output.current_sample[index]) != *(uint32_t *)&output) {
+ refresh_channel(gb, index, cycles_offset);
+ gb->apu_output.current_sample[index] = output;
+ }
+ }
+}
+
+static double smooth(double x)
+{
+ return 3*x*x - 2*x*x*x;
+}
+
+static void render(GB_gameboy_t *gb)
+{
+ GB_sample_t output = {0,0};
+
+ UNROLL
+ for (unsigned i = 0; i < GB_N_CHANNELS; i++) {
+ double multiplier = CH_STEP;
+
+ if (gb->model < GB_MODEL_AGB) {
+ if (!GB_apu_is_DAC_enabled(gb, i)) {
+ gb->apu_output.dac_discharge[i] -= ((double) DAC_DECAY_SPEED) / gb->apu_output.sample_rate;
+ if (gb->apu_output.dac_discharge[i] < 0) {
+ multiplier = 0;
+ gb->apu_output.dac_discharge[i] = 0;
+ }
+ else {
+ multiplier *= smooth(gb->apu_output.dac_discharge[i]);
+ }
+ }
+ else {
+ gb->apu_output.dac_discharge[i] += ((double) DAC_ATTACK_SPEED) / gb->apu_output.sample_rate;
+ if (gb->apu_output.dac_discharge[i] > 1) {
+ gb->apu_output.dac_discharge[i] = 1;
+ }
+ else {
+ multiplier *= smooth(gb->apu_output.dac_discharge[i]);
+ }
+ }
+ }
+
+ if (likely(gb->apu_output.last_update[i] == 0)) {
+ output.left += gb->apu_output.current_sample[i].left * multiplier;
+ output.right += gb->apu_output.current_sample[i].right * multiplier;
+ }
+ else {
+ refresh_channel(gb, i, 0);
+ output.left += (signed long) gb->apu_output.summed_samples[i].left * multiplier
+ / gb->apu_output.cycles_since_render;
+ output.right += (signed long) gb->apu_output.summed_samples[i].right * multiplier
+ / gb->apu_output.cycles_since_render;
+ gb->apu_output.summed_samples[i] = (GB_sample_t){0, 0};
+ }
+ gb->apu_output.last_update[i] = 0;
+ }
+ gb->apu_output.cycles_since_render = 0;
+
+ GB_sample_t filtered_output = gb->apu_output.highpass_mode?
+ (GB_sample_t) {output.left - gb->apu_output.highpass_diff.left,
+ output.right - gb->apu_output.highpass_diff.right} :
+ output;
+
+ switch (gb->apu_output.highpass_mode) {
+ case GB_HIGHPASS_OFF:
+ gb->apu_output.highpass_diff = (GB_double_sample_t) {0, 0};
+ break;
+ case GB_HIGHPASS_ACCURATE:
+ gb->apu_output.highpass_diff = (GB_double_sample_t)
+ {output.left - filtered_output.left * gb->apu_output.highpass_rate,
+ output.right - filtered_output.right * gb->apu_output.highpass_rate};
+ break;
+ case GB_HIGHPASS_REMOVE_DC_OFFSET: {
+ unsigned mask = gb->io_registers[GB_IO_NR51];
+ unsigned left_volume = 0;
+ unsigned right_volume = 0;
+ UNROLL
+ for (unsigned i = GB_N_CHANNELS; i--;) {
+ if (gb->apu.is_active[i]) {
+ if (mask & 1) {
+ left_volume += (gb->io_registers[GB_IO_NR50] & 7) * CH_STEP * 0xF;
+ }
+ if (mask & 0x10) {
+ right_volume += ((gb->io_registers[GB_IO_NR50] >> 4) & 7) * CH_STEP * 0xF;
+ }
+ }
+ else {
+ left_volume += gb->apu_output.current_sample[i].left * CH_STEP;
+ right_volume += gb->apu_output.current_sample[i].right * CH_STEP;
+ }
+ mask >>= 1;
+ }
+ gb->apu_output.highpass_diff = (GB_double_sample_t)
+ {left_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.left * gb->apu_output.highpass_rate,
+ right_volume * (1 - gb->apu_output.highpass_rate) + gb->apu_output.highpass_diff.right * gb->apu_output.highpass_rate};
+
+ case GB_HIGHPASS_MAX:;
+ }
+
+ }
+
+ assert(gb->apu_output.sample_callback);
+ gb->apu_output.sample_callback(gb, &filtered_output);
+}
+
+static uint16_t new_sweep_sample_legnth(GB_gameboy_t *gb)
+{
+ uint16_t delta = gb->apu.shadow_sweep_sample_legnth >> (gb->io_registers[GB_IO_NR10] & 7);
+ if (gb->io_registers[GB_IO_NR10] & 8) {
+ return gb->apu.shadow_sweep_sample_legnth - delta;
+ }
+ return gb->apu.shadow_sweep_sample_legnth + delta;
+}
+
+static void update_square_sample(GB_gameboy_t *gb, unsigned index)
+{
+ if (gb->apu.square_channels[index].current_sample_index & 0x80) return;
+
+ uint8_t duty = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
+ update_sample(gb, index,
+ duties[gb->apu.square_channels[index].current_sample_index + duty * 8]?
+ gb->apu.square_channels[index].current_volume : 0,
+ 0);
+}
+
+
+/* the effects of NRX2 writes on current volume are not well documented and differ
+ between models and variants. The exact behavior can only be verified on CGB as it
+ requires the PCM12 register. The behavior implemented here was verified on *my*
+ CGB, which might behave differently from other CGB revisions, as well as from the
+ DMG, MGB or SGB/2 */
+static void nrx2_glitch(uint8_t *volume, uint8_t value, uint8_t old_value)
+{
+ if (value & 8) {
+ (*volume)++;
+ }
+
+ if (((value ^ old_value) & 8)) {
+ *volume = 0x10 - *volume;
+ }
+
+ if ((value & 7) && !(old_value & 7) && *volume && !(value & 8)) {
+ (*volume)--;
+ }
+
+ if ((old_value & 7) && (value & 8)) {
+ (*volume)--;
+ }
+
+ (*volume) &= 0xF;
+}
+
+static void tick_square_envelope(GB_gameboy_t *gb, enum GB_CHANNELS index)
+{
+ uint8_t nrx2 = gb->io_registers[index == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
+
+ if (gb->apu.square_channels[index].volume_countdown || (nrx2 & 7)) {
+ if (!gb->apu.square_channels[index].volume_countdown || !--gb->apu.square_channels[index].volume_countdown) {
+ if ((nrx2 & 8) && gb->apu.square_channels[index].current_volume < 0xF) {
+ gb->apu.square_channels[index].current_volume++;
+ }
+
+ else if (!(nrx2 & 8) && gb->apu.square_channels[index].current_volume > 0) {
+ gb->apu.square_channels[index].current_volume--;
+ }
+
+ gb->apu.square_channels[index].volume_countdown = nrx2 & 7;
+
+ if (gb->apu.is_active[index]) {
+ update_square_sample(gb, index);
+ }
+ }
+ }
+}
+
+static void tick_noise_envelope(GB_gameboy_t *gb)
+{
+ uint8_t nr42 = gb->io_registers[GB_IO_NR42];
+
+ if (gb->apu.noise_channel.volume_countdown || (nr42 & 7)) {
+ if (!--gb->apu.noise_channel.volume_countdown) {
+ if ((nr42 & 8) && gb->apu.noise_channel.current_volume < 0xF) {
+ gb->apu.noise_channel.current_volume++;
+ }
+
+ else if (!(nr42 & 8) && gb->apu.noise_channel.current_volume > 0) {
+ gb->apu.noise_channel.current_volume--;
+ }
+
+ gb->apu.noise_channel.volume_countdown = nr42 & 7;
+
+ if (gb->apu.is_active[GB_NOISE]) {
+ update_sample(gb, GB_NOISE,
+ (gb->apu.noise_channel.lfsr & 1) ?
+ gb->apu.noise_channel.current_volume : 0,
+ 0);
+ }
+ }
+ }
+}
+
+void GB_apu_div_event(GB_gameboy_t *gb)
+{
+ if (!gb->apu.global_enable) return;
+ if (gb->apu.skip_div_event) {
+ gb->apu.skip_div_event = false;
+ return;
+ }
+ gb->apu.div_divider++;
+
+ if ((gb->apu.div_divider & 1) == 0) {
+ for (unsigned i = GB_SQUARE_2 + 1; i--;) {
+ uint8_t nrx2 = gb->io_registers[i == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
+ if (gb->apu.is_active[i] && gb->apu.square_channels[i].volume_countdown == 0 && (nrx2 & 7)) {
+ tick_square_envelope(gb, i);
+ }
+ }
+
+ if (gb->apu.is_active[GB_NOISE] && gb->apu.noise_channel.volume_countdown == 0 && (gb->io_registers[GB_IO_NR42] & 7)) {
+ tick_noise_envelope(gb);
+ }
+ }
+
+ if ((gb->apu.div_divider & 7) == 0) {
+ for (unsigned i = GB_SQUARE_2 + 1; i--;) {
+ tick_square_envelope(gb, i);
+ }
+
+ tick_noise_envelope(gb);
+ }
+
+ if ((gb->apu.div_divider & 1) == 1) {
+ for (unsigned i = GB_SQUARE_2 + 1; i--;) {
+ if (gb->apu.square_channels[i].length_enabled) {
+ if (gb->apu.square_channels[i].pulse_length) {
+ if (!--gb->apu.square_channels[i].pulse_length) {
+ gb->apu.is_active[i] = false;
+ update_sample(gb, i, 0, 0);
+ }
+ }
+ }
+ }
+
+ if (gb->apu.wave_channel.length_enabled) {
+ if (gb->apu.wave_channel.pulse_length) {
+ if (!--gb->apu.wave_channel.pulse_length) {
+ gb->apu.is_active[GB_WAVE] = false;
+ update_sample(gb, GB_WAVE, 0, 0);
+ }
+ }
+ }
+
+ if (gb->apu.noise_channel.length_enabled) {
+ if (gb->apu.noise_channel.pulse_length) {
+ if (!--gb->apu.noise_channel.pulse_length) {
+ gb->apu.is_active[GB_NOISE] = false;
+ update_sample(gb, GB_NOISE, 0, 0);
+ }
+ }
+ }
+ }
+
+ if ((gb->apu.div_divider & 3) == 3) {
+ if (!gb->apu.sweep_enabled) {
+ return;
+ }
+ if (gb->apu.square_sweep_countdown) {
+ if (!--gb->apu.square_sweep_countdown) {
+ if ((gb->io_registers[GB_IO_NR10] & 0x70) && (gb->io_registers[GB_IO_NR10] & 0x07)) {
+ gb->apu.square_channels[GB_SQUARE_1].sample_length =
+ gb->apu.shadow_sweep_sample_legnth =
+ gb->apu.new_sweep_sample_legnth;
+ }
+
+ if (gb->io_registers[GB_IO_NR10] & 0x70) {
+ /* Recalculation and overflow check only occurs after a delay */
+ gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div;
+ }
+
+ gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7);
+ if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8;
+ }
+ }
+ }
+}
+
+
+void GB_apu_run(GB_gameboy_t *gb)
+{
+ /* Convert 4MHZ to 2MHz. apu_cycles is always divisable by 4. */
+ uint8_t cycles = gb->apu.apu_cycles >> 2;
+ gb->apu.apu_cycles = 0;
+ if (!cycles) return;
+
+ if (likely(!gb->stopped || GB_is_cgb(gb))) {
+ /* To align the square signal to 1MHz */
+ gb->apu.lf_div ^= cycles & 1;
+ gb->apu.noise_channel.alignment += cycles;
+
+ if (gb->apu.square_sweep_calculate_countdown) {
+ if (gb->apu.square_sweep_calculate_countdown > cycles) {
+ gb->apu.square_sweep_calculate_countdown -= cycles;
+ }
+ else {
+ /* APU bug: sweep frequency is checked after adding the sweep delta twice */
+ gb->apu.new_sweep_sample_legnth = new_sweep_sample_legnth(gb);
+ if (gb->apu.new_sweep_sample_legnth > 0x7ff) {
+ gb->apu.is_active[GB_SQUARE_1] = false;
+ update_sample(gb, GB_SQUARE_1, 0, gb->apu.square_sweep_calculate_countdown - cycles);
+ gb->apu.sweep_enabled = false;
+ }
+ gb->apu.sweep_decreasing |= gb->io_registers[GB_IO_NR10] & 8;
+ gb->apu.square_sweep_calculate_countdown = 0;
+ }
+ }
+
+ UNROLL
+ for (unsigned i = GB_SQUARE_1; i <= GB_SQUARE_2; i++) {
+ if (gb->apu.is_active[i]) {
+ uint8_t cycles_left = cycles;
+ while (unlikely(cycles_left > gb->apu.square_channels[i].sample_countdown)) {
+ cycles_left -= gb->apu.square_channels[i].sample_countdown + 1;
+ gb->apu.square_channels[i].sample_countdown = (gb->apu.square_channels[i].sample_length ^ 0x7FF) * 2 + 1;
+ gb->apu.square_channels[i].current_sample_index++;
+ gb->apu.square_channels[i].current_sample_index &= 0x7;
+
+ update_square_sample(gb, i);
+ }
+ if (cycles_left) {
+ gb->apu.square_channels[i].sample_countdown -= cycles_left;
+ }
+ }
+ }
+
+ gb->apu.wave_channel.wave_form_just_read = false;
+ if (gb->apu.is_active[GB_WAVE]) {
+ uint8_t cycles_left = cycles;
+ while (unlikely(cycles_left > gb->apu.wave_channel.sample_countdown)) {
+ cycles_left -= gb->apu.wave_channel.sample_countdown + 1;
+ gb->apu.wave_channel.sample_countdown = gb->apu.wave_channel.sample_length ^ 0x7FF;
+ gb->apu.wave_channel.current_sample_index++;
+ gb->apu.wave_channel.current_sample_index &= 0x1F;
+ gb->apu.wave_channel.current_sample =
+ gb->apu.wave_channel.wave_form[gb->apu.wave_channel.current_sample_index];
+ update_sample(gb, GB_WAVE,
+ gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift,
+ cycles - cycles_left);
+ gb->apu.wave_channel.wave_form_just_read = true;
+ }
+ if (cycles_left) {
+ gb->apu.wave_channel.sample_countdown -= cycles_left;
+ gb->apu.wave_channel.wave_form_just_read = false;
+ }
+ }
+
+ if (gb->apu.is_active[GB_NOISE]) {
+ uint8_t cycles_left = cycles;
+ while (unlikely(cycles_left > gb->apu.noise_channel.sample_countdown)) {
+ cycles_left -= gb->apu.noise_channel.sample_countdown + 1;
+ gb->apu.noise_channel.sample_countdown = gb->apu.noise_channel.sample_length * 4 + 3;
+
+ /* Step LFSR */
+ unsigned high_bit_mask = gb->apu.noise_channel.narrow ? 0x4040 : 0x4000;
+ /* Todo: is this formula is different on a GBA? */
+ bool new_high_bit = (gb->apu.noise_channel.lfsr ^ (gb->apu.noise_channel.lfsr >> 1) ^ 1) & 1;
+ gb->apu.noise_channel.lfsr >>= 1;
+
+ if (new_high_bit) {
+ gb->apu.noise_channel.lfsr |= high_bit_mask;
+ }
+ else {
+ /* This code is not redundent, it's relevant when switching LFSR widths */
+ gb->apu.noise_channel.lfsr &= ~high_bit_mask;
+ }
+
+ gb->apu.current_lfsr_sample = gb->apu.noise_channel.lfsr & 1;
+
+ update_sample(gb, GB_NOISE,
+ gb->apu.current_lfsr_sample ?
+ gb->apu.noise_channel.current_volume : 0,
+ 0);
+ }
+ if (cycles_left) {
+ gb->apu.noise_channel.sample_countdown -= cycles_left;
+ }
+ }
+ }
+
+ if (gb->apu_output.sample_rate) {
+ gb->apu_output.cycles_since_render += cycles;
+
+ if (gb->apu_output.sample_cycles >= gb->apu_output.cycles_per_sample) {
+ gb->apu_output.sample_cycles -= gb->apu_output.cycles_per_sample;
+ render(gb);
+ }
+ }
+}
+void GB_apu_init(GB_gameboy_t *gb)
+{
+ memset(&gb->apu, 0, sizeof(gb->apu));
+ /* Restore the wave form */
+ for (unsigned reg = GB_IO_WAV_START; reg <= GB_IO_WAV_END; reg++) {
+ gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = gb->io_registers[reg] >> 4;
+ gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = gb->io_registers[reg] & 0xF;
+ }
+ gb->apu.lf_div = 1;
+ /* APU glitch: When turning the APU on while DIV's bit 4 (or 5 in double speed mode) is on,
+ the first DIV/APU event is skipped. */
+ if (gb->div_counter & (gb->cgb_double_speed? 0x2000 : 0x1000)) {
+ gb->apu.skip_div_event = true;
+ }
+}
+
+uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg)
+{
+ if (reg == GB_IO_NR52) {
+ uint8_t value = 0;
+ for (int i = 0; i < GB_N_CHANNELS; i++) {
+ value >>= 1;
+ if (gb->apu.is_active[i]) {
+ value |= 0x8;
+ }
+ }
+ if (gb->apu.global_enable) {
+ value |= 0x80;
+ }
+ value |= 0x70;
+ return value;
+ }
+
+ static const char read_mask[GB_IO_WAV_END - GB_IO_NR10 + 1] = {
+ /* NRX0 NRX1 NRX2 NRX3 NRX4 */
+ 0x80, 0x3F, 0x00, 0xFF, 0xBF, // NR1X
+ 0xFF, 0x3F, 0x00, 0xFF, 0xBF, // NR2X
+ 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, // NR3X
+ 0xFF, 0xFF, 0x00, 0x00, 0xBF, // NR4X
+ 0x00, 0x00, 0x70, 0xFF, 0xFF, // NR5X
+
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Unused
+ // Wave RAM
+ 0, /* ... */
+ };
+
+ if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) {
+ if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) {
+ return 0xFF;
+ }
+ reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
+ }
+
+ return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10];
+}
+
+void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
+{
+ if (!gb->apu.global_enable && reg != GB_IO_NR52 && reg < GB_IO_WAV_START && (GB_is_cgb(gb) ||
+ (
+ reg != GB_IO_NR11 &&
+ reg != GB_IO_NR21 &&
+ reg != GB_IO_NR31 &&
+ reg != GB_IO_NR41
+ )
+ )) {
+ return;
+ }
+
+ if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.is_active[GB_WAVE]) {
+ if (!GB_is_cgb(gb) && !gb->apu.wave_channel.wave_form_just_read) {
+ return;
+ }
+ reg = GB_IO_WAV_START + gb->apu.wave_channel.current_sample_index / 2;
+ }
+
+ /* Todo: this can and should be rewritten with a function table. */
+ switch (reg) {
+ /* Globals */
+ case GB_IO_NR50:
+ case GB_IO_NR51:
+ gb->io_registers[reg] = value;
+ /* These registers affect the output of all 4 channels (but not the output of the PCM registers).*/
+ /* We call update_samples with the current value so the APU output is updated with the new outputs */
+ for (unsigned i = GB_N_CHANNELS; i--;) {
+ update_sample(gb, i, gb->apu.samples[i], 0);
+ }
+ break;
+ case GB_IO_NR52: {
+
+ uint8_t old_nrx1[] = {
+ gb->io_registers[GB_IO_NR11],
+ gb->io_registers[GB_IO_NR21],
+ gb->io_registers[GB_IO_NR31],
+ gb->io_registers[GB_IO_NR41]
+ };
+ if ((value & 0x80) && !gb->apu.global_enable) {
+ GB_apu_init(gb);
+ gb->apu.global_enable = true;
+ }
+ else if (!(value & 0x80) && gb->apu.global_enable) {
+ for (unsigned i = GB_N_CHANNELS; i--;) {
+ update_sample(gb, i, 0, 0);
+ }
+ memset(&gb->apu, 0, sizeof(gb->apu));
+ memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10);
+ old_nrx1[0] &= 0x3F;
+ old_nrx1[1] &= 0x3F;
+
+ gb->apu.global_enable = false;
+ }
+
+ if (!GB_is_cgb(gb) && (value & 0x80)) {
+ GB_apu_write(gb, GB_IO_NR11, old_nrx1[0]);
+ GB_apu_write(gb, GB_IO_NR21, old_nrx1[1]);
+ GB_apu_write(gb, GB_IO_NR31, old_nrx1[2]);
+ GB_apu_write(gb, GB_IO_NR41, old_nrx1[3]);
+ }
+ }
+ break;
+
+ /* Square channels */
+ case GB_IO_NR10:
+ if (gb->apu.sweep_decreasing && !(value & 8)) {
+ gb->apu.is_active[GB_SQUARE_1] = false;
+ update_sample(gb, GB_SQUARE_1, 0, 0);
+ gb->apu.sweep_enabled = false;
+ gb->apu.square_sweep_calculate_countdown = 0;
+ }
+ if ((value & 0x70) == 0) {
+ /* Todo: what happens if we set period to 0 while a calculate event is scheduled, and then
+ re-set it to non-zero? */
+ gb->apu.square_sweep_calculate_countdown = 0;
+ }
+ break;
+
+ case GB_IO_NR11:
+ case GB_IO_NR21: {
+ unsigned index = reg == GB_IO_NR21? GB_SQUARE_2: GB_SQUARE_1;
+ gb->apu.square_channels[index].pulse_length = (0x40 - (value & 0x3f));
+ if (!gb->apu.global_enable) {
+ value &= 0x3f;
+ }
+ break;
+ }
+
+ case GB_IO_NR12:
+ case GB_IO_NR22: {
+ unsigned index = reg == GB_IO_NR22? GB_SQUARE_2: GB_SQUARE_1;
+ if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) {
+ /* Envelope disabled */
+ gb->apu.square_channels[index].volume_countdown = 0;
+ }
+ if ((value & 0xF8) == 0) {
+ /* This disables the DAC */
+ gb->io_registers[reg] = value;
+ gb->apu.is_active[index] = false;
+ update_sample(gb, index, 0, 0);
+ }
+ else if (gb->apu.is_active[index]) {
+ nrx2_glitch(&gb->apu.square_channels[index].current_volume, value, gb->io_registers[reg]);
+ update_square_sample(gb, index);
+ }
+
+ break;
+ }
+
+ case GB_IO_NR13:
+ case GB_IO_NR23: {
+ unsigned index = reg == GB_IO_NR23? GB_SQUARE_2: GB_SQUARE_1;
+ gb->apu.square_channels[index].sample_length &= ~0xFF;
+ gb->apu.square_channels[index].sample_length |= value & 0xFF;
+ break;
+ }
+
+ case GB_IO_NR14:
+ case GB_IO_NR24: {
+ unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1;
+
+ /* TODO: When the sample length changes right before being updated, the countdown should change to the
+ old length, but the current sample should not change. Because our write timing isn't accurate to
+ the T-cycle, we hack around it by stepping the sample index backwards. */
+ if ((value & 0x80) == 0 && gb->apu.is_active[index]) {
+ /* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on
+ double speed. */
+ if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */ || gb->apu.square_channels[index].sample_countdown & 1) {
+ if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
+ gb->apu.square_channels[index].current_sample_index--;
+ gb->apu.square_channels[index].current_sample_index &= 7;
+ }
+ }
+ }
+
+ gb->apu.square_channels[index].sample_length &= 0xFF;
+ gb->apu.square_channels[index].sample_length |= (value & 7) << 8;
+ if (index == GB_SQUARE_1) {
+ gb->apu.shadow_sweep_sample_legnth =
+ gb->apu.new_sweep_sample_legnth =
+ gb->apu.square_channels[0].sample_length;
+ }
+ if (value & 0x80) {
+ /* Current sample index remains unchanged when restarting channels 1 or 2. It is only reset by
+ turning the APU off. */
+ if (!gb->apu.is_active[index]) {
+ gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 6 - gb->apu.lf_div;
+ }
+ else {
+ /* Timing quirk: if already active, sound starts 2 (2MHz) ticks earlier.*/
+ gb->apu.square_channels[index].sample_countdown = (gb->apu.square_channels[index].sample_length ^ 0x7FF) * 2 + 4 - gb->apu.lf_div;
+ }
+ gb->apu.square_channels[index].current_volume = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] >> 4;
+
+ /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously
+ started sound). The playback itself is not instant which is why we don't update the sample for other
+ cases. */
+ if (gb->apu.is_active[index]) {
+ update_square_sample(gb, index);
+ }
+
+ gb->apu.square_channels[index].volume_countdown = gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 7;
+
+ if ((gb->io_registers[index == GB_SQUARE_1 ? GB_IO_NR12 : GB_IO_NR22] & 0xF8) != 0 && !gb->apu.is_active[index]) {
+ gb->apu.is_active[index] = true;
+ update_sample(gb, index, 0, 0);
+ /* We use the highest bit in current_sample_index to mark this sample is not actually playing yet, */
+ gb->apu.square_channels[index].current_sample_index |= 0x80;
+ }
+ if (gb->apu.square_channels[index].pulse_length == 0) {
+ gb->apu.square_channels[index].pulse_length = 0x40;
+ gb->apu.square_channels[index].length_enabled = false;
+ }
+
+ if (index == GB_SQUARE_1) {
+ gb->apu.sweep_decreasing = false;
+ if (gb->io_registers[GB_IO_NR10] & 7) {
+ /* APU bug: if shift is nonzero, overflow check also occurs on trigger */
+ gb->apu.square_sweep_calculate_countdown = 0x13 - gb->apu.lf_div;
+ }
+ else {
+ gb->apu.square_sweep_calculate_countdown = 0;
+ }
+ gb->apu.sweep_enabled = gb->io_registers[GB_IO_NR10] & 0x77;
+ gb->apu.square_sweep_countdown = ((gb->io_registers[GB_IO_NR10] >> 4) & 7);
+ if (!gb->apu.square_sweep_countdown) gb->apu.square_sweep_countdown = 8;
+ }
+
+ }
+
+ /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
+ if ((value & 0x40) &&
+ !gb->apu.square_channels[index].length_enabled &&
+ (gb->apu.div_divider & 1) &&
+ gb->apu.square_channels[index].pulse_length) {
+ gb->apu.square_channels[index].pulse_length--;
+ if (gb->apu.square_channels[index].pulse_length == 0) {
+ if (value & 0x80) {
+ gb->apu.square_channels[index].pulse_length = 0x3F;
+ }
+ else {
+ gb->apu.is_active[index] = false;
+ update_sample(gb, index, 0, 0);
+ }
+ }
+ }
+ gb->apu.square_channels[index].length_enabled = value & 0x40;
+ break;
+ }
+
+ /* Wave channel */
+ case GB_IO_NR30:
+ gb->apu.wave_channel.enable = value & 0x80;
+ if (!gb->apu.wave_channel.enable) {
+ gb->apu.is_active[GB_WAVE] = false;
+ update_sample(gb, GB_WAVE, 0, 0);
+ }
+ break;
+ case GB_IO_NR31:
+ gb->apu.wave_channel.pulse_length = (0x100 - value);
+ break;
+ case GB_IO_NR32:
+ gb->apu.wave_channel.shift = (uint8_t[]){4, 0, 1, 2}[(value >> 5) & 3];
+ if (gb->apu.is_active[GB_WAVE]) {
+ update_sample(gb, GB_WAVE, gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift, 0);
+ }
+ break;
+ case GB_IO_NR33:
+ gb->apu.wave_channel.sample_length &= ~0xFF;
+ gb->apu.wave_channel.sample_length |= value & 0xFF;
+ break;
+ case GB_IO_NR34:
+ gb->apu.wave_channel.sample_length &= 0xFF;
+ gb->apu.wave_channel.sample_length |= (value & 7) << 8;
+ if ((value & 0x80)) {
+ /* DMG bug: wave RAM gets corrupted if the channel is retriggerred 1 cycle before the APU
+ reads from it. */
+ if (!GB_is_cgb(gb) &&
+ gb->apu.is_active[GB_WAVE] &&
+ gb->apu.wave_channel.sample_countdown == 0 &&
+ gb->apu.wave_channel.enable) {
+ unsigned offset = ((gb->apu.wave_channel.current_sample_index + 1) >> 1) & 0xF;
+
+ /* This glitch varies between models and even specific instances:
+ DMG-B: Most of them behave as emulated. A few behave differently.
+ SGB: As far as I know, all tested instances behave as emulated.
+ MGB, SGB2: Most instances behave non-deterministically, a few behave as emulated.
+
+ Additionally, I believe DMGs, including those we behave differently than emulated,
+ are all deterministic. */
+ if (offset < 4) {
+ gb->io_registers[GB_IO_WAV_START] = gb->io_registers[GB_IO_WAV_START + offset];
+ gb->apu.wave_channel.wave_form[0] = gb->apu.wave_channel.wave_form[offset / 2];
+ gb->apu.wave_channel.wave_form[1] = gb->apu.wave_channel.wave_form[offset / 2 + 1];
+ }
+ else {
+ memcpy(gb->io_registers + GB_IO_WAV_START,
+ gb->io_registers + GB_IO_WAV_START + (offset & ~3),
+ 4);
+ memcpy(gb->apu.wave_channel.wave_form,
+ gb->apu.wave_channel.wave_form + (offset & ~3) * 2,
+ 8);
+ }
+ }
+ if (!gb->apu.is_active[GB_WAVE]) {
+ gb->apu.is_active[GB_WAVE] = true;
+ update_sample(gb, GB_WAVE,
+ gb->apu.wave_channel.current_sample >> gb->apu.wave_channel.shift,
+ 0);
+ }
+ gb->apu.wave_channel.sample_countdown = (gb->apu.wave_channel.sample_length ^ 0x7FF) + 3;
+ gb->apu.wave_channel.current_sample_index = 0;
+ if (gb->apu.wave_channel.pulse_length == 0) {
+ gb->apu.wave_channel.pulse_length = 0x100;
+ gb->apu.wave_channel.length_enabled = false;
+ }
+ /* Note that we don't change the sample just yet! This was verified on hardware. */
+ }
+
+ /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
+ if ((value & 0x40) &&
+ !gb->apu.wave_channel.length_enabled &&
+ (gb->apu.div_divider & 1) &&
+ gb->apu.wave_channel.pulse_length) {
+ gb->apu.wave_channel.pulse_length--;
+ if (gb->apu.wave_channel.pulse_length == 0) {
+ if (value & 0x80) {
+ gb->apu.wave_channel.pulse_length = 0xFF;
+ }
+ else {
+ gb->apu.is_active[GB_WAVE] = false;
+ update_sample(gb, GB_WAVE, 0, 0);
+ }
+ }
+ }
+ gb->apu.wave_channel.length_enabled = value & 0x40;
+ if (gb->apu.is_active[GB_WAVE] && !gb->apu.wave_channel.enable) {
+ gb->apu.is_active[GB_WAVE] = false;
+ update_sample(gb, GB_WAVE, 0, 0);
+ }
+
+ break;
+
+ /* Noise Channel */
+
+ case GB_IO_NR41: {
+ gb->apu.noise_channel.pulse_length = (0x40 - (value & 0x3f));
+ break;
+ }
+
+ case GB_IO_NR42: {
+ if (((value & 0x7) == 0) && ((gb->io_registers[reg] & 0x7) != 0)) {
+ /* Envelope disabled */
+ gb->apu.noise_channel.volume_countdown = 0;
+ }
+ if ((value & 0xF8) == 0) {
+ /* This disables the DAC */
+ gb->io_registers[reg] = value;
+ gb->apu.is_active[GB_NOISE] = false;
+ update_sample(gb, GB_NOISE, 0, 0);
+ }
+ else if (gb->apu.is_active[GB_NOISE]){
+ nrx2_glitch(&gb->apu.noise_channel.current_volume, value, gb->io_registers[reg]);
+ update_sample(gb, GB_NOISE,
+ gb->apu.current_lfsr_sample ?
+ gb->apu.noise_channel.current_volume : 0,
+ 0);
+ }
+ break;
+ }
+
+ case GB_IO_NR43: {
+ gb->apu.noise_channel.narrow = value & 8;
+ unsigned divisor = (value & 0x07) << 1;
+ if (!divisor) divisor = 1;
+ gb->apu.noise_channel.sample_length = (divisor << (value >> 4)) - 1;
+
+ /* Todo: changing the frequency sometimes delays the next sample. This is probably
+ due to how the frequency is actually calculated in the noise channel, which is probably
+ not by calculating the effective sample length and counting simiarly to the other channels.
+ This is not emulated correctly. */
+ break;
+ }
+
+ case GB_IO_NR44: {
+ if (value & 0x80) {
+ gb->apu.noise_channel.sample_countdown = (gb->apu.noise_channel.sample_length) * 2 + 6 - gb->apu.lf_div;
+
+ /* I'm COMPLETELY unsure about this logic, but it passes all relevant tests.
+ See comment in NR43. */
+ if ((gb->io_registers[GB_IO_NR43] & 7) && (gb->apu.noise_channel.alignment & 2) == 0) {
+ if ((gb->io_registers[GB_IO_NR43] & 7) == 1) {
+ gb->apu.noise_channel.sample_countdown += 2;
+ }
+ else {
+ gb->apu.noise_channel.sample_countdown -= 2;
+ }
+ }
+ if (gb->apu.is_active[GB_NOISE]) {
+ gb->apu.noise_channel.sample_countdown += 2;
+ }
+
+ gb->apu.noise_channel.current_volume = gb->io_registers[GB_IO_NR42] >> 4;
+
+ /* The volume changes caused by NRX4 sound start take effect instantly (i.e. the effect the previously
+ started sound). The playback itself is not instant which is why we don't update the sample for other
+ cases. */
+ if (gb->apu.is_active[GB_NOISE]) {
+ update_sample(gb, GB_NOISE,
+ gb->apu.current_lfsr_sample ?
+ gb->apu.noise_channel.current_volume : 0,
+ 0);
+ }
+ gb->apu.noise_channel.lfsr = 0;
+ gb->apu.current_lfsr_sample = false;
+ gb->apu.noise_channel.volume_countdown = gb->io_registers[GB_IO_NR42] & 7;
+
+ if (!gb->apu.is_active[GB_NOISE] && (gb->io_registers[GB_IO_NR42] & 0xF8) != 0) {
+ gb->apu.is_active[GB_NOISE] = true;
+ update_sample(gb, GB_NOISE, 0, 0);
+ }
+
+ if (gb->apu.noise_channel.pulse_length == 0) {
+ gb->apu.noise_channel.pulse_length = 0x40;
+ gb->apu.noise_channel.length_enabled = false;
+ }
+ }
+
+ /* APU glitch - if length is enabled while the DIV-divider's LSB is 1, tick the length once. */
+ if ((value & 0x40) &&
+ !gb->apu.noise_channel.length_enabled &&
+ (gb->apu.div_divider & 1) &&
+ gb->apu.noise_channel.pulse_length) {
+ gb->apu.noise_channel.pulse_length--;
+ if (gb->apu.noise_channel.pulse_length == 0) {
+ if (value & 0x80) {
+ gb->apu.noise_channel.pulse_length = 0x3F;
+ }
+ else {
+ gb->apu.is_active[GB_NOISE] = false;
+ update_sample(gb, GB_NOISE, 0, 0);
+ }
+ }
+ }
+ gb->apu.noise_channel.length_enabled = value & 0x40;
+ break;
+ }
+
+ default:
+ if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) {
+ gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4;
+ gb->apu.wave_channel.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF;
+ }
+ }
+ gb->io_registers[reg] = value;
+}
+
+void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate)
+{
+
+ gb->apu_output.sample_rate = sample_rate;
+ if (sample_rate) {
+ gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate);
+ }
+ gb->apu_output.rate_set_in_clocks = false;
+ GB_apu_update_cycles_per_sample(gb);
+}
+
+void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample)
+{
+
+ if (cycles_per_sample == 0) {
+ GB_set_sample_rate(gb, 0);
+ return;
+ }
+ gb->apu_output.cycles_per_sample = cycles_per_sample;
+ gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2;
+ gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample);
+ gb->apu_output.rate_set_in_clocks = true;
+}
+
+void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback)
+{
+ gb->apu_output.sample_callback = callback;
+}
+
+void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode)
+{
+ gb->apu_output.highpass_mode = mode;
+}
+
+void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb)
+{
+ if (gb->apu_output.rate_set_in_clocks) return;
+ if (gb->apu_output.sample_rate) {
+ gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */
+ }
+}
diff --git a/gb/Core/apu.h b/gb/Core/apu.h
new file mode 100644
index 0000000..885e0ce
--- /dev/null
+++ b/gb/Core/apu.h
@@ -0,0 +1,164 @@
+#ifndef apu_h
+#define apu_h
+#include
+#include
+#include
+#include "gb_struct_def.h"
+
+
+#ifdef GB_INTERNAL
+/* Speed = 1 / Length (in seconds) */
+#define DAC_DECAY_SPEED 20000
+#define DAC_ATTACK_SPEED 20000
+
+
+/* Divides nicely and never overflows with 4 channels and 8 (1-8) volume levels */
+#ifdef WIIU
+/* Todo: Remove this hack once https://github.com/libretro/RetroArch/issues/6252 is fixed*/
+#define MAX_CH_AMP (0xFF0 / 2)
+#else
+#define MAX_CH_AMP 0xFF0
+#endif
+#define CH_STEP (MAX_CH_AMP/0xF/8)
+#endif
+
+
+
+/* APU ticks are 2MHz, triggered by an internal APU clock. */
+
+typedef struct
+{
+ int16_t left;
+ int16_t right;
+} GB_sample_t;
+
+typedef struct
+{
+ double left;
+ double right;
+} GB_double_sample_t;
+
+enum GB_CHANNELS {
+ GB_SQUARE_1,
+ GB_SQUARE_2,
+ GB_WAVE,
+ GB_NOISE,
+ GB_N_CHANNELS
+};
+
+typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t *sample);
+
+typedef struct
+{
+ bool global_enable;
+ uint8_t apu_cycles;
+
+ uint8_t samples[GB_N_CHANNELS];
+ bool is_active[GB_N_CHANNELS];
+
+ uint8_t div_divider; // The DIV register ticks the APU at 512Hz, but is then divided
+ // once more to generate 128Hz and 64Hz clocks
+
+ uint8_t lf_div; // The APU runs in 2MHz, but channels 1, 2 and 4 run in 1MHZ so we divide
+ // need to divide the signal.
+
+ uint8_t square_sweep_countdown; // In 128Hz
+ uint8_t square_sweep_calculate_countdown; // In 2 MHz
+ uint16_t new_sweep_sample_legnth;
+ uint16_t shadow_sweep_sample_legnth;
+ bool sweep_enabled;
+ bool sweep_decreasing;
+
+ struct {
+ uint16_t pulse_length; // Reloaded from NRX1 (xorred), in 256Hz DIV ticks
+ uint8_t current_volume; // Reloaded from NRX2
+ uint8_t volume_countdown; // Reloaded from NRX2
+ uint8_t current_sample_index; /* For save state compatibility,
+ highest bit is reused (See NR14/NR24's
+ write code)*/
+
+ uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
+ uint16_t sample_length; // From NRX3, NRX4, in APU ticks
+ bool length_enabled; // NRX4
+
+ } square_channels[2];
+
+ struct {
+ bool enable; // NR30
+ uint16_t pulse_length; // Reloaded from NR31 (xorred), in 256Hz DIV ticks
+ uint8_t shift; // NR32
+ uint16_t sample_length; // NR33, NR34, in APU ticks
+ bool length_enabled; // NR34
+
+ uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length, xorred $7FF)
+ uint8_t current_sample_index;
+ uint8_t current_sample; // Current sample before shifting.
+
+ int8_t wave_form[32];
+ bool wave_form_just_read;
+ } wave_channel;
+
+ struct {
+ uint16_t pulse_length; // Reloaded from NR41 (xorred), in 256Hz DIV ticks
+ uint8_t current_volume; // Reloaded from NR42
+ uint8_t volume_countdown; // Reloaded from NR42
+ uint16_t lfsr;
+ bool narrow;
+
+ uint16_t sample_countdown; // in APU ticks (Reloaded from sample_length)
+ uint16_t sample_length; // From NR43, in APU ticks
+ bool length_enabled; // NR44
+
+ uint8_t alignment; // If (NR43 & 7) != 0, samples are aligned to 512KHz clock instead of
+ // 1MHz. This variable keeps track of the alignment.
+
+ } noise_channel;
+
+ bool skip_div_event;
+ bool current_lfsr_sample;
+} GB_apu_t;
+
+typedef enum {
+ GB_HIGHPASS_OFF, // Do not apply any filter, keep DC offset
+ GB_HIGHPASS_ACCURATE, // Apply a highpass filter similar to the one used on hardware
+ GB_HIGHPASS_REMOVE_DC_OFFSET, // Remove DC Offset without affecting the waveform
+ GB_HIGHPASS_MAX
+} GB_highpass_mode_t;
+
+typedef struct {
+ unsigned sample_rate;
+
+ double sample_cycles; // In 8 MHz units
+ double cycles_per_sample;
+
+ // Samples are NOT normalized to MAX_CH_AMP * 4 at this stage!
+ unsigned cycles_since_render;
+ unsigned last_update[GB_N_CHANNELS];
+ GB_sample_t current_sample[GB_N_CHANNELS];
+ GB_sample_t summed_samples[GB_N_CHANNELS];
+ double dac_discharge[GB_N_CHANNELS];
+
+ GB_highpass_mode_t highpass_mode;
+ double highpass_rate;
+ GB_double_sample_t highpass_diff;
+
+ GB_sample_callback_t sample_callback;
+
+ bool rate_set_in_clocks;
+} GB_apu_output_t;
+
+void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
+void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */
+void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
+void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
+#ifdef GB_INTERNAL
+bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index);
+void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value);
+uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg);
+void GB_apu_div_event(GB_gameboy_t *gb);
+void GB_apu_init(GB_gameboy_t *gb);
+void GB_apu_run(GB_gameboy_t *gb);
+void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb);
+#endif
+
+#endif /* apu_h */
diff --git a/gb/Core/camera.c b/gb/Core/camera.c
new file mode 100644
index 0000000..9b34998
--- /dev/null
+++ b/gb/Core/camera.c
@@ -0,0 +1,149 @@
+#include "gb.h"
+
+static int noise_seed = 0;
+
+/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported.
+ We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */
+
+static uint8_t generate_noise(uint8_t x, uint8_t y)
+{
+ int value = (x + y * 128 + noise_seed);
+ uint8_t *data = (uint8_t *) &value;
+ unsigned hash = 0;
+
+ while ((int *) data != &value + 1) {
+ hash ^= (*data << 8);
+ if (hash & 0x8000) {
+ hash ^= 0x8a00;
+ hash ^= *data;
+ }
+ data++;
+ hash <<= 1;
+ }
+ return (hash >> 8);
+}
+
+static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y)
+{
+ if (x >= 128) {
+ x = 0;
+ }
+ if (y >= 112) {
+ y = 0;
+ }
+
+ long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y));
+
+ static const double gain_values[] =
+ {0.8809390, 0.9149149, 0.9457498, 0.9739758,
+ 1.0000000, 1.0241412, 1.0466537, 1.0677433,
+ 1.0875793, 1.1240310, 1.1568911, 1.1868043,
+ 1.2142561, 1.2396208, 1.2743837, 1.3157323,
+ 1.3525190, 1.3856512, 1.4157897, 1.4434309,
+ 1.4689574, 1.4926697, 1.5148087, 1.5355703,
+ 1.5551159, 1.5735801, 1.5910762, 1.6077008,
+ 1.6235366, 1.6386550, 1.6531183, 1.6669808};
+ /* Multiply color by gain value */
+ color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F];
+
+
+ /* Color is multiplied by the exposure register to simulate exposure. */
+ color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000;
+
+ return color;
+}
+
+uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) {
+ /* Forbid reading the image while the camera is busy. */
+ return 0xFF;
+ }
+ uint8_t tile_x = addr / 0x10 % 0x10;
+ uint8_t tile_y = addr / 0x10 / 0x10;
+
+ uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8;
+ uint8_t bit = addr & 1;
+
+ uint8_t ret = 0;
+
+ for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) {
+
+ long color = get_processed_color(gb, x, y);
+
+ static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5};
+ double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7];
+ if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) {
+ color += (color * 4) * edge_enhancement_ratio;
+ color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio;
+ color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio;
+ color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio;
+ color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio;
+ }
+
+
+ /* The camera's registers are used as a threshold pattern, which defines the dithering */
+ uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START;
+
+ if (color < gb->camera_registers[pattern_base]) {
+ color = 3;
+ }
+ else if (color < gb->camera_registers[pattern_base + 1]) {
+ color = 2;
+ }
+ else if (color < gb->camera_registers[pattern_base + 2]) {
+ color = 1;
+ }
+ else {
+ color = 0;
+ }
+
+ ret <<= 1;
+ ret |= (color >> bit) & 1;
+ }
+
+ return ret;
+}
+
+void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback)
+{
+ gb->camera_get_pixel_callback = callback;
+}
+
+void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback)
+{
+ gb->camera_update_request_callback = callback;
+}
+
+void GB_camera_updated(GB_gameboy_t *gb)
+{
+ gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1;
+}
+
+void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ addr &= 0x7F;
+ if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) {
+ value &= 0x7;
+ noise_seed = rand();
+ if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) {
+ /* If no callback is set, ignore the write as if the camera is instantly done */
+ gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1;
+ gb->camera_update_request_callback(gb);
+ }
+ }
+ else {
+ if (addr >= 0x36) {
+ GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value);
+ return;
+ }
+ gb->camera_registers[addr] = value;
+ }
+}
+uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr)
+{
+ if ((addr & 0x7F) == 0) {
+ return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS];
+ }
+ return 0;
+}
diff --git a/gb/Core/camera.h b/gb/Core/camera.h
new file mode 100644
index 0000000..21c69b6
--- /dev/null
+++ b/gb/Core/camera.h
@@ -0,0 +1,29 @@
+#ifndef camera_h
+#define camera_h
+#include
+#include "gb_struct_def.h"
+
+typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y);
+typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb);
+
+enum {
+ GB_CAMERA_SHOOT_AND_1D_FLAGS = 0,
+ GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1,
+ GB_CAMERA_EXPOSURE_HIGH = 2,
+ GB_CAMERA_EXPOSURE_LOW = 3,
+ GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE = 4,
+ GB_CAMERA_DITHERING_PATTERN_START = 6,
+ GB_CAMERA_DITHERING_PATTERN_END = 0x35,
+};
+
+uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr);
+
+void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback);
+void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback);
+
+void GB_camera_updated(GB_gameboy_t *gb);
+
+void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
+uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr);
+
+#endif
diff --git a/gb/Core/debugger.c b/gb/Core/debugger.c
new file mode 100644
index 0000000..df480f3
--- /dev/null
+++ b/gb/Core/debugger.c
@@ -0,0 +1,2459 @@
+#include
+#include
+#include
+#include "gb.h"
+
+typedef struct {
+ bool has_bank;
+ uint16_t bank:9;
+ uint16_t value;
+} value_t;
+
+typedef struct {
+ enum {
+ LVALUE_MEMORY,
+ LVALUE_MEMORY16,
+ LVALUE_REG16,
+ LVALUE_REG_H,
+ LVALUE_REG_L,
+ } kind;
+ union {
+ uint16_t *register_address;
+ value_t memory_address;
+ };
+} lvalue_t;
+
+#define VALUE_16(x) ((value_t){false, 0, (x)})
+
+struct GB_breakpoint_s {
+ union {
+ struct {
+ uint16_t addr;
+ uint16_t bank; /* -1 = any bank*/
+ };
+ uint32_t key; /* For sorting and comparing */
+ };
+ char *condition;
+ bool is_jump_to;
+};
+
+#define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key)
+
+#define GB_WATCHPOINT_R (1)
+#define GB_WATCHPOINT_W (2)
+
+struct GB_watchpoint_s {
+ union {
+ struct {
+ uint16_t addr;
+ uint16_t bank; /* -1 = any bank*/
+ };
+ uint32_t key; /* For sorting and comparing */
+ };
+ char *condition;
+ uint8_t flags;
+};
+
+#define WP_KEY(x) (((struct GB_watchpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key)
+
+static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (addr < 0x4000) {
+ return gb->mbc_rom0_bank;
+ }
+
+ if (addr < 0x8000) {
+ return gb->mbc_rom_bank;
+ }
+
+ if (addr < 0xD000) {
+ return 0;
+ }
+
+ if (addr < 0xE000) {
+ return gb->cgb_ram_bank;
+ }
+
+ return 0;
+}
+
+typedef struct {
+ uint16_t rom0_bank;
+ uint16_t rom_bank;
+ uint8_t mbc_ram_bank;
+ bool mbc_ram_enable;
+ uint8_t ram_bank;
+ uint8_t vram_bank;
+} banking_state_t;
+
+static inline void save_banking_state(GB_gameboy_t *gb, banking_state_t *state)
+{
+ state->rom0_bank = gb->mbc_rom0_bank;
+ state->rom_bank = gb->mbc_rom_bank;
+ state->mbc_ram_bank = gb->mbc_ram_bank;
+ state->mbc_ram_enable = gb->mbc_ram_enable;
+ state->ram_bank = gb->cgb_ram_bank;
+ state->vram_bank = gb->cgb_vram_bank;
+}
+
+static inline void restore_banking_state(GB_gameboy_t *gb, banking_state_t *state)
+{
+
+ gb->mbc_rom0_bank = state->rom0_bank;
+ gb->mbc_rom_bank = state->rom_bank;
+ gb->mbc_ram_bank = state->mbc_ram_bank;
+ gb->mbc_ram_enable = state->mbc_ram_enable;
+ gb->cgb_ram_bank = state->ram_bank;
+ gb->cgb_vram_bank = state->vram_bank;
+}
+
+static inline void switch_banking_state(GB_gameboy_t *gb, uint16_t bank)
+{
+ gb->mbc_rom0_bank = bank;
+ gb->mbc_rom_bank = bank;
+ gb->mbc_ram_bank = bank;
+ gb->mbc_ram_enable = true;
+ if (GB_is_cgb(gb)) {
+ gb->cgb_ram_bank = bank & 7;
+ gb->cgb_vram_bank = bank & 1;
+ if (gb->cgb_ram_bank == 0) {
+ gb->cgb_ram_bank = 1;
+ }
+ }
+}
+
+static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name)
+{
+ static __thread char output[256];
+ const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, value);
+
+ if (symbol && (value - symbol->addr > 0x1000 || symbol->addr == 0) ) {
+ symbol = NULL;
+ }
+
+ /* Avoid overflow */
+ if (symbol && strlen(symbol->name) > 240) {
+ symbol = NULL;
+ }
+
+ if (!symbol) {
+ sprintf(output, "$%04x", value);
+ }
+
+ else if (symbol->addr == value) {
+ if (prefer_name) {
+ sprintf(output, "%s ($%04x)", symbol->name, value);
+ }
+ else {
+ sprintf(output, "$%04x (%s)", value, symbol->name);
+ }
+ }
+
+ else {
+ if (prefer_name) {
+ sprintf(output, "%s+$%03x ($%04x)", symbol->name, value - symbol->addr, value);
+ }
+ else {
+ sprintf(output, "$%04x (%s+$%03x)", value, symbol->name, value - symbol->addr);
+ }
+ }
+ return output;
+}
+
+static const char *debugger_value_to_string(GB_gameboy_t *gb, value_t value, bool prefer_name)
+{
+ if (!value.has_bank) return value_to_string(gb, value.value, prefer_name);
+
+ static __thread char output[256];
+ const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[value.bank], value.value);
+
+ if (symbol && (value.value - symbol->addr > 0x1000 || symbol->addr == 0) ) {
+ symbol = NULL;
+ }
+
+ /* Avoid overflow */
+ if (symbol && strlen(symbol->name) > 240) {
+ symbol = NULL;
+ }
+
+ if (!symbol) {
+ sprintf(output, "$%02x:$%04x", value.bank, value.value);
+ }
+
+ else if (symbol->addr == value.value) {
+ if (prefer_name) {
+ sprintf(output, "%s ($%02x:$%04x)", symbol->name, value.bank, value.value);
+ }
+ else {
+ sprintf(output, "$%02x:$%04x (%s)", value.bank, value.value, symbol->name);
+ }
+ }
+
+ else {
+ if (prefer_name) {
+ sprintf(output, "%s+$%03x ($%02x:$%04x)", symbol->name, value.value - symbol->addr, value.bank, value.value);
+ }
+ else {
+ sprintf(output, "$%02x:$%04x (%s+$%03x)", value.bank, value.value, symbol->name, value.value - symbol->addr);
+ }
+ }
+ return output;
+}
+
+static value_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue)
+{
+ /* Not used until we add support for operators like += */
+ switch (lvalue.kind) {
+ case LVALUE_MEMORY:
+ if (lvalue.memory_address.has_bank) {
+ banking_state_t state;
+ save_banking_state(gb, &state);
+ switch_banking_state(gb, lvalue.memory_address.bank);
+ value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value));
+ restore_banking_state(gb, &state);
+ return r;
+ }
+ return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value));
+
+ case LVALUE_MEMORY16:
+ if (lvalue.memory_address.has_bank) {
+ banking_state_t state;
+ save_banking_state(gb, &state);
+ switch_banking_state(gb, lvalue.memory_address.bank);
+ value_t r = VALUE_16(GB_read_memory(gb, lvalue.memory_address.value));
+ restore_banking_state(gb, &state);
+ return r;
+ }
+ return VALUE_16(GB_read_memory(gb, lvalue.memory_address.value) |
+ (GB_read_memory(gb, lvalue.memory_address.value + 1) * 0x100));
+
+ case LVALUE_REG16:
+ return VALUE_16(*lvalue.register_address);
+
+ case LVALUE_REG_L:
+ return VALUE_16(*lvalue.register_address & 0x00FF);
+
+ case LVALUE_REG_H:
+ return VALUE_16(*lvalue.register_address >> 8);
+ }
+
+ return VALUE_16(0);
+}
+
+static void write_lvalue(GB_gameboy_t *gb, lvalue_t lvalue, uint16_t value)
+{
+ switch (lvalue.kind) {
+ case LVALUE_MEMORY:
+ if (lvalue.memory_address.has_bank) {
+ banking_state_t state;
+ save_banking_state(gb, &state);
+ switch_banking_state(gb, lvalue.memory_address.bank);
+ GB_write_memory(gb, lvalue.memory_address.value, value);
+ restore_banking_state(gb, &state);
+ return;
+ }
+ GB_write_memory(gb, lvalue.memory_address.value, value);
+ return;
+
+ case LVALUE_MEMORY16:
+ if (lvalue.memory_address.has_bank) {
+ banking_state_t state;
+ save_banking_state(gb, &state);
+ switch_banking_state(gb, lvalue.memory_address.bank);
+ GB_write_memory(gb, lvalue.memory_address.value, value);
+ restore_banking_state(gb, &state);
+ return;
+ }
+ GB_write_memory(gb, lvalue.memory_address.value, value);
+ GB_write_memory(gb, lvalue.memory_address.value + 1, value >> 8);
+ return;
+
+ case LVALUE_REG16:
+ *lvalue.register_address = value;
+ return;
+
+ case LVALUE_REG_L:
+ *lvalue.register_address &= 0xFF00;
+ *lvalue.register_address |= value & 0xFF;
+ return;
+
+ case LVALUE_REG_H:
+ *lvalue.register_address &= 0x00FF;
+ *lvalue.register_address |= value << 8;
+ return;
+ }
+}
+
+/* 16 bit value 16 bit value = 16 bit value
+ 25 bit address 16 bit value = 25 bit address
+ 16 bit value 25 bit address = 25 bit address
+ 25 bit address 25 bit address = 16 bit value (since adding pointers, for examples, makes no sense)
+
+ Boolean operators always return a 16-bit value
+ */
+#define FIX_BANK(x) ((value_t){a.has_bank ^ b.has_bank, a.has_bank? a.bank : b.bank, (x)})
+
+static value_t add(value_t a, value_t b) {return FIX_BANK(a.value + b.value);}
+static value_t sub(value_t a, value_t b) {return FIX_BANK(a.value - b.value);}
+static value_t mul(value_t a, value_t b) {return FIX_BANK(a.value * b.value);}
+static value_t _div(value_t a, value_t b) {
+ if (b.value == 0) {
+ return FIX_BANK(0);
+ }
+ return FIX_BANK(a.value / b.value);
+};
+static value_t mod(value_t a, value_t b) {
+ if (b.value == 0) {
+ return FIX_BANK(0);
+ }
+ return FIX_BANK(a.value % b.value);
+};
+static value_t and(value_t a, value_t b) {return FIX_BANK(a.value & b.value);}
+static value_t or(value_t a, value_t b) {return FIX_BANK(a.value | b.value);}
+static value_t xor(value_t a, value_t b) {return FIX_BANK(a.value ^ b.value);}
+static value_t shleft(value_t a, value_t b) {return FIX_BANK(a.value << b.value);}
+static value_t shright(value_t a, value_t b) {return FIX_BANK(a.value >> b.value);}
+static value_t assign(GB_gameboy_t *gb, lvalue_t a, uint16_t b)
+{
+ write_lvalue(gb, a, b);
+ return read_lvalue(gb, a);
+}
+
+static value_t bool_and(value_t a, value_t b) {return VALUE_16(a.value && b.value);}
+static value_t bool_or(value_t a, value_t b) {return VALUE_16(a.value || b.value);}
+static value_t equals(value_t a, value_t b) {return VALUE_16(a.value == b.value);}
+static value_t different(value_t a, value_t b) {return VALUE_16(a.value != b.value);}
+static value_t lower(value_t a, value_t b) {return VALUE_16(a.value < b.value);}
+static value_t greater(value_t a, value_t b) {return VALUE_16(a.value > b.value);}
+static value_t lower_equals(value_t a, value_t b) {return VALUE_16(a.value <= b.value);}
+static value_t greater_equals(value_t a, value_t b) {return VALUE_16(a.value >= b.value);}
+static value_t bank(value_t a, value_t b) {return (value_t) {true, a.value, b.value};}
+
+
+static struct {
+ const char *string;
+ char priority;
+ value_t (*operator)(value_t, value_t);
+ value_t (*lvalue_operator)(GB_gameboy_t *, lvalue_t, uint16_t);
+} operators[] =
+{
+ // Yes. This is not C-like. But it makes much more sense.
+ // Deal with it.
+ {"+", 0, add},
+ {"-", 0, sub},
+ {"||", 0, bool_or},
+ {"|", 0, or},
+ {"*", 1, mul},
+ {"/", 1, _div},
+ {"%", 1, mod},
+ {"&&", 1, bool_and},
+ {"&", 1, and},
+ {"^", 1, xor},
+ {"<<", 2, shleft},
+ {"<=", -1, lower_equals},
+ {"<", -1, lower},
+ {">>", 2, shright},
+ {">=", -1, greater_equals},
+ {">", -1, greater},
+ {"==", -1, equals},
+ {"=", -2, NULL, assign},
+ {"!=", -1, different},
+ {":", 3, bank},
+};
+
+value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
+ size_t length, bool *error,
+ uint16_t *watchpoint_address, uint8_t *watchpoint_new_value);
+
+static lvalue_t debugger_evaluate_lvalue(GB_gameboy_t *gb, const char *string,
+ size_t length, bool *error,
+ uint16_t *watchpoint_address, uint8_t *watchpoint_new_value)
+{
+ *error = false;
+ // Strip whitespace
+ while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) {
+ string++;
+ length--;
+ }
+ while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
+ length--;
+ }
+ if (length == 0)
+ {
+ GB_log(gb, "Expected expression.\n");
+ *error = true;
+ return (lvalue_t){0,};
+ }
+ if (string[0] == '(' && string[length - 1] == ')') {
+ // Attempt to strip parentheses
+ signed int depth = 0;
+ for (int i = 0; i < length; i++) {
+ if (string[i] == '(') depth++;
+ if (depth == 0) {
+ // First and last are not matching
+ depth = 1;
+ break;
+ }
+ if (string[i] == ')') depth--;
+ }
+ if (depth == 0) return debugger_evaluate_lvalue(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value);
+ }
+ else if (string[0] == '[' && string[length - 1] == ']') {
+ // Attempt to strip square parentheses (memory dereference)
+ signed int depth = 0;
+ for (int i = 0; i < length; i++) {
+ if (string[i] == '[') depth++;
+ if (depth == 0) {
+ // First and last are not matching
+ depth = 1;
+ break;
+ }
+ if (string[i] == ']') depth--;
+ }
+ if (depth == 0) {
+ return (lvalue_t){LVALUE_MEMORY, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)};
+ }
+ }
+ else if (string[0] == '{' && string[length - 1] == '}') {
+ // Attempt to strip curly parentheses (memory dereference)
+ signed int depth = 0;
+ for (int i = 0; i < length; i++) {
+ if (string[i] == '{') depth++;
+ if (depth == 0) {
+ // First and last are not matching
+ depth = 1;
+ break;
+ }
+ if (string[i] == '}') depth--;
+ }
+ if (depth == 0) {
+ return (lvalue_t){LVALUE_MEMORY16, .memory_address = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value)};
+ }
+ }
+
+ // Registers
+ if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) {
+ if (length == 1) {
+ switch (string[0]) {
+ case 'a': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_AF]};
+ case 'f': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_AF]};
+ case 'b': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_BC]};
+ case 'c': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_BC]};
+ case 'd': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_DE]};
+ case 'e': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_DE]};
+ case 'h': return (lvalue_t){LVALUE_REG_H, .register_address = &gb->registers[GB_REGISTER_HL]};
+ case 'l': return (lvalue_t){LVALUE_REG_L, .register_address = &gb->registers[GB_REGISTER_HL]};
+ }
+ }
+ else if (length == 2) {
+ switch (string[0]) {
+ case 'a': if (string[1] == 'f') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_AF]};
+ case 'b': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_BC]};
+ case 'd': if (string[1] == 'e') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_DE]};
+ case 'h': if (string[1] == 'l') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_HL]};
+ case 's': if (string[1] == 'p') return (lvalue_t){LVALUE_REG16, .register_address = &gb->registers[GB_REGISTER_SP]};
+ case 'p': if (string[1] == 'c') return (lvalue_t){LVALUE_REG16, .register_address = &gb->pc};
+ }
+ }
+ GB_log(gb, "Unknown register: %.*s\n", (unsigned) length, string);
+ *error = true;
+ return (lvalue_t){0,};
+ }
+
+ GB_log(gb, "Expression is not an lvalue: %.*s\n", (unsigned) length, string);
+ *error = true;
+ return (lvalue_t){0,};
+}
+
+#define ERROR ((value_t){0,})
+value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
+ size_t length, bool *error,
+ uint16_t *watchpoint_address, uint8_t *watchpoint_new_value)
+{
+ /* Disable watchpoints while evaulating expressions */
+ uint16_t n_watchpoints = gb->n_watchpoints;
+ gb->n_watchpoints = 0;
+
+ value_t ret = ERROR;
+
+ *error = false;
+ // Strip whitespace
+ while (length && (string[0] == ' ' || string[0] == '\n' || string[0] == '\r' || string[0] == '\t')) {
+ string++;
+ length--;
+ }
+ while (length && (string[length-1] == ' ' || string[length-1] == '\n' || string[length-1] == '\r' || string[length-1] == '\t')) {
+ length--;
+ }
+ if (length == 0)
+ {
+ GB_log(gb, "Expected expression.\n");
+ *error = true;
+ goto exit;
+ }
+ if (string[0] == '(' && string[length - 1] == ')') {
+ // Attempt to strip parentheses
+ signed int depth = 0;
+ for (int i = 0; i < length; i++) {
+ if (string[i] == '(') depth++;
+ if (depth == 0) {
+ // First and last are not matching
+ depth = 1;
+ break;
+ }
+ if (string[i] == ')') depth--;
+ }
+ if (depth == 0) {
+ ret = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value);
+ goto exit;
+ }
+ }
+ else if (string[0] == '[' && string[length - 1] == ']') {
+ // Attempt to strip square parentheses (memory dereference)
+ signed int depth = 0;
+ for (int i = 0; i < length; i++) {
+ if (string[i] == '[') depth++;
+ if (depth == 0) {
+ // First and last are not matching
+ depth = 1;
+ break;
+ }
+ if (string[i] == ']') depth--;
+ }
+
+ if (depth == 0) {
+ value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value);
+ banking_state_t state;
+ if (addr.bank) {
+ save_banking_state(gb, &state);
+ switch_banking_state(gb, addr.bank);
+ }
+ ret = VALUE_16(GB_read_memory(gb, addr.value));
+ if (addr.bank) {
+ restore_banking_state(gb, &state);
+ }
+ goto exit;
+ }
+ }
+ else if (string[0] == '{' && string[length - 1] == '}') {
+ // Attempt to strip curly parentheses (memory dereference)
+ signed int depth = 0;
+ for (int i = 0; i < length; i++) {
+ if (string[i] == '{') depth++;
+ if (depth == 0) {
+ // First and last are not matching
+ depth = 1;
+ break;
+ }
+ if (string[i] == '}') depth--;
+ }
+
+ if (depth == 0) {
+ value_t addr = debugger_evaluate(gb, string + 1, length - 2, error, watchpoint_address, watchpoint_new_value);
+ banking_state_t state;
+ if (addr.bank) {
+ save_banking_state(gb, &state);
+ switch_banking_state(gb, addr.bank);
+ }
+ ret = VALUE_16(GB_read_memory(gb, addr.value) | (GB_read_memory(gb, addr.value + 1) * 0x100));
+ if (addr.bank) {
+ restore_banking_state(gb, &state);
+ }
+ goto exit;
+ }
+ }
+ // Search for lowest priority operator
+ signed int depth = 0;
+ unsigned operator_index = -1;
+ unsigned operator_pos = 0;
+ for (int i = 0; i < length; i++) {
+ if (string[i] == '(') depth++;
+ else if (string[i] == ')') depth--;
+ else if (string[i] == '[') depth++;
+ else if (string[i] == ']') depth--;
+ else if (depth == 0) {
+ for (int j = 0; j < sizeof(operators) / sizeof(operators[0]); j++) {
+ if (strlen(operators[j].string) > length - i) continue; // Operator too big.
+ // Priority higher than what we already have.
+ unsigned long operator_length = strlen(operators[j].string);
+ if (memcmp(string + i, operators[j].string, operator_length) == 0) {
+ if (operator_index != -1 && operators[operator_index].priority < operators[j].priority) {
+ /* for supporting = vs ==, etc*/
+ i += operator_length - 1;
+ continue;
+ }
+ // Found an operator!
+ operator_pos = i;
+ operator_index = j;
+ /* for supporting = vs ==, etc*/
+ i += operator_length - 1;
+ break;
+ }
+ }
+ }
+ }
+ if (operator_index != -1) {
+ unsigned right_start = (unsigned)(operator_pos + strlen(operators[operator_index].string));
+ value_t right = debugger_evaluate(gb, string + right_start, length - right_start, error, watchpoint_address, watchpoint_new_value);
+ if (*error) goto exit;
+ if (operators[operator_index].lvalue_operator) {
+ lvalue_t left = debugger_evaluate_lvalue(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value);
+ if (*error) goto exit;
+ ret = operators[operator_index].lvalue_operator(gb, left, right.value);
+ goto exit;
+ }
+ value_t left = debugger_evaluate(gb, string, operator_pos, error, watchpoint_address, watchpoint_new_value);
+ if (*error) goto exit;
+ ret = operators[operator_index].operator(left, right);
+ goto exit;
+ }
+
+ // Not an expression - must be a register or a literal
+
+ // Registers
+ if (string[0] != '$' && (string[0] < '0' || string[0] > '9')) {
+ if (length == 1) {
+ switch (string[0]) {
+ case 'a': ret = VALUE_16(gb->registers[GB_REGISTER_AF] >> 8); goto exit;
+ case 'f': ret = VALUE_16(gb->registers[GB_REGISTER_AF] & 0xFF); goto exit;
+ case 'b': ret = VALUE_16(gb->registers[GB_REGISTER_BC] >> 8); goto exit;
+ case 'c': ret = VALUE_16(gb->registers[GB_REGISTER_BC] & 0xFF); goto exit;
+ case 'd': ret = VALUE_16(gb->registers[GB_REGISTER_DE] >> 8); goto exit;
+ case 'e': ret = VALUE_16(gb->registers[GB_REGISTER_DE] & 0xFF); goto exit;
+ case 'h': ret = VALUE_16(gb->registers[GB_REGISTER_HL] >> 8); goto exit;
+ case 'l': ret = VALUE_16(gb->registers[GB_REGISTER_HL] & 0xFF); goto exit;
+ }
+ }
+ else if (length == 2) {
+ switch (string[0]) {
+ case 'a': if (string[1] == 'f') {ret = VALUE_16(gb->registers[GB_REGISTER_AF]); goto exit;}
+ case 'b': if (string[1] == 'c') {ret = VALUE_16(gb->registers[GB_REGISTER_BC]); goto exit;}
+ case 'd': if (string[1] == 'e') {ret = VALUE_16(gb->registers[GB_REGISTER_DE]); goto exit;}
+ case 'h': if (string[1] == 'l') {ret = VALUE_16(gb->registers[GB_REGISTER_HL]); goto exit;}
+ case 's': if (string[1] == 'p') {ret = VALUE_16(gb->registers[GB_REGISTER_SP]); goto exit;}
+ case 'p': if (string[1] == 'c') {ret = (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}; goto exit;}
+ }
+ }
+ else if (length == 3) {
+ if (watchpoint_address && memcmp(string, "old", 3) == 0) {
+ ret = VALUE_16(GB_read_memory(gb, *watchpoint_address));
+ goto exit;
+ }
+
+ if (watchpoint_new_value && memcmp(string, "new", 3) == 0) {
+ ret = VALUE_16(*watchpoint_new_value);
+ goto exit;
+ }
+
+ /* $new is identical to $old in read conditions */
+ if (watchpoint_address && memcmp(string, "new", 3) == 0) {
+ ret = VALUE_16(GB_read_memory(gb, *watchpoint_address));
+ goto exit;
+ }
+ }
+
+ char symbol_name[length + 1];
+ memcpy(symbol_name, string, length);
+ symbol_name[length] = 0;
+ const GB_symbol_t *symbol = GB_reversed_map_find_symbol(&gb->reversed_symbol_map, symbol_name);
+ if (symbol) {
+ ret = (value_t){true, symbol->bank, symbol->addr};
+ goto exit;
+ }
+
+ GB_log(gb, "Unknown register or symbol: %.*s\n", (unsigned) length, string);
+ *error = true;
+ goto exit;
+ }
+
+ char *end;
+ int base = 10;
+ if (string[0] == '$') {
+ string++;
+ base = 16;
+ length--;
+ }
+ uint16_t literal = (uint16_t) (strtol(string, &end, base));
+ if (end != string + length) {
+ GB_log(gb, "Failed to parse: %.*s\n", (unsigned) length, string);
+ *error = true;
+ goto exit;
+ }
+ ret = VALUE_16(literal);
+exit:
+ gb->n_watchpoints = n_watchpoints;
+ return ret;
+}
+
+struct debugger_command_s;
+typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, char *modifiers, const struct debugger_command_s *command);
+
+typedef struct debugger_command_s {
+ const char *command;
+ uint8_t min_length;
+ debugger_command_imp_t *implementation;
+ const char *help_string; // Null if should not appear in help
+ const char *arguments_format; // For usage message
+ const char *modifiers_format; // For usage message
+} debugger_command_t;
+
+static const char *lstrip(const char *str)
+{
+ while (*str == ' ' || *str == '\t') {
+ str++;
+ }
+ return str;
+}
+
+#define STOPPED_ONLY \
+if (!gb->debug_stopped) { \
+GB_log(gb, "Program is running. \n"); \
+return false; \
+}
+
+#define NO_MODIFIERS \
+if (modifiers) { \
+print_usage(gb, command); \
+return true; \
+}
+
+static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command)
+{
+ GB_log(gb, "Usage: %s", command->command);
+
+ if (command->modifiers_format) {
+ GB_log(gb, "[/%s]", command->modifiers_format);
+ }
+
+ if (command->arguments_format) {
+ GB_log(gb, " %s", command->arguments_format);
+ }
+
+ GB_log(gb, "\n");
+}
+
+static bool cont(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ STOPPED_ONLY
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ gb->debug_stopped = false;
+ return false;
+}
+
+static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ STOPPED_ONLY
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ gb->debug_stopped = false;
+ gb->debug_next_command = true;
+ gb->debug_call_depth = 0;
+ return false;
+}
+
+static bool step(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ STOPPED_ONLY
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ return false;
+}
+
+static bool finish(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ STOPPED_ONLY
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ gb->debug_stopped = false;
+ gb->debug_fin_command = true;
+ gb->debug_call_depth = 0;
+ return false;
+}
+
+static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ STOPPED_ONLY
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ gb->debug_stopped = false;
+ gb->stack_leak_detection = true;
+ gb->debug_call_depth = 0;
+ return false;
+}
+
+static bool registers(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+
+ GB_log(gb, "AF = $%04x (%c%c%c%c)\n", gb->registers[GB_REGISTER_AF], /* AF can't really be an address */
+ (gb->f & GB_CARRY_FLAG)? 'C' : '-',
+ (gb->f & GB_HALF_CARRY_FLAG)? 'H' : '-',
+ (gb->f & GB_SUBSTRACT_FLAG)? 'N' : '-',
+ (gb->f & GB_ZERO_FLAG)? 'Z' : '-');
+ GB_log(gb, "BC = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_BC], false));
+ GB_log(gb, "DE = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_DE], false));
+ GB_log(gb, "HL = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_HL], false));
+ GB_log(gb, "SP = %s\n", value_to_string(gb, gb->registers[GB_REGISTER_SP], false));
+ GB_log(gb, "PC = %s\n", value_to_string(gb, gb->pc, false));
+ return true;
+}
+
+/* Find the index of the closest breakpoint equal or greater to addr */
+static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
+{
+ if (!gb->breakpoints) {
+ return 0;
+ }
+
+ uint32_t key = BP_KEY(addr);
+
+ int min = 0;
+ int max = gb->n_breakpoints;
+ while (min < max) {
+ uint16_t pivot = (min + max) / 2;
+ if (gb->breakpoints[pivot].key == key) return pivot;
+ if (gb->breakpoints[pivot].key > key) {
+ max = pivot;
+ }
+ else {
+ min = pivot + 1;
+ }
+ }
+ return (uint16_t) min;
+}
+
+static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ bool is_jump_to = true;
+ if (!modifiers) {
+ is_jump_to = false;
+ }
+ else if (strcmp(modifiers, "j") != 0) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ if (strlen(lstrip(arguments)) == 0) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ if (gb->n_breakpoints == (typeof(gb->n_breakpoints)) -1) {
+ GB_log(gb, "Too many breakpoints set\n");
+ return true;
+ }
+
+ char *condition = NULL;
+ if ((condition = strstr(arguments, " if "))) {
+ *condition = 0;
+ condition += strlen(" if ");
+ /* Verify condition is sane (Todo: This might have side effects!) */
+ bool error;
+ debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, NULL, NULL);
+ if (error) return true;
+
+ }
+
+ bool error;
+ value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
+ uint32_t key = BP_KEY(result);
+
+ if (error) return true;
+
+ uint16_t index = find_breakpoint(gb, result);
+ if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) {
+ GB_log(gb, "Breakpoint already set at %s\n", debugger_value_to_string(gb, result, true));
+ if (!gb->breakpoints[index].condition && condition) {
+ GB_log(gb, "Added condition to breakpoint\n");
+ gb->breakpoints[index].condition = strdup(condition);
+ }
+ else if (gb->breakpoints[index].condition && condition) {
+ GB_log(gb, "Replaced breakpoint condition\n");
+ free(gb->breakpoints[index].condition);
+ gb->breakpoints[index].condition = strdup(condition);
+ }
+ else if (gb->breakpoints[index].condition && !condition) {
+ GB_log(gb, "Removed breakpoint condition\n");
+ free(gb->breakpoints[index].condition);
+ gb->breakpoints[index].condition = NULL;
+ }
+ return true;
+ }
+
+ gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0]));
+ memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0]));
+ gb->breakpoints[index].key = key;
+
+ if (condition) {
+ gb->breakpoints[index].condition = strdup(condition);
+ }
+ else {
+ gb->breakpoints[index].condition = NULL;
+ }
+ gb->n_breakpoints++;
+
+ gb->breakpoints[index].is_jump_to = is_jump_to;
+
+ if (is_jump_to) {
+ gb->has_jump_to_breakpoints = true;
+ }
+
+ GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true));
+ return true;
+}
+
+static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ if (strlen(lstrip(arguments)) == 0) {
+ for (unsigned i = gb->n_breakpoints; i--;) {
+ if (gb->breakpoints[i].condition) {
+ free(gb->breakpoints[i].condition);
+ }
+ }
+ free(gb->breakpoints);
+ gb->breakpoints = NULL;
+ gb->n_breakpoints = 0;
+ return true;
+ }
+
+ bool error;
+ value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
+ uint32_t key = BP_KEY(result);
+
+ if (error) return true;
+
+ uint16_t index = 0;
+ for (unsigned i = 0; i < gb->n_breakpoints; i++) {
+ if (gb->breakpoints[i].key == key) {
+ /* Full match */
+ index = i;
+ break;
+ }
+ if (gb->breakpoints[i].addr == result.value && result.has_bank != (gb->breakpoints[i].bank != (uint16_t) -1)) {
+ /* Partial match */
+ index = i;
+ }
+ }
+
+ if (index >= gb->n_breakpoints) {
+ GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true));
+ return true;
+ }
+
+ result.bank = gb->breakpoints[index].bank;
+ result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1;
+
+ if (gb->breakpoints[index].condition) {
+ free(gb->breakpoints[index].condition);
+ }
+
+ if (gb->breakpoints[index].is_jump_to) {
+ gb->has_jump_to_breakpoints = false;
+ for (unsigned i = 0; i < gb->n_breakpoints; i++) {
+ if (i == index) continue;
+ if (gb->breakpoints[i].is_jump_to) {
+ gb->has_jump_to_breakpoints = true;
+ break;
+ }
+ }
+ }
+
+ memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0]));
+ gb->n_breakpoints--;
+ gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0]));
+
+ GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true));
+ return true;
+}
+
+/* Find the index of the closest watchpoint equal or greater to addr */
+static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
+{
+ if (!gb->watchpoints) {
+ return 0;
+ }
+ uint32_t key = WP_KEY(addr);
+ int min = 0;
+ int max = gb->n_watchpoints;
+ while (min < max) {
+ uint16_t pivot = (min + max) / 2;
+ if (gb->watchpoints[pivot].key == key) return pivot;
+ if (gb->watchpoints[pivot].key > key) {
+ max = pivot;
+ }
+ else {
+ min = pivot + 1;
+ }
+ }
+ return (uint16_t) min;
+}
+
+static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ if (strlen(lstrip(arguments)) == 0) {
+print_usage:
+ print_usage(gb, command);
+ return true;
+ }
+
+ if (gb->n_watchpoints == (typeof(gb->n_watchpoints)) -1) {
+ GB_log(gb, "Too many watchpoints set\n");
+ return true;
+ }
+
+ if (!modifiers) {
+ modifiers = "w";
+ }
+
+ uint8_t flags = 0;
+ while (*modifiers) {
+ switch (*modifiers) {
+ case 'r':
+ flags |= GB_WATCHPOINT_R;
+ break;
+ case 'w':
+ flags |= GB_WATCHPOINT_W;
+ break;
+ default:
+ goto print_usage;
+ }
+ modifiers++;
+ }
+
+ if (!flags) {
+ goto print_usage;
+ }
+
+ char *condition = NULL;
+ if ((condition = strstr(arguments, " if "))) {
+ *condition = 0;
+ condition += strlen(" if ");
+ /* Verify condition is sane (Todo: This might have side effects!) */
+ bool error;
+ /* To make $new and $old legal */
+ uint16_t dummy = 0;
+ uint8_t dummy2 = 0;
+ debugger_evaluate(gb, condition, (unsigned)strlen(condition), &error, &dummy, &dummy2);
+ if (error) return true;
+
+ }
+
+ bool error;
+ value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
+ uint32_t key = WP_KEY(result);
+
+ if (error) return true;
+
+ uint16_t index = find_watchpoint(gb, result);
+ if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) {
+ GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true));
+ if (gb->watchpoints[index].flags != flags) {
+ GB_log(gb, "Modified watchpoint type\n");
+ gb->watchpoints[index].flags = flags;
+ }
+ if (!gb->watchpoints[index].condition && condition) {
+ GB_log(gb, "Added condition to watchpoint\n");
+ gb->watchpoints[index].condition = strdup(condition);
+ }
+ else if (gb->watchpoints[index].condition && condition) {
+ GB_log(gb, "Replaced watchpoint condition\n");
+ free(gb->watchpoints[index].condition);
+ gb->watchpoints[index].condition = strdup(condition);
+ }
+ else if (gb->watchpoints[index].condition && !condition) {
+ GB_log(gb, "Removed watchpoint condition\n");
+ free(gb->watchpoints[index].condition);
+ gb->watchpoints[index].condition = NULL;
+ }
+ return true;
+ }
+
+ gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0]));
+ memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0]));
+ gb->watchpoints[index].key = key;
+ gb->watchpoints[index].flags = flags;
+ if (condition) {
+ gb->watchpoints[index].condition = strdup(condition);
+ }
+ else {
+ gb->watchpoints[index].condition = NULL;
+ }
+ gb->n_watchpoints++;
+
+ GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
+ return true;
+}
+
+static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ if (strlen(lstrip(arguments)) == 0) {
+ for (unsigned i = gb->n_watchpoints; i--;) {
+ if (gb->watchpoints[i].condition) {
+ free(gb->watchpoints[i].condition);
+ }
+ }
+ free(gb->watchpoints);
+ gb->watchpoints = NULL;
+ gb->n_watchpoints = 0;
+ return true;
+ }
+
+ bool error;
+ value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
+ uint32_t key = WP_KEY(result);
+
+ if (error) return true;
+
+ uint16_t index = 0;
+ for (unsigned i = 0; i < gb->n_watchpoints; i++) {
+ if (gb->watchpoints[i].key == key) {
+ /* Full match */
+ index = i;
+ break;
+ }
+ if (gb->watchpoints[i].addr == result.value && result.has_bank != (gb->watchpoints[i].bank != (uint16_t) -1)) {
+ /* Partial match */
+ index = i;
+ }
+ }
+
+ if (index >= gb->n_watchpoints) {
+ GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
+ return true;
+ }
+
+ result.bank = gb->watchpoints[index].bank;
+ result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1;
+
+ if (gb->watchpoints[index].condition) {
+ free(gb->watchpoints[index].condition);
+ }
+
+ memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0]));
+ gb->n_watchpoints--;
+ gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0]));
+
+ GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true));
+ return true;
+}
+
+static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ if (gb->n_breakpoints == 0) {
+ GB_log(gb, "No breakpoints set.\n");
+ }
+ else {
+ GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints);
+ for (uint16_t i = 0; i < gb->n_breakpoints; i++) {
+ value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr};
+ if (gb->breakpoints[i].condition) {
+ GB_log(gb, " %d. %s (%sCondition: %s)\n", i + 1,
+ debugger_value_to_string(gb, addr, addr.has_bank),
+ gb->breakpoints[i].is_jump_to? "Jump to, ": "",
+ gb->breakpoints[i].condition);
+ }
+ else {
+ GB_log(gb, " %d. %s%s\n", i + 1,
+ debugger_value_to_string(gb, addr, addr.has_bank),
+ gb->breakpoints[i].is_jump_to? " (Jump to)" : "");
+ }
+ }
+ }
+
+ if (gb->n_watchpoints == 0) {
+ GB_log(gb, "No watchpoints set.\n");
+ }
+ else {
+ GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints);
+ for (uint16_t i = 0; i < gb->n_watchpoints; i++) {
+ value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr};
+ if (gb->watchpoints[i].condition) {
+ GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank),
+ (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-',
+ (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-',
+ gb->watchpoints[i].condition);
+ }
+ else {
+ GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb,addr, addr.has_bank),
+ (gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-',
+ (gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-');
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to)
+{
+ uint16_t index = find_breakpoint(gb, addr);
+ uint32_t key = BP_KEY(addr);
+
+ if (index < gb->n_breakpoints && gb->breakpoints[index].key == key && gb->breakpoints[index].is_jump_to == jump_to) {
+ if (!gb->breakpoints[index].condition) {
+ return true;
+ }
+ bool error;
+ bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition,
+ (unsigned)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value;
+ if (error) {
+ /* Should never happen */
+ GB_log(gb, "An internal error has occured\n");
+ return true;
+ }
+ return condition;
+ }
+ return false;
+}
+
+static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to)
+{
+ /* Try any-bank breakpoint */
+ value_t full_addr = (VALUE_16(addr));
+ if (_should_break(gb, full_addr, jump_to)) return true;
+
+ /* Try bank-specific breakpoint */
+ full_addr.has_bank = true;
+ full_addr.bank = bank_for_addr(gb, addr);
+ return _should_break(gb, full_addr, jump_to);
+}
+
+static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ if (strlen(lstrip(arguments)) == 0) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ if (!modifiers || !modifiers[0]) {
+ modifiers = "a";
+ }
+ else if (modifiers[1]) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ bool error;
+ value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
+ if (!error) {
+ switch (modifiers[0]) {
+ case 'a':
+ GB_log(gb, "=%s\n", debugger_value_to_string(gb, result, false));
+ break;
+ case 'd':
+ GB_log(gb, "=%d\n", result.value);
+ break;
+ case 'x':
+ GB_log(gb, "=$%x\n", result.value);
+ break;
+ case 'o':
+ GB_log(gb, "=0%o\n", result.value);
+ break;
+ case 'b':
+ {
+ if (!result.value) {
+ GB_log(gb, "=%%0\n");
+ break;
+ }
+ char binary[17];
+ binary[16] = 0;
+ char *ptr = &binary[16];
+ while (result.value) {
+ *(--ptr) = (result.value & 1)? '1' : '0';
+ result.value >>= 1;
+ }
+ GB_log(gb, "=%%%s\n", ptr);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+static bool examine(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ if (strlen(lstrip(arguments)) == 0) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ bool error;
+ value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
+ uint16_t count = 32;
+
+ if (modifiers) {
+ char *end;
+ count = (uint16_t) (strtol(modifiers, &end, 10));
+ if (*end) {
+ print_usage(gb, command);
+ return true;
+ }
+ }
+
+ if (!error) {
+ if (addr.has_bank) {
+ banking_state_t old_state;
+ save_banking_state(gb, &old_state);
+ switch_banking_state(gb, addr.bank);
+
+ while (count) {
+ GB_log(gb, "%02x:%04x: ", addr.bank, addr.value);
+ for (int i = 0; i < 16 && count; i++) {
+ GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i));
+ count--;
+ }
+ addr.value += 16;
+ GB_log(gb, "\n");
+ }
+
+ restore_banking_state(gb, &old_state);
+ }
+ else {
+ while (count) {
+ GB_log(gb, "%04x: ", addr.value);
+ for (int i = 0; i < 16 && count; i++) {
+ GB_log(gb, "%02x ", GB_read_memory(gb, addr.value + i));
+ count--;
+ }
+ addr.value += 16;
+ GB_log(gb, "\n");
+ }
+ }
+ }
+ return true;
+}
+
+static bool disassemble(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ if (strlen(lstrip(arguments)) == 0) {
+ arguments = "pc";
+ }
+
+ bool error;
+ value_t addr = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
+ uint16_t count = 5;
+
+ if (modifiers) {
+ char *end;
+ count = (uint16_t) (strtol(modifiers, &end, 10));
+ if (*end) {
+ print_usage(gb, command);
+ return true;
+ }
+ }
+
+ if (!error) {
+ if (addr.has_bank) {
+ banking_state_t old_state;
+ save_banking_state(gb, &old_state);
+ switch_banking_state(gb, addr.bank);
+
+ GB_cpu_disassemble(gb, addr.value, count);
+
+ restore_banking_state(gb, &old_state);
+ }
+ else {
+ GB_cpu_disassemble(gb, addr.value, count);
+ }
+ }
+ return true;
+}
+
+static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ const GB_cartridge_t *cartridge = gb->cartridge_type;
+
+ if (cartridge->has_ram) {
+ GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size);
+ }
+ else {
+ GB_log(gb, "No cartridge RAM\n");
+ }
+
+ if (cartridge->mbc_type) {
+ static const char * const mapper_names[] = {
+ [GB_MBC1] = "MBC1",
+ [GB_MBC2] = "MBC2",
+ [GB_MBC3] = "MBC3",
+ [GB_MBC5] = "MBC5",
+ [GB_HUC1] = "HUC1",
+ [GB_HUC3] = "HUC3",
+ };
+ GB_log(gb, "%s\n", mapper_names[cartridge->mbc_type]);
+ GB_log(gb, "Current mapped ROM bank: %x\n", gb->mbc_rom_bank);
+ if (cartridge->has_ram) {
+ GB_log(gb, "Current mapped RAM bank: %x\n", gb->mbc_ram_bank);
+ GB_log(gb, "RAM is curently %s\n", gb->mbc_ram_enable? "enabled" : "disabled");
+ }
+ if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_STANDARD_MBC1_WIRING) {
+ GB_log(gb, "MBC1 banking mode is %s\n", gb->mbc1.mode == 1 ? "RAM" : "ROM");
+ }
+ if (cartridge->mbc_type == GB_MBC1 && gb->mbc1_wiring == GB_MBC1M_WIRING) {
+ GB_log(gb, "MBC1 uses MBC1M wiring. \n");
+ GB_log(gb, "Current mapped ROM0 bank: %x\n", gb->mbc_rom0_bank);
+ GB_log(gb, "MBC1 multicart banking mode is %s\n", gb->mbc1.mode == 1 ? "enabled" : "disabled");
+ }
+
+ }
+ else {
+ GB_log(gb, "No MBC\n");
+ }
+
+ if (cartridge->has_rumble) {
+ GB_log(gb, "Cart contains a rumble pak\n");
+ }
+
+ if (cartridge->has_rtc) {
+ GB_log(gb, "Cart contains a real time clock\n");
+ }
+
+ return true;
+}
+
+static bool backtrace(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ GB_log(gb, " 1. %s\n", debugger_value_to_string(gb, (value_t){true, bank_for_addr(gb, gb->pc), gb->pc}, true));
+ for (unsigned i = gb->backtrace_size; i--;) {
+ GB_log(gb, "%3d. %s\n", gb->backtrace_size - i + 1, debugger_value_to_string(gb, (value_t){true, gb->backtrace_returns[i].bank, gb->backtrace_returns[i].addr}, true));
+ }
+
+ return true;
+}
+
+static bool ticks(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ STOPPED_ONLY
+
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ GB_log(gb, "Ticks: %lu. (Resetting)\n", gb->debugger_ticks);
+ gb->debugger_ticks = 0;
+
+ return true;
+}
+
+
+static bool palettes(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ if (!GB_is_cgb(gb)) {
+ GB_log(gb, "Not available on a DMG.\n");
+ return true;
+ }
+
+ GB_log(gb, "Background palettes: \n");
+ for (unsigned i = 0; i < 32; i++) {
+ GB_log(gb, "%04x ", ((uint16_t *)&gb->background_palettes_data)[i]);
+ if (i % 4 == 3) {
+ GB_log(gb, "\n");
+ }
+ }
+
+ GB_log(gb, "Sprites palettes: \n");
+ for (unsigned i = 0; i < 32; i++) {
+ GB_log(gb, "%04x ", ((uint16_t *)&gb->sprite_palettes_data)[i]);
+ if (i % 4 == 3) {
+ GB_log(gb, "\n");
+ }
+ }
+
+ return true;
+}
+
+static bool lcd(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+ GB_log(gb, "LCDC:\n");
+ GB_log(gb, " LCD enabled: %s\n",(gb->io_registers[GB_IO_LCDC] & 128)? "Enabled" : "Disabled");
+ GB_log(gb, " %s: %s\n", (gb->cgb_mode? "Sprite priority flags" : "Background and Window"),
+ (gb->io_registers[GB_IO_LCDC] & 1)? "Enabled" : "Disabled");
+ GB_log(gb, " Objects: %s\n", (gb->io_registers[GB_IO_LCDC] & 2)? "Enabled" : "Disabled");
+ GB_log(gb, " Object size: %s\n", (gb->io_registers[GB_IO_LCDC] & 4)? "8x16" : "8x8");
+ GB_log(gb, " Background tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 8)? "$9C00" : "$9800");
+ GB_log(gb, " Background and Window Tileset: %s\n", (gb->io_registers[GB_IO_LCDC] & 16)? "$8000" : "$8800");
+ GB_log(gb, " Window: %s\n", (gb->io_registers[GB_IO_LCDC] & 32)? "Enabled" : "Disabled");
+ GB_log(gb, " Window tilemap: %s\n", (gb->io_registers[GB_IO_LCDC] & 64)? "$9C00" : "$9800");
+
+ GB_log(gb, "\nSTAT:\n");
+ static const char *modes[] = {"Mode 0, H-Blank", "Mode 1, V-Blank", "Mode 2, OAM", "Mode 3, Rendering"};
+ GB_log(gb, " Current mode: %s\n", modes[gb->io_registers[GB_IO_STAT] & 3]);
+ GB_log(gb, " LYC flag: %s\n", (gb->io_registers[GB_IO_STAT] & 4)? "On" : "Off");
+ GB_log(gb, " H-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 8)? "Enabled" : "Disabled");
+ GB_log(gb, " V-Blank interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 16)? "Enabled" : "Disabled");
+ GB_log(gb, " OAM interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 32)? "Enabled" : "Disabled");
+ GB_log(gb, " LYC interrupt: %s\n", (gb->io_registers[GB_IO_STAT] & 64)? "Enabled" : "Disabled");
+
+
+
+ GB_log(gb, "\nCurrent line: %d\n", gb->current_line);
+ GB_log(gb, "Current state: ");
+ if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
+ GB_log(gb, "Off\n");
+ }
+ else if (gb->display_state == 7 || gb->display_state == 8) {
+ GB_log(gb, "Reading OAM data (%d/40)\n", gb->display_state == 8? gb->oam_search_index : 0);
+ }
+ else if (gb->display_state <= 3 || gb->display_state == 24 || gb->display_state == 31) {
+ GB_log(gb, "Glitched line 0 OAM mode (%d cycles to next event)\n", -gb->display_cycles / 2);
+ }
+ else if (gb->mode_for_interrupt == 3) {
+ signed pixel = gb->position_in_line > 160? (int8_t) gb->position_in_line : gb->position_in_line;
+ GB_log(gb, "Rendering pixel (%d/160)\n", pixel);
+ }
+ else {
+ GB_log(gb, "Sleeping (%d cycles to next event)\n", -gb->display_cycles / 2);
+ }
+ GB_log(gb, "LY: %d\n", gb->io_registers[GB_IO_LY]);
+ GB_log(gb, "LYC: %d\n", gb->io_registers[GB_IO_LYC]);
+ GB_log(gb, "Window position: %d, %d\n", (signed) gb->io_registers[GB_IO_WX] - 7 , gb->io_registers[GB_IO_WY]);
+ GB_log(gb, "Interrupt line: %s\n", gb->stat_interrupt_line? "On" : "Off");
+
+ return true;
+}
+
+static bool apu(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ NO_MODIFIERS
+ if (strlen(lstrip(arguments))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+
+ GB_log(gb, "Current state: ");
+ if (!gb->apu.global_enable) {
+ GB_log(gb, "Disabled\n");
+ }
+ else {
+ GB_log(gb, "Enabled\n");
+ for (uint8_t channel = 0; channel < GB_N_CHANNELS; channel++) {
+ GB_log(gb, "CH%u is %s, DAC %s; current sample = 0x%x\n", channel + 1,
+ gb->apu.is_active[channel] ? "active " : "inactive",
+ GB_apu_is_DAC_enabled(gb, channel) ? "active " : "inactive",
+ gb->apu.samples[channel]);
+ }
+ }
+
+ GB_log(gb, "SO1 (left output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x07);
+ if (gb->io_registers[GB_IO_NR51] & 0x0f) {
+ for (uint8_t channel = 0, mask = 0x01; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
+ if (gb->io_registers[GB_IO_NR51] & mask) {
+ GB_log(gb, " CH%u", channel + 1);
+ }
+ }
+ }
+ else {
+ GB_log(gb, " no channels");
+ }
+ GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
+
+ GB_log(gb, "SO2 (right output): volume %u,", gb->io_registers[GB_IO_NR50] & 0x70 >> 4);
+ if (gb->io_registers[GB_IO_NR51] & 0xf0) {
+ for (uint8_t channel = 0, mask = 0x10; channel < GB_N_CHANNELS; channel++, mask <<= 1) {
+ if (gb->io_registers[GB_IO_NR51] & mask) {
+ GB_log(gb, " CH%u", channel + 1);
+ }
+ }
+ }
+ else {
+ GB_log(gb, " no channels");
+ }
+ GB_log(gb, "%s\n", gb->io_registers[GB_IO_NR50] & 0x80 ? " VIN": "");
+
+
+ for (uint8_t channel = GB_SQUARE_1; channel <= GB_SQUARE_2; channel++) {
+ GB_log(gb, "\nCH%u:\n", channel + 1);
+ GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
+ gb->apu.square_channels[channel].current_volume,
+ (gb->apu.square_channels[channel].sample_length ^ 0x7FF) * 2 + 1,
+ gb->apu.square_channels[channel].sample_countdown);
+
+ uint8_t nrx2 = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR12 : GB_IO_NR22];
+ GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
+ gb->apu.square_channels[channel].volume_countdown,
+ nrx2 & 8 ? "in" : "de",
+ nrx2 & 7);
+
+ uint8_t duty = gb->io_registers[channel == GB_SQUARE_1? GB_IO_NR11 :GB_IO_NR21] >> 6;
+ GB_log(gb, " Duty cycle %s%% (%s), current index %u/8%s\n",
+ duty > 3? "" : (const char *[]){"12.5", " 25", " 50", " 75"}[duty],
+ duty > 3? "" : (const char *[]){"_______-", "-______-", "-____---", "_------_"}[duty],
+ gb->apu.square_channels[channel].current_sample_index & 0x7f,
+ gb->apu.square_channels[channel].current_sample_index >> 7 ? " (suppressed)" : "");
+
+ if (channel == GB_SQUARE_1) {
+ GB_log(gb, " Frequency sweep %s and %s (next in %u APU ticks)\n",
+ gb->apu.sweep_enabled? "active" : "inactive",
+ gb->apu.sweep_decreasing? "decreasing" : "increasing",
+ gb->apu.square_sweep_calculate_countdown);
+ }
+
+ if (gb->apu.square_channels[channel].length_enabled) {
+ GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
+ gb->apu.square_channels[channel].pulse_length);
+ }
+ }
+
+
+ GB_log(gb, "\nCH3:\n");
+ GB_log(gb, " Wave:");
+ for (uint8_t i = 0; i < 32; i++) {
+ GB_log(gb, "%s%X", i%4?"":" ", gb->apu.wave_channel.wave_form[i]);
+ }
+ GB_log(gb, "\n");
+ GB_log(gb, " Current position: %u\n", gb->apu.wave_channel.current_sample_index);
+
+ GB_log(gb, " Volume %s (right-shifted %u times)\n",
+ gb->apu.wave_channel.shift > 4? "" : (const char *[]){"100%", "50%", "25%", "", "muted"}[gb->apu.wave_channel.shift],
+ gb->apu.wave_channel.shift);
+
+ GB_log(gb, " Current sample length: %u APU ticks (next in %u ticks)\n",
+ gb->apu.wave_channel.sample_length ^ 0x7ff,
+ gb->apu.wave_channel.sample_countdown);
+
+ if (gb->apu.wave_channel.length_enabled) {
+ GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
+ gb->apu.wave_channel.pulse_length);
+ }
+
+
+ GB_log(gb, "\nCH4:\n");
+ GB_log(gb, " Current volume: %u, current sample length: %u APU ticks (next in %u ticks)\n",
+ gb->apu.noise_channel.current_volume,
+ gb->apu.noise_channel.sample_length * 4 + 3,
+ gb->apu.noise_channel.sample_countdown);
+
+ GB_log(gb, " %u 256 Hz ticks till next volume %screase (out of %u)\n",
+ gb->apu.noise_channel.volume_countdown,
+ gb->io_registers[GB_IO_NR42] & 8 ? "in" : "de",
+ gb->io_registers[GB_IO_NR42] & 7);
+
+ GB_log(gb, " LFSR in %u-step mode, current value ",
+ gb->apu.noise_channel.narrow? 7 : 15);
+ for (uint16_t lfsr = gb->apu.noise_channel.lfsr, i = 15; i--; lfsr <<= 1) {
+ GB_log(gb, "%u%s", (lfsr >> 14) & 1, i%4 ? "" : " ");
+ }
+
+ if (gb->apu.noise_channel.length_enabled) {
+ GB_log(gb, " Channel will end in %u 256 Hz ticks\n",
+ gb->apu.noise_channel.pulse_length);
+ }
+
+
+ GB_log(gb, "\n\nReminder: APU ticks are @ 2 MiHz\n");
+
+ return true;
+}
+
+static bool wave(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
+{
+ if (strlen(lstrip(arguments)) || (modifiers && !strchr("fcl", modifiers[0]))) {
+ print_usage(gb, command);
+ return true;
+ }
+
+ uint8_t shift_amount = 1, mask;
+ if (modifiers) {
+ switch(modifiers[0]) {
+ case 'c':
+ shift_amount = 2;
+ break;
+ case 'l':
+ shift_amount = 8;
+ break;
+ }
+ }
+ mask = (0xf << (shift_amount - 1)) & 0xf;
+
+ for (int8_t cur_val = 0xf & mask; cur_val >= 0; cur_val -= shift_amount) {
+ for (uint8_t i = 0; i < 32; i++) {
+ if ((gb->apu.wave_channel.wave_form[i] & mask) == cur_val) {
+ GB_log(gb, "%X", gb->apu.wave_channel.wave_form[i]);
+ }
+ else {
+ GB_log(gb, "%c", i%4 == 2 ? '-' : ' ');
+ }
+ }
+ GB_log(gb, "\n");
+ }
+
+ return true;
+}
+
+static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command);
+
+#define HELP_NEWLINE "\n "
+
+/* Commands without implementations are aliases of the previous non-alias commands */
+static const debugger_command_t commands[] = {
+ {"continue", 1, cont, "Continue running until next stop"},
+ {"next", 1, next, "Run the next instruction, skipping over function calls"},
+ {"step", 1, step, "Run the next instruction, stepping into function calls"},
+ {"finish", 1, finish, "Run until the current function returns"},
+ {"backtrace", 2, backtrace, "Display the current call stack"},
+ {"bt", 2, }, /* Alias */
+ {"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected (Experimental)"},
+ {"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was used"},
+ {"registers", 1, registers, "Print values of processor registers and other important registers"},
+ {"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
+ {"mbc", 3, }, /* Alias */
+ {"apu", 3, apu, "Displays information about the current state of the audio chip"},
+ {"wave", 3, wave, "Prints a visual representation of the wave RAM." HELP_NEWLINE
+ "Modifiers can be used for a (f)ull print (the default)," HELP_NEWLINE
+ "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)"},
+ {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"},
+ {"palettes", 3, palettes, "Displays the current CGB palettes"},
+ {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE
+ "Can also modify the condition of existing breakpoints." HELP_NEWLINE
+ "If the j modifier is used, the breakpoint will occur just before" HELP_NEWLINE
+ "jumping to the target.",
+ "[ if ]", "j"},
+ {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"},
+ {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
+ "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE
+ "Default watchpoint type is write-only.",
+ "[ if ]", "(r|w|rw)"},
+ {"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[]"},
+ {"list", 1, list, "List all set breakpoints and watchpoints"},
+ {"print", 1, print, "Evaluate and print an expression" HELP_NEWLINE
+ "Use modifier to format as an address (a, default) or as a number in" HELP_NEWLINE
+ "decimal (d), hexadecimal (x), octal (o) or binary (b).",
+ "", "format"},
+ {"eval", 2, }, /* Alias */
+ {"examine", 2, examine, "Examine values at address", "", "count"},
+ {"x", 1, }, /* Alias */
+ {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count"},
+
+
+ {"help", 1, help, "List available commands or show help for the specified command", "[]"},
+ {NULL,}, /* Null terminator */
+};
+
+static const debugger_command_t *find_command(const char *string)
+{
+ size_t length = strlen(string);
+ for (const debugger_command_t *command = commands; command->command; command++) {
+ if (command->min_length > length) continue;
+ if (memcmp(command->command, string, length) == 0) { /* Is a substring? */
+ /* Aliases */
+ while (!command->implementation) {
+ command--;
+ }
+ return command;
+ }
+ }
+
+ return NULL;
+}
+
+static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command)
+{
+ GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command);
+ GB_attributed_log(gb, GB_LOG_BOLD , "%s", command->command + command->min_length);
+}
+
+static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command)
+{
+ print_command_shortcut(gb, command);
+ GB_log(gb, ": ");
+ GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string);
+}
+
+static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *ignored)
+{
+ const debugger_command_t *command = find_command(arguments);
+ if (command) {
+ print_command_description(gb, command);
+ GB_log(gb, "\n");
+ print_usage(gb, command);
+
+ command++;
+ if (command->command && !command->implementation) { /* Command has aliases*/
+ GB_log(gb, "\nAliases: ");
+ do {
+ print_command_shortcut(gb, command);
+ GB_log(gb, " ");
+ command++;
+ } while (command->command && !command->implementation);
+ GB_log(gb, "\n");
+ }
+ return true;
+ }
+ for (command = commands; command->command; command++) {
+ if (command->help_string) {
+ print_command_description(gb, command);
+ }
+ }
+ return true;
+}
+
+void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
+{
+ /* Called just after the CPU calls a function/enters an interrupt/etc... */
+
+ if (gb->stack_leak_detection) {
+ if (gb->debug_call_depth >= sizeof(gb->sp_for_call_depth) / sizeof(gb->sp_for_call_depth[0])) {
+ GB_log(gb, "Potential stack overflow detected (Functions nest too much). \n");
+ gb->debug_stopped = true;
+ }
+ else {
+ gb->sp_for_call_depth[gb->debug_call_depth] = gb->registers[GB_REGISTER_SP];
+ gb->addr_for_call_depth[gb->debug_call_depth] = gb->pc;
+ }
+ }
+
+ if (gb->backtrace_size < sizeof(gb->backtrace_sps) / sizeof(gb->backtrace_sps[0])) {
+
+ while (gb->backtrace_size) {
+ if (gb->backtrace_sps[gb->backtrace_size - 1] < gb->registers[GB_REGISTER_SP]) {
+ gb->backtrace_size--;
+ }
+ else {
+ break;
+ }
+ }
+
+ gb->backtrace_sps[gb->backtrace_size] = gb->registers[GB_REGISTER_SP];
+ gb->backtrace_returns[gb->backtrace_size].bank = bank_for_addr(gb, call_addr);
+ gb->backtrace_returns[gb->backtrace_size].addr = call_addr;
+ gb->backtrace_size++;
+ }
+
+ gb->debug_call_depth++;
+}
+
+void GB_debugger_ret_hook(GB_gameboy_t *gb)
+{
+ /* Called just before the CPU runs ret/reti */
+
+ gb->debug_call_depth--;
+
+ if (gb->stack_leak_detection) {
+ if (gb->debug_call_depth < 0) {
+ GB_log(gb, "Function finished without a stack leak.\n");
+ gb->debug_stopped = true;
+ }
+ else {
+ if (gb->registers[GB_REGISTER_SP] != gb->sp_for_call_depth[gb->debug_call_depth]) {
+ GB_log(gb, "Stack leak detected for function %s!\n", value_to_string(gb, gb->addr_for_call_depth[gb->debug_call_depth], true));
+ GB_log(gb, "SP is $%04x, should be $%04x.\n", gb->registers[GB_REGISTER_SP],
+ gb->sp_for_call_depth[gb->debug_call_depth]);
+ gb->debug_stopped = true;
+ }
+ }
+ }
+
+ while (gb->backtrace_size) {
+ if (gb->backtrace_sps[gb->backtrace_size - 1] <= gb->registers[GB_REGISTER_SP]) {
+ gb->backtrace_size--;
+ }
+ else {
+ break;
+ }
+ }
+}
+
+static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value)
+{
+ uint16_t index = find_watchpoint(gb, addr);
+ uint32_t key = WP_KEY(addr);
+
+ if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) {
+ if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) {
+ return false;
+ }
+ if (!gb->watchpoints[index].condition) {
+ gb->debug_stopped = true;
+ GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value);
+ return true;
+ }
+ bool error;
+ bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
+ (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value;
+ if (error) {
+ /* Should never happen */
+ GB_log(gb, "An internal error has occured\n");
+ return false;
+ }
+ if (condition) {
+ gb->debug_stopped = true;
+ GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value);
+ return true;
+ }
+ }
+ return false;
+}
+
+void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ if (gb->debug_stopped) return;
+
+ /* Try any-bank breakpoint */
+ value_t full_addr = (VALUE_16(addr));
+ if (_GB_debugger_test_write_watchpoint(gb, full_addr, value)) return;
+
+ /* Try bank-specific breakpoint */
+ full_addr.has_bank = true;
+ full_addr.bank = bank_for_addr(gb, addr);
+ _GB_debugger_test_write_watchpoint(gb, full_addr, value);
+}
+
+static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr)
+{
+ uint16_t index = find_watchpoint(gb, addr);
+ uint32_t key = WP_KEY(addr);
+
+ if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) {
+ if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) {
+ return false;
+ }
+ if (!gb->watchpoints[index].condition) {
+ gb->debug_stopped = true;
+ GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true));
+ return true;
+ }
+ bool error;
+ bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
+ (unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value;
+ if (error) {
+ /* Should never happen */
+ GB_log(gb, "An internal error has occured\n");
+ return false;
+ }
+ if (condition) {
+ gb->debug_stopped = true;
+ GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true));
+ return true;
+ }
+ }
+ return false;
+}
+
+void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (gb->debug_stopped) return;
+
+ /* Try any-bank breakpoint */
+ value_t full_addr = (VALUE_16(addr));
+ if (_GB_debugger_test_read_watchpoint(gb, full_addr)) return;
+
+ /* Try bank-specific breakpoint */
+ full_addr.has_bank = true;
+ full_addr.bank = bank_for_addr(gb, addr);
+ _GB_debugger_test_read_watchpoint(gb, full_addr);
+}
+
+/* Returns true if debugger waits for more commands */
+bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input)
+{
+ if (!input[0]) {
+ return true;
+ }
+
+ char *command_string = input;
+ char *arguments = strchr(input, ' ');
+ if (arguments) {
+ /* Actually "split" the string. */
+ arguments[0] = 0;
+ arguments++;
+ }
+ else {
+ arguments = "";
+ }
+
+ char *modifiers = strchr(command_string, '/');
+ if (modifiers) {
+ /* Actually "split" the string. */
+ modifiers[0] = 0;
+ modifiers++;
+ }
+
+ const debugger_command_t *command = find_command(command_string);
+ if (command) {
+ return command->implementation(gb, arguments, modifiers, command);
+ }
+ else {
+ GB_log(gb, "%s: no such command.\n", command_string);
+ return true;
+ }
+}
+
+typedef enum {
+ JUMP_TO_NONE,
+ JUMP_TO_BREAK,
+ JUMP_TO_NONTRIVIAL,
+} jump_to_return_t;
+
+static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address);
+
+void GB_debugger_run(GB_gameboy_t *gb)
+{
+ if (gb->debug_disable) return;
+
+ char *input = NULL;
+ if (gb->debug_next_command && gb->debug_call_depth <= 0) {
+ gb->debug_stopped = true;
+ }
+ if (gb->debug_fin_command && gb->debug_call_depth == -1) {
+ gb->debug_stopped = true;
+ }
+ if (gb->debug_stopped) {
+ GB_cpu_disassemble(gb, gb->pc, 5);
+ }
+next_command:
+ if (input) {
+ free(input);
+ }
+ if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc, false)) {
+ gb->debug_stopped = true;
+ GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true));
+ GB_cpu_disassemble(gb, gb->pc, 5);
+ }
+
+ if (gb->breakpoints && !gb->debug_stopped) {
+ uint16_t address = 0;
+ jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address);
+
+ bool should_delete_state = true;
+ if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) {
+ if (gb->non_trivial_jump_breakpoint_occured) {
+ gb->non_trivial_jump_breakpoint_occured = false;
+ }
+ else {
+ gb->non_trivial_jump_breakpoint_occured = true;
+ GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true));
+ GB_cpu_disassemble(gb, gb->pc, 5);
+ GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1);
+ gb->debug_stopped = true;
+ }
+ }
+ else if (jump_to_result == JUMP_TO_BREAK) {
+ gb->debug_stopped = true;
+ GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, address, true));
+ GB_cpu_disassemble(gb, gb->pc, 5);
+ gb->non_trivial_jump_breakpoint_occured = false;
+ }
+ else if (jump_to_result == JUMP_TO_NONTRIVIAL) {
+ if (!gb->nontrivial_jump_state) {
+ gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb));
+ }
+ GB_save_state_to_buffer(gb, gb->nontrivial_jump_state);
+ gb->non_trivial_jump_breakpoint_occured = false;
+ should_delete_state = false;
+ }
+ else {
+ gb->non_trivial_jump_breakpoint_occured = false;
+ }
+
+ if (should_delete_state) {
+ if (gb->nontrivial_jump_state) {
+ free(gb->nontrivial_jump_state);
+ gb->nontrivial_jump_state = NULL;
+ }
+ }
+ }
+
+ if (gb->debug_stopped && !gb->debug_disable) {
+ gb->debug_next_command = false;
+ gb->debug_fin_command = false;
+ gb->stack_leak_detection = false;
+ input = gb->input_callback(gb);
+
+ if (input == NULL) {
+ /* Debugging is no currently available, continue running */
+ gb->debug_stopped = false;
+ return;
+ }
+
+ if (GB_debugger_execute_command(gb, input)) {
+ goto next_command;
+ }
+
+ free(input);
+ }
+}
+
+void GB_debugger_handle_async_commands(GB_gameboy_t *gb)
+{
+ char *input = NULL;
+
+ while (gb->async_input_callback && (input = gb->async_input_callback(gb))) {
+ GB_debugger_execute_command(gb, input);
+ free(input);
+ }
+}
+
+void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
+{
+ FILE *f = fopen(path, "r");
+ if (!f) return;
+
+ char *line = NULL;
+ size_t size = 0;
+ size_t length = 0;
+ while ((length = getline(&line, &size, f)) != -1) {
+ for (unsigned i = 0; i < length; i++) {
+ if (line[i] == ';' || line[i] == '\n' || line[i] == '\r') {
+ line[i] = 0;
+ length = i;
+ break;
+ }
+ }
+ if (length == 0) continue;
+
+ unsigned bank, address;
+ char symbol[length];
+
+ if (sscanf(line, "%x:%x %s", &bank, &address, symbol) == 3) {
+ bank &= 0x1FF;
+ if (!gb->bank_symbols[bank]) {
+ gb->bank_symbols[bank] = GB_map_alloc();
+ }
+ GB_bank_symbol_t *allocated_symbol = GB_map_add_symbol(gb->bank_symbols[bank], address, symbol);
+ if (allocated_symbol) {
+ GB_reversed_map_add_symbol(&gb->reversed_symbol_map, bank, allocated_symbol);
+ }
+ }
+ }
+ free(line);
+ fclose(f);
+}
+
+void GB_debugger_clear_symbols(GB_gameboy_t *gb)
+{
+ for (int i = sizeof(gb->bank_symbols) / sizeof(gb->bank_symbols[0]); i--;) {
+ if (gb->bank_symbols[i]) {
+ GB_map_free(gb->bank_symbols[i]);
+ gb->bank_symbols[i] = 0;
+ }
+ }
+ for (int i = sizeof(gb->reversed_symbol_map.buckets) / sizeof(gb->reversed_symbol_map.buckets[0]); i--;) {
+ while (gb->reversed_symbol_map.buckets[i]) {
+ GB_symbol_t *next = gb->reversed_symbol_map.buckets[i]->next;
+ free(gb->reversed_symbol_map.buckets[i]);
+ gb->reversed_symbol_map.buckets[i] = next;
+ }
+ }
+}
+
+const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr)
+{
+ uint16_t bank = bank_for_addr(gb, addr);
+
+ const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr);
+ if (symbol) return symbol;
+ if (bank != 0) return GB_map_find_symbol(gb->bank_symbols[0], addr); /* Maybe the symbol incorrectly uses bank 0? */
+
+ return NULL;
+}
+
+const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr)
+{
+ const GB_bank_symbol_t *symbol = GB_debugger_find_symbol(gb, addr);
+ if (symbol && symbol->addr == addr) return symbol->name;
+ return NULL;
+}
+
+/* The public version of debugger_evaluate */
+bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank)
+{
+ bool error = false;
+ value_t value = debugger_evaluate(gb, string, strlen(string), &error, NULL, NULL);
+ if (result) {
+ *result = value.value;
+ }
+ if (result_bank) {
+ *result_bank = value.has_bank? value.value : -1;
+ }
+ return error;
+}
+
+void GB_debugger_break(GB_gameboy_t *gb)
+{
+ gb->debug_stopped = true;
+}
+
+bool GB_debugger_is_stopped(GB_gameboy_t *gb)
+{
+ return gb->debug_stopped;
+}
+
+void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled)
+{
+ gb->debug_disable = disabled;
+}
+
+/* Jump-to breakpoints */
+
+static bool is_in_trivial_memory(uint16_t addr)
+{
+ /* ROM */
+ if (addr < 0x8000) {
+ return true;
+ }
+
+ /* HRAM */
+ if (addr >= 0xFF80 && addr < 0xFFFF) {
+ return true;
+ }
+
+ /* RAM */
+ if (addr >= 0xC000 && addr < 0xE000) {
+ return true;
+ }
+
+ return false;
+}
+
+typedef uint16_t GB_opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode);
+
+uint16_t trivial_1(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return gb->pc + 1;
+}
+
+uint16_t trivial_2(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return gb->pc + 2;
+}
+
+uint16_t trivial_3(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return gb->pc + 3;
+}
+
+static uint16_t jr_r8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1);
+}
+
+static bool condition_code(GB_gameboy_t *gb, uint8_t opcode)
+{
+ switch ((opcode >> 3) & 0x3) {
+ case 0:
+ return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG);
+ case 1:
+ return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG);
+ case 2:
+ return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
+ case 3:
+ return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
+ }
+
+ return false;
+}
+
+static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ if (!condition_code(gb, opcode)) {
+ return gb->pc + 2;
+ }
+
+ return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1);
+}
+
+static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) |
+ (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8);
+}
+
+
+static uint16_t ret_cc(GB_gameboy_t *gb, uint8_t opcode)
+{
+ if (condition_code(gb, opcode)) {
+ return ret(gb, opcode);
+ }
+ else {
+ return gb->pc + 1;
+ }
+}
+
+static uint16_t jp_a16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return GB_read_memory(gb, gb->pc + 1) |
+ (GB_read_memory(gb, gb->pc + 2) << 8);
+}
+
+static uint16_t jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ if (condition_code(gb, opcode)) {
+ return jp_a16(gb, opcode);
+ }
+ else {
+ return gb->pc + 3;
+ }
+}
+
+static uint16_t rst(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return opcode ^ 0xC7;
+}
+
+static uint16_t jp_hl(GB_gameboy_t *gb, uint8_t opcode)
+{
+ return gb->hl;
+}
+
+static GB_opcode_address_getter_t *opcodes[256] = {
+ /* X0 X1 X2 X3 X4 X5 X6 X7 */
+ /* X8 X9 Xa Xb Xc Xd Xe Xf */
+ trivial_1, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 0X */
+ trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1,
+ trivial_2, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 1X */
+ jr_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1,
+ jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 2X */
+ jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1,
+ jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 3X */
+ jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 4X */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 5X */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 6X */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, NULL, trivial_1, /* 7X */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 8X */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 9X */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* aX */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* bX */
+ trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1,
+ ret_cc, trivial_1, jp_cc_a16, jp_a16, jp_cc_a16, trivial_1, trivial_2, rst, /* cX */
+ ret_cc, ret, jp_cc_a16, trivial_2, jp_cc_a16, jp_a16, trivial_2, rst,
+ ret_cc, trivial_1, jp_cc_a16, NULL, jp_cc_a16, trivial_1, trivial_2, rst, /* dX */
+ ret_cc, ret, jp_cc_a16, NULL, jp_cc_a16, NULL, trivial_2, rst,
+ trivial_2, trivial_1, trivial_1, NULL, NULL, trivial_1, trivial_2, rst, /* eX */
+ trivial_2, jp_hl, trivial_3, NULL, NULL, NULL, trivial_2, rst,
+ trivial_2, trivial_1, trivial_1, trivial_1, NULL, trivial_1, trivial_2, rst, /* fX */
+ trivial_2, trivial_1, trivial_3, trivial_1, NULL, NULL, trivial_2, rst,
+};
+
+static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address)
+{
+ if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE;
+
+ if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) ||
+ !is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) {
+ return JUMP_TO_NONTRIVIAL;
+ }
+
+ /* Interrupts */
+ if (gb->ime) {
+ for (unsigned i = 0; i < 5; i++) {
+ if ((gb->interrupt_enable & (1 << i)) && (gb->io_registers[GB_IO_IF] & (1 << i))) {
+ if (should_break(gb, 0x40 + i * 8, true)) {
+ if (address) {
+ *address = 0x40 + i * 8;
+ }
+ return JUMP_TO_BREAK;
+ }
+ }
+ }
+ }
+
+ uint16_t n_watchpoints = gb->n_watchpoints;
+ gb->n_watchpoints = 0;
+
+ uint8_t opcode = GB_read_memory(gb, gb->pc);
+
+ if (opcode == 0x76) {
+ gb->n_watchpoints = n_watchpoints;
+ if (gb->ime) { /* Already handled in above */
+ return JUMP_TO_NONE;
+ }
+
+ if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) {
+ return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */
+ }
+
+ return JUMP_TO_NONE;
+ }
+
+ GB_opcode_address_getter_t *getter = opcodes[opcode];
+ if (!getter) {
+ gb->n_watchpoints = n_watchpoints;
+ return JUMP_TO_NONE;
+ }
+
+ uint16_t new_pc = getter(gb, opcode);
+
+ gb->n_watchpoints = n_watchpoints;
+
+ if (address) {
+ *address = new_pc;
+ }
+
+ return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE;
+}
diff --git a/gb/Core/debugger.h b/gb/Core/debugger.h
new file mode 100644
index 0000000..2906ad9
--- /dev/null
+++ b/gb/Core/debugger.h
@@ -0,0 +1,43 @@
+#ifndef debugger_h
+#define debugger_h
+#include
+#include
+#include "gb_struct_def.h"
+#include "symbol_hash.h"
+
+
+#ifdef GB_INTERNAL
+#ifdef DISABLE_DEBUGGER
+#define GB_debugger_run(gb) (void)0
+#define GB_debugger_handle_async_commands(gb) (void)0
+#define GB_debugger_ret_hook(gb) (void)0
+#define GB_debugger_call_hook(gb, addr) (void)addr
+#define GB_debugger_test_write_watchpoint(gb, addr, value) ((void)addr, (void)value)
+#define GB_debugger_test_read_watchpoint(gb, addr) (void)addr
+#else
+void GB_debugger_run(GB_gameboy_t *gb);
+void GB_debugger_handle_async_commands(GB_gameboy_t *gb);
+void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr);
+void GB_debugger_ret_hook(GB_gameboy_t *gb);
+void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
+void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr);
+const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr);
+#endif /* DISABLE_DEBUGGER */
+#endif
+
+#ifdef GB_INTERNAL
+bool /* Returns true if debugger waits for more commands. Not relevant for non-GB_INTERNAL */
+#else
+void
+#endif
+GB_debugger_execute_command(GB_gameboy_t *gb, char *input); /* Destroys input. */
+
+
+void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path);
+const char *GB_debugger_name_for_address(GB_gameboy_t *gb, uint16_t addr);
+bool GB_debugger_evaluate(GB_gameboy_t *gb, const char *string, uint16_t *result, uint16_t *result_bank); /* result_bank is -1 if unused. */
+void GB_debugger_break(GB_gameboy_t *gb);
+bool GB_debugger_is_stopped(GB_gameboy_t *gb);
+void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled);
+void GB_debugger_clear_symbols(GB_gameboy_t *gb);
+#endif /* debugger_h */
diff --git a/gb/Core/display.c b/gb/Core/display.c
new file mode 100644
index 0000000..5c1935c
--- /dev/null
+++ b/gb/Core/display.c
@@ -0,0 +1,1227 @@
+#include
+#include
+#include
+#include
+#include "gb.h"
+
+/* FIFO functions */
+
+static inline unsigned fifo_size(GB_fifo_t *fifo)
+{
+ return (fifo->write_end - fifo->read_end) & (GB_FIFO_LENGTH - 1);
+}
+
+static void fifo_clear(GB_fifo_t *fifo)
+{
+ fifo->read_end = fifo->write_end = 0;
+}
+
+static GB_fifo_item_t *fifo_pop(GB_fifo_t *fifo)
+{
+ GB_fifo_item_t *ret = &fifo->fifo[fifo->read_end];
+ fifo->read_end++;
+ fifo->read_end &= (GB_FIFO_LENGTH - 1);
+ return ret;
+}
+
+static void fifo_push_bg_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, bool flip_x)
+{
+ if (!flip_x) {
+ UNROLL
+ for (unsigned i = 8; i--;) {
+ fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
+ (lower >> 7) | ((upper >> 7) << 1),
+ palette,
+ 0,
+ bg_priority,
+ };
+ lower <<= 1;
+ upper <<= 1;
+
+ fifo->write_end++;
+ fifo->write_end &= (GB_FIFO_LENGTH - 1);
+ }
+ }
+ else {
+ UNROLL
+ for (unsigned i = 8; i--;) {
+ fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {
+ (lower & 1) | ((upper & 1) << 1),
+ palette,
+ 0,
+ bg_priority,
+ };
+ lower >>= 1;
+ upper >>= 1;
+
+ fifo->write_end++;
+ fifo->write_end &= (GB_FIFO_LENGTH - 1);
+ }
+ }
+}
+
+static void fifo_overlay_object_row(GB_fifo_t *fifo, uint8_t lower, uint8_t upper, uint8_t palette, bool bg_priority, uint8_t priority, bool flip_x)
+{
+ while (fifo_size(fifo) < 8) {
+ fifo->fifo[fifo->write_end] = (GB_fifo_item_t) {0,};
+ fifo->write_end++;
+ fifo->write_end &= (GB_FIFO_LENGTH - 1);
+ }
+
+ uint8_t flip_xor = flip_x? 0: 0x7;
+
+ UNROLL
+ for (unsigned i = 8; i--;) {
+ uint8_t pixel = (lower >> 7) | ((upper >> 7) << 1);
+ GB_fifo_item_t *target = &fifo->fifo[(fifo->read_end + (i ^ flip_xor)) & (GB_FIFO_LENGTH - 1)];
+ if (pixel != 0 && (target->pixel == 0 || target->priority > priority)) {
+ target->pixel = pixel;
+ target->palette = palette;
+ target->bg_priority = bg_priority;
+ target->priority = priority;
+ }
+ lower <<= 1;
+ upper <<= 1;
+ }
+}
+
+
+/*
+ Each line is 456 cycles. Without scrolling, sprites or a window:
+ Mode 2 - 80 cycles / OAM Transfer
+ Mode 3 - 172 cycles / Rendering
+ Mode 0 - 204 cycles / HBlank
+
+ Mode 1 is VBlank
+ */
+
+#define MODE2_LENGTH (80)
+#define LINE_LENGTH (456)
+#define LINES (144)
+#define WIDTH (160)
+#define FRAME_LENGTH (LCDC_PERIOD)
+#define VIRTUAL_LINES (FRAME_LENGTH / LINE_LENGTH) // = 154
+
+typedef struct __attribute__((packed)) {
+ uint8_t y;
+ uint8_t x;
+ uint8_t tile;
+ uint8_t flags;
+} GB_object_t;
+
+static bool window_enabled(GB_gameboy_t *gb)
+{
+ if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
+ if (!gb->cgb_mode) {
+ return false;
+ }
+ }
+ return (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] < 167;
+}
+
+static void display_vblank(GB_gameboy_t *gb)
+{
+ gb->vblank_just_occured = true;
+
+ /* TODO: Slow in turbo mode! */
+ if (GB_is_hle_sgb(gb)) {
+ GB_sgb_render(gb);
+ }
+
+ if (gb->turbo) {
+ if (GB_timing_sync_turbo(gb)) {
+ return;
+ }
+ }
+
+ if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
+ /* LCD is off, set screen to white or black (if LCD is on in stop mode) */
+ if (gb->sgb) {
+ for (unsigned i = 0; i < WIDTH * LINES; i++) {
+ gb->sgb->screen_buffer[i] = 0x0;
+ }
+ }
+ else {
+ uint32_t color = (gb->io_registers[GB_IO_LCDC] & 0x80) && gb->stopped && GB_is_cgb(gb) ?
+ gb->rgb_encode_callback(gb, 0, 0, 0) :
+ gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
+ for (unsigned i = 0; i < WIDTH * LINES; i++) {
+ gb ->screen[i] = color;
+ }
+ }
+ }
+
+ gb->vblank_callback(gb);
+ GB_timing_sync(gb);
+}
+
+static inline uint8_t scale_channel(uint8_t x)
+{
+ return (x << 3) | (x >> 2);
+}
+
+static inline uint8_t scale_channel_with_curve(uint8_t x)
+{
+ return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255}[x];
+}
+
+static inline uint8_t scale_channel_with_curve_agb(uint8_t x)
+{
+ return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x];
+}
+
+static inline uint8_t scale_channel_with_curve_sgb(uint8_t x)
+{
+ return (uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x];
+}
+
+
+uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
+{
+ uint8_t r = (color) & 0x1F;
+ uint8_t g = (color >> 5) & 0x1F;
+ uint8_t b = (color >> 10) & 0x1F;
+
+ if (gb->color_correction_mode == GB_COLOR_CORRECTION_DISABLED) {
+ r = scale_channel(r);
+ g = scale_channel(g);
+ b = scale_channel(b);
+ }
+ else {
+ if (GB_is_sgb(gb)) {
+ return gb->rgb_encode_callback(gb,
+ scale_channel_with_curve_sgb(r),
+ scale_channel_with_curve_sgb(g),
+ scale_channel_with_curve_sgb(b));
+ }
+ bool agb = gb->model == GB_MODEL_AGB;
+ r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
+ g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
+ b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b);
+
+ if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) {
+ uint8_t new_r, new_g, new_b;
+ if (agb) {
+ new_r = (r * 7 + g * 1) / 8;
+ new_g = (g * 3 + b * 1) / 4;
+ new_b = (b * 7 + r * 1) / 8;
+ }
+ else {
+ new_g = (g * 3 + b) / 4;
+ new_r = r;
+ new_b = b;
+ }
+ if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
+ uint8_t old_max = MAX(r, MAX(g, b));
+ uint8_t new_max = MAX(new_r, MAX(new_g, new_b));
+
+ if (new_max != 0) {
+ new_r = new_r * old_max / new_max;
+ new_g = new_g * old_max / new_max;
+ new_b = new_b * old_max / new_max;
+ }
+
+ uint8_t old_min = MIN(r, MIN(g, b));
+ uint8_t new_min = MIN(new_r, MIN(new_g, new_b));
+
+ if (new_min != 0xff) {
+ new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min);
+ new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min);
+ new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);
+ }
+ }
+ r = new_r;
+ g = new_g;
+ b = new_b;
+ }
+ }
+
+ return gb->rgb_encode_callback(gb, r, g, b);
+}
+
+void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index)
+{
+ if (!gb->rgb_encode_callback || !GB_is_cgb(gb)) return;
+ uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data;
+ uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8);
+
+ (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = GB_convert_rgb15(gb, color);
+}
+
+void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode)
+{
+ gb->color_correction_mode = mode;
+ if (GB_is_cgb(gb)) {
+ for (unsigned i = 0; i < 32; i++) {
+ GB_palette_changed(gb, false, i * 2);
+ GB_palette_changed(gb, true, i * 2);
+ }
+ }
+}
+
+/*
+ STAT interrupt is implemented based on this finding:
+ http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531
+
+ General timing is based on GiiBiiAdvance's documents:
+ https://github.com/AntonioND/giibiiadvance
+
+ */
+
+void GB_STAT_update(GB_gameboy_t *gb)
+{
+ if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) return;
+
+ bool previous_interrupt_line = gb->stat_interrupt_line;
+ /* Set LY=LYC bit */
+ /* TODO: This behavior might not be correct for CGB revisions other than C and E */
+ if (gb->ly_for_comparison != (uint16_t)-1 || gb->model <= GB_MODEL_CGB_C) {
+ if (gb->ly_for_comparison == gb->io_registers[GB_IO_LYC]) {
+ gb->lyc_interrupt_line = true;
+ gb->io_registers[GB_IO_STAT] |= 4;
+ }
+ else {
+ if (gb->ly_for_comparison != (uint16_t)-1) {
+ gb->lyc_interrupt_line = false;
+ }
+ gb->io_registers[GB_IO_STAT] &= ~4;
+ }
+ }
+
+ switch (gb->mode_for_interrupt) {
+ case 0: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 8; break;
+ case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break;
+ case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break;
+ default: gb->stat_interrupt_line = false;
+ }
+
+ /* User requested a LY=LYC interrupt and the LY=LYC bit is on */
+ if ((gb->io_registers[GB_IO_STAT] & 0x40) && gb->lyc_interrupt_line) {
+ gb->stat_interrupt_line = true;
+ }
+
+ if (gb->stat_interrupt_line && !previous_interrupt_line) {
+ gb->io_registers[GB_IO_IF] |= 2;
+ }
+}
+
+void GB_lcd_off(GB_gameboy_t *gb)
+{
+ gb->display_state = 0;
+ gb->display_cycles = 0;
+ /* When the LCD is disabled, state is constant */
+
+ /* When the LCD is off, LY is 0 and STAT mode is 0. */
+ gb->io_registers[GB_IO_LY] = 0;
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ if (gb->hdma_on_hblank) {
+ gb->hdma_on_hblank = false;
+ gb->hdma_on = false;
+
+ /* Todo: is this correct? */
+ gb->hdma_steps_left = 0xff;
+ }
+
+ gb->oam_read_blocked = false;
+ gb->vram_read_blocked = false;
+ gb->oam_write_blocked = false;
+ gb->vram_write_blocked = false;
+ gb->cgb_palettes_blocked = false;
+
+ /* Reset window rendering state */
+ gb->wy_diff = 0;
+ gb->window_disabled_while_active = false;
+ gb->current_line = 0;
+ gb->ly_for_comparison = 0;
+
+ gb->accessed_oam_row = -1;
+}
+
+static void add_object_from_index(GB_gameboy_t *gb, unsigned index)
+{
+ if (gb->n_visible_objs == 10) return;
+
+ /* TODO: It appears that DMA blocks PPU access to OAM, but it needs verification. */
+ if (gb->dma_steps_left && (gb->dma_cycles >= 0 || gb->is_dma_restarting)) {
+ return;
+ }
+
+ /* This reverse sorts the visible objects by location and priority */
+ GB_object_t *objects = (GB_object_t *) &gb->oam;
+ bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
+ signed y = objects[index].y - 16;
+ if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) {
+ unsigned j = 0;
+ for (; j < gb->n_visible_objs; j++) {
+ if (gb->obj_comparators[j] <= objects[index].x) break;
+ }
+ memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j);
+ memmove(gb->obj_comparators + j + 1, gb->obj_comparators + j, gb->n_visible_objs - j);
+ gb->visible_objs[j] = index;
+ gb->obj_comparators[j] = objects[index].x;
+ gb->n_visible_objs++;
+ }
+}
+
+static void render_pixel_if_possible(GB_gameboy_t *gb)
+{
+ GB_fifo_item_t *fifo_item = NULL;
+ GB_fifo_item_t *oam_fifo_item = NULL;
+ bool draw_oam = false;
+ bool bg_enabled = true, bg_priority = false;
+
+ if (!gb->bg_fifo_paused) {
+ fifo_item = fifo_pop(&gb->bg_fifo);
+ bg_priority = fifo_item->bg_priority;
+ }
+
+ if (!gb->oam_fifo_paused && fifo_size(&gb->oam_fifo)) {
+ oam_fifo_item = fifo_pop(&gb->oam_fifo);
+ /* Todo: Verify access timings */
+ if (oam_fifo_item->pixel && (gb->io_registers[GB_IO_LCDC] & 2)) {
+ draw_oam = true;
+ bg_priority |= oam_fifo_item->bg_priority;
+ }
+ }
+
+ /* Drop pixels for scrollings */
+ if (gb->position_in_line >= 160 || gb->disable_rendering) {
+ gb->position_in_line++;
+ return;
+ }
+ if (gb->bg_fifo_paused) return;
+
+ /* Mixing */
+
+ /* Todo: Verify access timings */
+ if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) {
+ if (gb->cgb_mode) {
+ bg_priority = false;
+ }
+ else {
+ bg_enabled = false;
+ }
+ }
+
+ uint8_t icd_pixel = 0;
+ {
+ uint8_t pixel = bg_enabled? fifo_item->pixel : 0;
+ if (pixel && bg_priority) {
+ draw_oam = false;
+ }
+ if (!gb->cgb_mode) {
+ pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
+ }
+ if (gb->sgb) {
+ if (gb->current_lcd_line < LINES) {
+ gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
+ }
+ }
+ else if (gb->model & GB_MODEL_NO_SFC_BIT) {
+ if (gb->icd_pixel_callback) {
+ icd_pixel = pixel;
+ }
+ }
+ else {
+ gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->background_palettes_rgb[fifo_item->palette * 4 + pixel];
+ }
+ }
+
+ if (draw_oam) {
+ uint8_t pixel = oam_fifo_item->pixel;
+ if (!gb->cgb_mode) {
+ /* Todo: Verify access timings */
+ pixel = ((gb->io_registers[oam_fifo_item->palette? GB_IO_OBP1 : GB_IO_OBP0] >> (pixel << 1)) & 3);
+ }
+ if (gb->sgb) {
+ if (gb->current_lcd_line < LINES) {
+ gb->sgb->screen_buffer[gb->position_in_line + gb->current_lcd_line * WIDTH] = gb->stopped? 0 : pixel;
+ }
+ }
+ else if (gb->model & GB_MODEL_NO_SFC_BIT) {
+ if (gb->icd_pixel_callback) {
+ icd_pixel = pixel;
+ //gb->icd_pixel_callback(gb, pixel);
+ }
+ }
+ else {
+ gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
+ }
+ }
+
+ if (gb->model & GB_MODEL_NO_SFC_BIT) {
+ if (gb->icd_pixel_callback) {
+ gb->icd_pixel_callback(gb, icd_pixel);
+ }
+ }
+
+ gb->position_in_line++;
+}
+
+/* All verified CGB timings are based on CGB CPU E. CGB CPUs >= D are known to have
+ slightly different timings than CPUs <= C.
+
+ Todo: Add support to CPU C and older */
+
+static inline uint8_t fetcher_y(GB_gameboy_t *gb)
+{
+ return gb->current_line + (gb->in_window? - gb->io_registers[GB_IO_WY] - gb->wy_diff : gb->io_registers[GB_IO_SCY]);
+}
+
+static void advance_fetcher_state_machine(GB_gameboy_t *gb)
+{
+ typedef enum {
+ GB_FETCHER_GET_TILE,
+ GB_FETCHER_GET_TILE_DATA_LOWER,
+ GB_FETCHER_GET_TILE_DATA_HIGH,
+ GB_FETCHER_PUSH,
+ GB_FETCHER_SLEEP,
+ } fetcher_step_t;
+
+ fetcher_step_t fetcher_state_machine [8] = {
+ GB_FETCHER_SLEEP,
+ GB_FETCHER_GET_TILE,
+ GB_FETCHER_SLEEP,
+ GB_FETCHER_GET_TILE_DATA_LOWER,
+ GB_FETCHER_SLEEP,
+ GB_FETCHER_GET_TILE_DATA_HIGH,
+ GB_FETCHER_SLEEP,
+ GB_FETCHER_PUSH,
+ };
+
+ switch (fetcher_state_machine[gb->fetcher_state]) {
+ case GB_FETCHER_GET_TILE: {
+ uint16_t map = 0x1800;
+
+ /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
+ if (gb->io_registers[GB_IO_LCDC] & 0x08 && !gb->in_window) {
+ map = 0x1C00;
+ }
+ else if (gb->io_registers[GB_IO_LCDC] & 0x40 && gb->in_window) {
+ map = 0x1C00;
+ }
+
+ /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
+ uint8_t y = fetcher_y(gb);
+ if (gb->model > GB_MODEL_CGB_C) {
+ /* This value is cached on the CGB-D and newer, so it cannot be used to mix tiles together */
+ gb->fetcher_y = y;
+ }
+ gb->current_tile = gb->vram[map + gb->fetcher_x + y / 8 * 32];
+ if (GB_is_cgb(gb)) {
+ /* The CGB actually accesses both the tile index AND the attributes in the same T-cycle.
+ This probably means the CGB has a 16-bit data bus for the VRAM. */
+ gb->current_tile_attributes = gb->vram[map + gb->fetcher_x + y / 8 * 32 + 0x2000];
+ }
+ gb->fetcher_x++;
+ gb->fetcher_x &= 0x1f;
+ }
+ gb->fetcher_state++;
+ break;
+
+ case GB_FETCHER_GET_TILE_DATA_LOWER: {
+ uint8_t y_flip = 0;
+ uint16_t tile_address = 0;
+ uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb);
+
+ /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong. */
+ if (gb->io_registers[GB_IO_LCDC] & 0x10) {
+ tile_address = gb->current_tile * 0x10;
+ }
+ else {
+ tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000;
+ }
+ if (gb->current_tile_attributes & 8) {
+ tile_address += 0x2000;
+ }
+ if (gb->current_tile_attributes & 0x40) {
+ y_flip = 0x7;
+ }
+ gb->current_tile_data[0] =
+ gb->vram[tile_address + ((y & 7) ^ y_flip) * 2];
+ }
+ gb->fetcher_state++;
+ break;
+
+ case GB_FETCHER_GET_TILE_DATA_HIGH: {
+ /* Todo: Verified for DMG (Tested: SGB2), CGB timing is wrong.
+ Additionally, on CGB-D and newer mixing two tiles by changing the tileset
+ bit mid-fetching causes a glitched mixing of the two, in comparison to the
+ more logical DMG version. */
+ uint16_t tile_address = 0;
+ uint8_t y = gb->model > GB_MODEL_CGB_C ? gb->fetcher_y : fetcher_y(gb);
+
+ if (gb->io_registers[GB_IO_LCDC] & 0x10) {
+ tile_address = gb->current_tile * 0x10;
+ }
+ else {
+ tile_address = (int8_t)gb->current_tile * 0x10 + 0x1000;
+ }
+ if (gb->current_tile_attributes & 8) {
+ tile_address += 0x2000;
+ }
+ uint8_t y_flip = 0;
+ if (gb->current_tile_attributes & 0x40) {
+ y_flip = 0x7;
+ }
+ gb->current_tile_data[1] =
+ gb->vram[tile_address + ((y & 7) ^ y_flip) * 2 + 1];
+ }
+ gb->fetcher_state++;
+ break;
+
+ case GB_FETCHER_PUSH: {
+ if (fifo_size(&gb->bg_fifo) > 0) break;
+ fifo_push_bg_row(&gb->bg_fifo, gb->current_tile_data[0], gb->current_tile_data[1],
+ gb->current_tile_attributes & 7, gb->current_tile_attributes & 0x80, gb->current_tile_attributes & 0x20);
+ gb->bg_fifo_paused = false;
+ gb->oam_fifo_paused = false;
+ gb->fetcher_state++;
+ }
+ break;
+
+ case GB_FETCHER_SLEEP:
+ {
+ gb->fetcher_state++;
+ }
+ break;
+ }
+
+ gb->fetcher_state &= 7;
+}
+
+/*
+ TODO: It seems that the STAT register's mode bits are always "late" by 4 T-cycles.
+ The PPU logic can be greatly simplified if that delay is simply emulated.
+ */
+void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
+{
+ /* The PPU does not advance while in STOP mode on the DMG */
+ if (gb->stopped && !GB_is_cgb(gb)) {
+ gb->cycles_in_stop_mode += cycles;
+ if (gb->cycles_in_stop_mode >= LCDC_PERIOD) {
+ gb->cycles_in_stop_mode -= LCDC_PERIOD;
+ display_vblank(gb);
+ }
+ return;
+ }
+ GB_object_t *objects = (GB_object_t *) &gb->oam;
+
+ GB_STATE_MACHINE(gb, display, cycles, 2) {
+ GB_STATE(gb, display, 1);
+ GB_STATE(gb, display, 2);
+ // GB_STATE(gb, display, 3);
+ // GB_STATE(gb, display, 4);
+ // GB_STATE(gb, display, 5);
+ GB_STATE(gb, display, 6);
+ GB_STATE(gb, display, 7);
+ GB_STATE(gb, display, 8);
+ // GB_STATE(gb, display, 9);
+ GB_STATE(gb, display, 10);
+ GB_STATE(gb, display, 11);
+ GB_STATE(gb, display, 12);
+ GB_STATE(gb, display, 13);
+ GB_STATE(gb, display, 14);
+ GB_STATE(gb, display, 15);
+ GB_STATE(gb, display, 16);
+ GB_STATE(gb, display, 17);
+ // GB_STATE(gb, display, 19);
+ GB_STATE(gb, display, 20);
+ GB_STATE(gb, display, 21);
+ GB_STATE(gb, display, 22);
+ GB_STATE(gb, display, 23);
+ // GB_STATE(gb, display, 24);
+ GB_STATE(gb, display, 25);
+ GB_STATE(gb, display, 26);
+ GB_STATE(gb, display, 27);
+ GB_STATE(gb, display, 28);
+ GB_STATE(gb, display, 29);
+ GB_STATE(gb, display, 30);
+ // GB_STATE(gb, display, 31);
+ GB_STATE(gb, display, 32);
+ GB_STATE(gb, display, 33);
+ GB_STATE(gb, display, 34);
+ GB_STATE(gb, display, 35);
+ GB_STATE(gb, display, 36);
+ GB_STATE(gb, display, 37);
+ GB_STATE(gb, display, 38);
+
+ }
+
+ if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
+ while (true) {
+ GB_SLEEP(gb, display, 1, LCDC_PERIOD);
+ display_vblank(gb);
+ }
+ return;
+ }
+
+ if (!GB_is_cgb(gb)) {
+ GB_SLEEP(gb, display, 23, 1);
+ }
+
+ /* Handle mode 2 on the very first line 0 */
+ gb->current_line = 0;
+ gb->ly_for_comparison = 0;
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ gb->mode_for_interrupt = -1;
+ gb->oam_read_blocked = false;
+ gb->vram_read_blocked = false;
+ gb->oam_write_blocked = false;
+ gb->vram_write_blocked = false;
+ gb->cgb_palettes_blocked = false;
+ gb->cycles_for_line = MODE2_LENGTH - 4;
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 2, MODE2_LENGTH - 4);
+
+ gb->oam_write_blocked = true;
+ gb->cycles_for_line += 2;
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 34, 2);
+
+ gb->n_visible_objs = 0;
+ gb->cycles_for_line += 8; // Mode 0 is shorter on the first line 0, so we augment cycles_for_line by 8 extra cycles.
+
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ gb->io_registers[GB_IO_STAT] |= 3;
+ gb->mode_for_interrupt = 3;
+
+ gb->oam_write_blocked = true;
+ gb->oam_read_blocked = true;
+ gb->vram_read_blocked = gb->cgb_double_speed;
+ gb->vram_write_blocked = gb->cgb_double_speed;
+ if (!GB_is_cgb(gb)) {
+ gb->vram_read_blocked = true;
+ gb->vram_write_blocked = true;
+ }
+ gb->cycles_for_line += 2;
+ GB_SLEEP(gb, display, 37, 2);
+
+ gb->cgb_palettes_blocked = true;
+ gb->cycles_for_line += 2;
+ GB_SLEEP(gb, display, 38, 2);
+
+ gb->vram_read_blocked = true;
+ gb->vram_write_blocked = true;
+ goto mode_3_start;
+
+
+ while (true) {
+ /* Lines 0 - 143 */
+ for (; gb->current_line < LINES; gb->current_line++) {
+ gb->oam_write_blocked = GB_is_cgb(gb) && !gb->cgb_double_speed;
+ gb->accessed_oam_row = 0;
+
+ GB_SLEEP(gb, display, 35, 2);
+ gb->oam_write_blocked = GB_is_cgb(gb);
+
+ GB_SLEEP(gb, display, 6, 1);
+ gb->io_registers[GB_IO_LY] = gb->current_line;
+ gb->oam_read_blocked = true;
+ gb->ly_for_comparison = gb->current_line? -1 : 0;
+
+ /* The OAM STAT interrupt occurs 1 T-cycle before STAT actually changes, except on line 0.
+ PPU glitch? */
+ if (gb->current_line != 0) {
+ gb->mode_for_interrupt = 2;
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ }
+ else if (!GB_is_cgb(gb)) {
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ }
+ GB_STAT_update(gb);
+
+ GB_SLEEP(gb, display, 7, 1);
+
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ gb->io_registers[GB_IO_STAT] |= 2;
+ gb->mode_for_interrupt = 2;
+ gb->oam_write_blocked = true;
+ gb->ly_for_comparison = gb->current_line;
+ GB_STAT_update(gb);
+ gb->mode_for_interrupt = -1;
+ GB_STAT_update(gb);
+ gb->n_visible_objs = 0;
+
+ for (gb->oam_search_index = 0; gb->oam_search_index < 40; gb->oam_search_index++) {
+ if (GB_is_cgb(gb)) {
+ add_object_from_index(gb, gb->oam_search_index);
+ /* The CGB does not care about the accessed OAM row as there's no OAM bug */
+ }
+ GB_SLEEP(gb, display, 8, 2);
+ if (!GB_is_cgb(gb)) {
+ add_object_from_index(gb, gb->oam_search_index);
+ gb->accessed_oam_row = (gb->oam_search_index & ~1) * 4 + 8;
+ }
+ if (gb->oam_search_index == 37) {
+ gb->vram_read_blocked = !GB_is_cgb(gb);
+ gb->vram_write_blocked = false;
+ gb->cgb_palettes_blocked = false;
+ gb->oam_write_blocked = GB_is_cgb(gb);
+ GB_STAT_update(gb);
+ }
+ }
+ gb->cycles_for_line = MODE2_LENGTH + 4;
+
+ gb->accessed_oam_row = -1;
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ gb->io_registers[GB_IO_STAT] |= 3;
+ gb->mode_for_interrupt = 3;
+ gb->vram_read_blocked = true;
+ gb->vram_write_blocked = true;
+ gb->cgb_palettes_blocked = false;
+ gb->oam_write_blocked = true;
+ gb->oam_read_blocked = true;
+
+ GB_STAT_update(gb);
+
+
+ uint8_t idle_cycles = 3;
+ if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) {
+ idle_cycles = 2;
+ }
+ gb->cycles_for_line += idle_cycles;
+ GB_SLEEP(gb, display, 10, idle_cycles);
+
+ gb->cgb_palettes_blocked = true;
+ gb->cycles_for_line += 2;
+ GB_SLEEP(gb, display, 32, 2);
+ mode_3_start:
+
+ fifo_clear(&gb->bg_fifo);
+ fifo_clear(&gb->oam_fifo);
+ /* Fill the FIFO with 8 pixels of "junk", it's going to be dropped anyway. */
+ fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false);
+ /* Todo: find out actual access time of SCX */
+ gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
+
+ gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f;
+ gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7);
+
+
+ /* The actual rendering cycle */
+ gb->fetcher_state = 0;
+ gb->bg_fifo_paused = false;
+ gb->oam_fifo_paused = false;
+ gb->in_window = false;
+ while (true) {
+ /* Handle objects */
+ /* When the sprite enabled bit is off, this proccess is skipped entirely on the DMG, but not on the CGB.
+ On the CGB, this bit is checked only when the pixel is actually popped from the FIFO. */
+
+ while (gb->n_visible_objs != 0 &&
+ (gb->position_in_line < 160 || gb->position_in_line >= (uint8_t)(-8)) &&
+ gb->obj_comparators[gb->n_visible_objs - 1] < (uint8_t)(gb->position_in_line + 8)) {
+ gb->n_visible_objs--;
+ }
+ while (gb->n_visible_objs != 0 &&
+ (gb->io_registers[GB_IO_LCDC] & 2 || GB_is_cgb(gb)) &&
+ gb->obj_comparators[gb->n_visible_objs - 1] == (uint8_t)(gb->position_in_line + 8)) {
+
+ while (gb->fetcher_state < 5) {
+ advance_fetcher_state_machine(gb);
+ gb->cycles_for_line++;
+ GB_SLEEP(gb, display, 27, 1);
+ }
+
+ /* Todo: Measure if penalty occurs before or after waiting for the fetcher. */
+ if (gb->extra_penalty_for_sprite_at_0 != 0) {
+ if (gb->obj_comparators[gb->n_visible_objs - 1] == 0) {
+ gb->cycles_for_line += gb->extra_penalty_for_sprite_at_0;
+ GB_SLEEP(gb, display, 28, gb->extra_penalty_for_sprite_at_0);
+ gb->extra_penalty_for_sprite_at_0 = 0;
+ }
+ }
+
+ gb->cycles_for_line += 6;
+ GB_SLEEP(gb, display, 20, 6);
+ /* TODO: what does the PPU read if DMA is active? */
+ GB_object_t *object = &objects[gb->visible_objs[gb->n_visible_objs - 1]];
+
+ bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0; /* Todo: Which T-cycle actually reads this? */
+ uint8_t tile_y = (gb->current_line - object->y) & (height_16? 0xF : 7);
+
+ if (object->flags & 0x40) { /* Flip Y */
+ tile_y ^= height_16? 0xF : 7;
+ }
+
+ /* Todo: I'm not 100% sure an access to OAM can't trigger the OAM bug while we're accessing this */
+ uint16_t line_address = (height_16? object->tile & 0xFE : object->tile) * 0x10 + tile_y * 2;
+
+ if (gb->cgb_mode && (object->flags & 0x8)) { /* Use VRAM bank 2 */
+ line_address += 0x2000;
+ }
+
+ uint8_t palette = (object->flags & 0x10) ? 1 : 0;
+ if (gb->cgb_mode) {
+ palette = object->flags & 0x7;
+ }
+
+ fifo_overlay_object_row(&gb->oam_fifo,
+ gb->vram[line_address],
+ gb->vram[line_address + 1],
+ palette,
+ object->flags & 0x80,
+ gb->cgb_mode? gb->visible_objs[gb->n_visible_objs - 1] : 0,
+ object->flags & 0x20);
+
+ gb->n_visible_objs--;
+ }
+
+ /* Handle window */
+ /* Todo: Timing (Including penalty and access timings) not verified by test ROM */
+ if (!gb->in_window && window_enabled(gb) &&
+ gb->current_line >= gb->io_registers[GB_IO_WY] + gb->wy_diff &&
+ (uint8_t)(gb->position_in_line + 7) == gb->io_registers[GB_IO_WX]) {
+ gb->in_window = true;
+ fifo_clear(&gb->bg_fifo);
+ gb->bg_fifo_paused = true;
+ gb->oam_fifo_paused = true;
+ gb->fetcher_x = 0;
+ gb->fetcher_state = 0;
+ }
+
+ render_pixel_if_possible(gb);
+ advance_fetcher_state_machine(gb);
+
+ if (gb->position_in_line == 160) break;
+ gb->cycles_for_line++;
+ GB_SLEEP(gb, display, 21, 1);
+ }
+
+ if (GB_is_cgb(gb) && gb->model <= GB_MODEL_CGB_C) {
+ gb->cycles_for_line++;
+ GB_SLEEP(gb, display, 30, 1);
+ }
+
+ if (!gb->cgb_double_speed) {
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ gb->mode_for_interrupt = 0;
+ gb->oam_read_blocked = false;
+ gb->vram_read_blocked = false;
+ gb->oam_write_blocked = false;
+ gb->vram_write_blocked = false;
+ }
+
+ gb->cycles_for_line++;
+ GB_SLEEP(gb, display, 22, 1);
+
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ gb->mode_for_interrupt = 0;
+ gb->oam_read_blocked = false;
+ gb->vram_read_blocked = false;
+ gb->oam_write_blocked = false;
+ gb->vram_write_blocked = false;
+ GB_STAT_update(gb);
+
+ /* Todo: Measure this value */
+ gb->cycles_for_line += 2;
+ GB_SLEEP(gb, display, 33, 2);
+ gb->cgb_palettes_blocked = !gb->cgb_double_speed;
+
+ gb->cycles_for_line += 2;
+ GB_SLEEP(gb, display, 36, 2);
+ gb->cgb_palettes_blocked = false;
+
+ gb->cycles_for_line += 8;
+ GB_SLEEP(gb, display, 25, 8);
+
+ if (gb->hdma_on_hblank) {
+ gb->hdma_starting = true;
+ }
+ GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
+ gb->mode_for_interrupt = 2;
+
+ // Todo: unverified timing
+ gb->current_lcd_line++;
+ if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) {
+ display_vblank(gb);
+ }
+
+ if (gb->icd_hreset_callback) {
+ gb->icd_hreset_callback(gb);
+ }
+ }
+
+ /* Lines 144 - 152 */
+ for (; gb->current_line < VIRTUAL_LINES - 1; gb->current_line++) {
+ gb->io_registers[GB_IO_LY] = gb->current_line;
+ gb->ly_for_comparison = -1;
+ GB_SLEEP(gb, display, 26, 2);
+ if (gb->current_line == LINES) {
+ gb->mode_for_interrupt = 2;
+ }
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 12, 2);
+ gb->ly_for_comparison = gb->current_line;
+
+ if (gb->current_line == LINES) {
+ /* Entering VBlank state triggers the OAM interrupt */
+ gb->io_registers[GB_IO_STAT] &= ~3;
+ gb->io_registers[GB_IO_STAT] |= 1;
+ gb->io_registers[GB_IO_IF] |= 1;
+ gb->mode_for_interrupt = 2;
+ GB_STAT_update(gb);
+ gb->mode_for_interrupt = 1;
+ GB_STAT_update(gb);
+
+ if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) {
+ if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
+ display_vblank(gb);
+ }
+ gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
+ }
+ else {
+ gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
+ if (!GB_is_sgb(gb) || gb->current_lcd_line < LINES) {
+ display_vblank(gb);
+ }
+ }
+ }
+
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 13, LINE_LENGTH - 4);
+ }
+
+ /* TODO: Verified on SGB2 and CGB-E. Actual interrupt timings not tested. */
+ /* Lines 153 */
+ gb->io_registers[GB_IO_LY] = 153;
+ gb->ly_for_comparison = -1;
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 14, (gb->model > GB_MODEL_CGB_C)? 4: 6);
+
+ if (!GB_is_cgb(gb)) {
+ gb->io_registers[GB_IO_LY] = 0;
+ }
+ gb->ly_for_comparison = 153;
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 15, (gb->model > GB_MODEL_CGB_C)? 4: 2);
+
+ gb->io_registers[GB_IO_LY] = 0;
+ gb->ly_for_comparison = (gb->model > GB_MODEL_CGB_C)? 153 : -1;
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 16, 4);
+
+ gb->ly_for_comparison = 0;
+ GB_STAT_update(gb);
+ GB_SLEEP(gb, display, 29, 12); /* Writing to LYC during this period on a CGB has side effects */
+ GB_SLEEP(gb, display, 17, LINE_LENGTH - 24);
+
+
+ /* Reset window rendering state */
+ gb->wy_diff = 0;
+ gb->window_disabled_while_active = false;
+ gb->current_line = 0;
+ // TODO: not the correct timing
+ gb->current_lcd_line = 0;
+ if (gb->icd_vreset_callback) {
+ gb->icd_vreset_callback(gb);
+ }
+ }
+}
+
+void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index)
+{
+ uint32_t none_palette[4];
+ uint32_t *palette = NULL;
+
+ switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) {
+ default:
+ case GB_PALETTE_NONE:
+ none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
+ none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
+ none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
+ none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 );
+ palette = none_palette;
+ break;
+ case GB_PALETTE_BACKGROUND:
+ palette = gb->background_palettes_rgb + (4 * (palette_index & 7));
+ break;
+ case GB_PALETTE_OAM:
+ palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7));
+ break;
+ }
+
+ for (unsigned y = 0; y < 192; y++) {
+ for (unsigned x = 0; x < 256; x++) {
+ if (x >= 128 && !GB_is_cgb(gb)) {
+ *(dest++) = gb->background_palettes_rgb[0];
+ continue;
+ }
+ uint16_t tile = (x % 128) / 8 + y / 8 * 16;
+ uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0);
+ uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) |
+ ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1);
+
+ if (!gb->cgb_mode) {
+ if (palette_type == GB_PALETTE_BACKGROUND) {
+ pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
+ }
+ else if (!gb->cgb_mode) {
+ if (palette_type == GB_PALETTE_OAM) {
+ pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3);
+ }
+ }
+ }
+
+
+ *(dest++) = palette[pixel];
+ }
+ }
+}
+
+void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type)
+{
+ uint32_t none_palette[4];
+ uint32_t *palette = NULL;
+ uint16_t map = 0x1800;
+
+ switch (GB_is_cgb(gb)? palette_type : GB_PALETTE_NONE) {
+ case GB_PALETTE_NONE:
+ none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
+ none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
+ none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
+ none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 );
+ palette = none_palette;
+ break;
+ case GB_PALETTE_BACKGROUND:
+ palette = gb->background_palettes_rgb + (4 * (palette_index & 7));
+ break;
+ case GB_PALETTE_OAM:
+ palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7));
+ break;
+ case GB_PALETTE_AUTO:
+ break;
+ }
+
+ if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) {
+ map = 0x1c00;
+ }
+
+ if (tileset_type == GB_TILESET_AUTO) {
+ tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000;
+ }
+
+ for (unsigned y = 0; y < 256; y++) {
+ for (unsigned x = 0; x < 256; x++) {
+ uint8_t tile = gb->vram[map + x/8 + y/8 * 32];
+ uint16_t tile_address;
+ uint8_t attributes = 0;
+
+ if (tileset_type == GB_TILESET_8800) {
+ tile_address = tile * 0x10;
+ }
+ else {
+ tile_address = (int8_t) tile * 0x10 + 0x1000;
+ }
+
+ if (gb->cgb_mode) {
+ attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000];
+ }
+
+ if (attributes & 0x8) {
+ tile_address += 0x2000;
+ }
+
+ uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) |
+ ((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1);
+
+ if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) {
+ pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3);
+ }
+
+ if (palette) {
+ *(dest++) = palette[pixel];
+ }
+ else {
+ *(dest++) = gb->background_palettes_rgb[(attributes & 7) * 4 + pixel];
+ }
+ }
+ }
+}
+
+uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height)
+{
+ uint8_t count = 0;
+ *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8;
+ uint8_t oam_to_dest_index[40] = {0,};
+ for (unsigned y = 0; y < LINES; y++) {
+ GB_object_t *sprite = (GB_object_t *) &gb->oam;
+ uint8_t sprites_in_line = 0;
+ for (uint8_t i = 0; i < 40; i++, sprite++) {
+ int sprite_y = sprite->y - 16;
+ bool obscured = false;
+ // Is sprite not in this line?
+ if (sprite_y > y || sprite_y + *sprite_height <= y) continue;
+ if (++sprites_in_line == 11) obscured = true;
+
+ GB_oam_info_t *info = NULL;
+ if (!oam_to_dest_index[i]) {
+ info = dest + count;
+ oam_to_dest_index[i] = ++count;
+ info->x = sprite->x;
+ info->y = sprite->y;
+ info->tile = *sprite_height == 16? sprite->tile & 0xFE : sprite->tile;
+ info->flags = sprite->flags;
+ info->obscured_by_line_limit = false;
+ info->oam_addr = 0xFE00 + i * sizeof(*sprite);
+ }
+ else {
+ info = dest + oam_to_dest_index[i] - 1;
+ }
+ info->obscured_by_line_limit |= obscured;
+ }
+ }
+
+ for (unsigned i = 0; i < count; i++) {
+ uint16_t vram_address = dest[i].tile * 0x10;
+ uint8_t flags = dest[i].flags;
+ uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0);
+ if (GB_is_cgb(gb) && (flags & 0x8)) {
+ vram_address += 0x2000;
+ }
+
+ for (unsigned y = 0; y < *sprite_height; y++) {
+ UNROLL
+ for (unsigned x = 0; x < 8; x++) {
+ uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) |
+ ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 );
+
+ if (!gb->cgb_mode) {
+ color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3;
+ }
+ dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?*sprite_height - 1 -y:y) * 8] = gb->sprite_palettes_rgb[palette * 4 + color];
+ }
+ vram_address += 2;
+ }
+ }
+ return count;
+}
+
+/* Called when a write might enable or disable the window */
+void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value)
+{
+ bool before = window_enabled(gb);
+ gb->io_registers[addr] = value;
+ bool after = window_enabled(gb);
+
+ if (before != after && gb->current_line < LINES) {
+ /* Window was disabled or enabled outside of vblank */
+ if (gb->current_line >= gb->io_registers[GB_IO_WY]) {
+ if (after) {
+ if (!gb->window_disabled_while_active) {
+ /* Window was turned on for the first time this frame while LY > WY,
+ should start window in the next line */
+ gb->wy_diff = gb->current_line + 1 - gb->io_registers[GB_IO_WY];
+ }
+ else {
+ gb->wy_diff += gb->current_line;
+ }
+ }
+ else {
+ gb->wy_diff -= gb->current_line;
+ gb->window_disabled_while_active = true;
+ }
+ }
+ }
+}
diff --git a/gb/Core/display.h b/gb/Core/display.h
new file mode 100644
index 0000000..9d1d1b4
--- /dev/null
+++ b/gb/Core/display.h
@@ -0,0 +1,54 @@
+#ifndef display_h
+#define display_h
+
+#include "gb.h"
+#include
+#include
+
+#ifdef GB_INTERNAL
+void GB_display_run(GB_gameboy_t *gb, uint8_t cycles);
+void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index);
+void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value);
+void GB_STAT_update(GB_gameboy_t *gb);
+void GB_lcd_off(GB_gameboy_t *gb);
+#endif
+
+typedef enum {
+ GB_PALETTE_NONE,
+ GB_PALETTE_BACKGROUND,
+ GB_PALETTE_OAM,
+ GB_PALETTE_AUTO,
+} GB_palette_type_t;
+
+typedef enum {
+ GB_MAP_AUTO,
+ GB_MAP_9800,
+ GB_MAP_9C00,
+} GB_map_type_t;
+
+typedef enum {
+ GB_TILESET_AUTO,
+ GB_TILESET_8800,
+ GB_TILESET_8000,
+} GB_tileset_type_t;
+
+typedef struct {
+ uint32_t image[128];
+ uint8_t x, y, tile, flags;
+ uint16_t oam_addr;
+ bool obscured_by_line_limit;
+} GB_oam_info_t;
+
+typedef enum {
+ GB_COLOR_CORRECTION_DISABLED,
+ GB_COLOR_CORRECTION_CORRECT_CURVES,
+ GB_COLOR_CORRECTION_EMULATE_HARDWARE,
+ GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS,
+} GB_color_correction_mode_t;
+
+void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index);
+void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type);
+uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height);
+uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color);
+void GB_set_color_correction_mode(GB_gameboy_t *gb, GB_color_correction_mode_t mode);
+#endif /* display_h */
diff --git a/gb/Core/gb.c b/gb/Core/gb.c
new file mode 100644
index 0000000..f29d400
--- /dev/null
+++ b/gb/Core/gb.c
@@ -0,0 +1,1084 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifndef _WIN32
+#include
+#include
+#endif
+#include "random.h"
+#include "gb.h"
+
+
+#ifdef DISABLE_REWIND
+#define GB_rewind_free(...)
+#define GB_rewind_push(...)
+#endif
+
+
+static inline uint32_t state_magic(void)
+{
+ if (sizeof(bool) == 1) return 'SAME';
+ return 'S4ME';
+}
+
+void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
+{
+ char *string = NULL;
+ vasprintf(&string, fmt, args);
+ if (string) {
+ if (gb->log_callback) {
+ gb->log_callback(gb, string, attributes);
+ }
+ else {
+ /* Todo: Add ANSI escape sequences for attributed text */
+ printf("%s", string);
+ }
+ }
+ free(string);
+}
+
+void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ GB_attributed_logv(gb, attributes, fmt, args);
+ va_end(args);
+}
+
+void GB_log(GB_gameboy_t *gb, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ GB_attributed_logv(gb, 0, fmt, args);
+ va_end(args);
+}
+
+#ifndef DISABLE_DEBUGGER
+static char *default_input_callback(GB_gameboy_t *gb)
+{
+ char *expression = NULL;
+ size_t size = 0;
+
+ if (getline(&expression, &size, stdin) == -1) {
+ /* The user doesn't have STDIN or used ^D. We make sure the program keeps running. */
+ GB_set_async_input_callback(gb, NULL); /* Disable async input */
+ return strdup("c");
+ }
+
+ if (!expression) {
+ return strdup("");
+ }
+
+ size_t length = strlen(expression);
+ if (expression[length - 1] == '\n') {
+ expression[length - 1] = 0;
+ }
+ return expression;
+}
+
+static char *default_async_input_callback(GB_gameboy_t *gb)
+{
+#ifndef _WIN32
+ fd_set set;
+ FD_ZERO(&set);
+ FD_SET(STDIN_FILENO, &set);
+ struct timeval time = {0,};
+ if (select(1, &set, NULL, NULL, &time) == 1) {
+ if (feof(stdin)) {
+ GB_set_async_input_callback(gb, NULL); /* Disable async input */
+ return NULL;
+ }
+ return default_input_callback(gb);
+ }
+#endif
+ return NULL;
+}
+#endif
+
+void GB_init(GB_gameboy_t *gb, GB_model_t model)
+{
+ memset(gb, 0, sizeof(*gb));
+ gb->model = model;
+ if (GB_is_cgb(gb)) {
+ gb->ram = malloc(gb->ram_size = 0x1000 * 8);
+ gb->vram = malloc(gb->vram_size = 0x2000 * 2);
+ }
+ else {
+ gb->ram = malloc(gb->ram_size = 0x2000);
+ gb->vram = malloc(gb->vram_size = 0x2000);
+ }
+
+#ifndef DISABLE_DEBUGGER
+ gb->input_callback = default_input_callback;
+ gb->async_input_callback = default_async_input_callback;
+#endif
+ gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type
+ gb->clock_multiplier = 1.0;
+
+ if (model & GB_MODEL_NO_SFC_BIT) {
+ /* Disable time syncing. Timing should be done by the SFC emulator. */
+ gb->turbo = true;
+ }
+
+ GB_reset(gb);
+}
+
+GB_model_t GB_get_model(GB_gameboy_t *gb)
+{
+ return gb->model;
+}
+
+void GB_free(GB_gameboy_t *gb)
+{
+ gb->magic = 0;
+ if (gb->ram) {
+ free(gb->ram);
+ }
+ if (gb->vram) {
+ free(gb->vram);
+ }
+ if (gb->mbc_ram) {
+ free(gb->mbc_ram);
+ }
+ if (gb->rom) {
+ free(gb->rom);
+ }
+ if (gb->breakpoints) {
+ free(gb->breakpoints);
+ }
+ if (gb->sgb) {
+ free(gb->sgb);
+ }
+ if (gb->nontrivial_jump_state) {
+ free(gb->nontrivial_jump_state);
+ }
+#ifndef DISABLE_DEBUGGER
+ GB_debugger_clear_symbols(gb);
+#endif
+ GB_rewind_free(gb);
+ memset(gb, 0, sizeof(*gb));
+}
+
+int GB_load_boot_rom(GB_gameboy_t *gb, const char *path)
+{
+ FILE *f = fopen(path, "rb");
+ if (!f) {
+ GB_log(gb, "Could not open boot ROM: %s.\n", strerror(errno));
+ return errno;
+ }
+ fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f);
+ fclose(f);
+ return 0;
+}
+
+void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size)
+{
+ if (size > sizeof(gb->boot_rom)) {
+ size = sizeof(gb->boot_rom);
+ }
+ memset(gb->boot_rom, 0xFF, sizeof(gb->boot_rom));
+ memcpy(gb->boot_rom, buffer, size);
+}
+
+int GB_load_rom(GB_gameboy_t *gb, const char *path)
+{
+ FILE *f = fopen(path, "rb");
+ if (!f) {
+ GB_log(gb, "Could not open ROM: %s.\n", strerror(errno));
+ return errno;
+ }
+ fseek(f, 0, SEEK_END);
+ gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */
+ /* And then round to a power of two */
+ while (gb->rom_size & (gb->rom_size - 1)) {
+ /* I promise this works. */
+ gb->rom_size |= gb->rom_size >> 1;
+ gb->rom_size++;
+ }
+ fseek(f, 0, SEEK_SET);
+ if (gb->rom) {
+ free(gb->rom);
+ }
+ gb->rom = malloc(gb->rom_size);
+ memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */
+ fread(gb->rom, 1, gb->rom_size, f);
+ fclose(f);
+ GB_configure_cart(gb);
+
+ return 0;
+}
+
+void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
+{
+ gb->rom_size = (size + 0x3fff) & ~0x3fff;
+ while (gb->rom_size & (gb->rom_size - 1)) {
+ gb->rom_size |= gb->rom_size >> 1;
+ gb->rom_size++;
+ }
+ if (gb->rom) {
+ free(gb->rom);
+ }
+ gb->rom = malloc(gb->rom_size);
+ memset(gb->rom, 0xff, gb->rom_size);
+ memcpy(gb->rom, buffer, size);
+ GB_configure_cart(gb);
+}
+
+typedef struct {
+ uint8_t seconds;
+ uint8_t padding1[3];
+ uint8_t minutes;
+ uint8_t padding2[3];
+ uint8_t hours;
+ uint8_t padding3[3];
+ uint8_t days;
+ uint8_t padding4[3];
+ uint8_t high;
+ uint8_t padding5[3];
+} GB_vba_rtc_time_t;
+
+typedef union {
+ struct __attribute__((packed)) {
+ GB_rtc_time_t rtc_real;
+ time_t last_rtc_second; /* Platform specific endianess and size */
+ } sameboy_legacy;
+ struct {
+ /* Used by VBA versions with 32-bit timestamp*/
+ GB_vba_rtc_time_t rtc_real, rtc_latched;
+ uint32_t last_rtc_second; /* Always little endian */
+ } vba32;
+ struct {
+ /* Used by BGB and VBA versions with 64-bit timestamp*/
+ GB_vba_rtc_time_t rtc_real, rtc_latched;
+ uint64_t last_rtc_second; /* Always little endian */
+ } vba64;
+} GB_rtc_save_t;
+
+int GB_save_battery_size(GB_gameboy_t *gb)
+{
+ if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
+ if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
+
+ GB_rtc_save_t rtc_save_size;
+ return gb->mbc_ram_size + (gb->cartridge_type->has_rtc ? sizeof(rtc_save_size.vba64) : 0);
+}
+
+int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size)
+{
+ if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
+ if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
+
+ if (size < GB_save_battery_size(gb)) return EIO;
+
+ memcpy(buffer, gb->mbc_ram, gb->mbc_ram_size);
+
+ if (gb->cartridge_type->has_rtc) {
+ GB_rtc_save_t rtc_save = {{{{0,}},},};
+ rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds;
+ rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes;
+ rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours;
+ rtc_save.vba64.rtc_real.days = gb->rtc_real.days;
+ rtc_save.vba64.rtc_real.high = gb->rtc_real.high;
+ rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds;
+ rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes;
+ rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours;
+ rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days;
+ rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high;
+#ifdef GB_BIG_ENDIAN
+ rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second);
+#else
+ rtc_save.vba64.last_rtc_second = gb->last_rtc_second;
+#endif
+ memcpy(buffer + gb->mbc_ram_size, &rtc_save.vba64, sizeof(rtc_save.vba64));
+ }
+
+ errno = 0;
+ return errno;
+}
+
+int GB_save_battery(GB_gameboy_t *gb, const char *path)
+{
+ if (!gb->cartridge_type->has_battery) return 0; // Nothing to save.
+ if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */
+ FILE *f = fopen(path, "wb");
+ if (!f) {
+ GB_log(gb, "Could not open battery save: %s.\n", strerror(errno));
+ return errno;
+ }
+
+ if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
+ fclose(f);
+ return EIO;
+ }
+ if (gb->cartridge_type->has_rtc) {
+ GB_rtc_save_t rtc_save = {{{{0,}},},};
+ rtc_save.vba64.rtc_real.seconds = gb->rtc_real.seconds;
+ rtc_save.vba64.rtc_real.minutes = gb->rtc_real.minutes;
+ rtc_save.vba64.rtc_real.hours = gb->rtc_real.hours;
+ rtc_save.vba64.rtc_real.days = gb->rtc_real.days;
+ rtc_save.vba64.rtc_real.high = gb->rtc_real.high;
+ rtc_save.vba64.rtc_latched.seconds = gb->rtc_latched.seconds;
+ rtc_save.vba64.rtc_latched.minutes = gb->rtc_latched.minutes;
+ rtc_save.vba64.rtc_latched.hours = gb->rtc_latched.hours;
+ rtc_save.vba64.rtc_latched.days = gb->rtc_latched.days;
+ rtc_save.vba64.rtc_latched.high = gb->rtc_latched.high;
+#ifdef GB_BIG_ENDIAN
+ rtc_save.vba64.last_rtc_second = __builtin_bswap64(gb->last_rtc_second);
+#else
+ rtc_save.vba64.last_rtc_second = gb->last_rtc_second;
+#endif
+ if (fwrite(&rtc_save.vba64, 1, sizeof(rtc_save.vba64), f) != sizeof(rtc_save.vba64)) {
+ fclose(f);
+ return EIO;
+ }
+
+ }
+
+ errno = 0;
+ fclose(f);
+ return errno;
+}
+
+void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size)
+{
+ memcpy(gb->mbc_ram, buffer, MIN(gb->mbc_ram_size, size));
+ if (size <= gb->mbc_ram_size) {
+ goto reset_rtc;
+ }
+
+ GB_rtc_save_t rtc_save;
+ memcpy(&rtc_save, buffer + gb->mbc_ram_size, MIN(sizeof(rtc_save), size));
+ switch (size - gb->mbc_ram_size) {
+ case sizeof(rtc_save.sameboy_legacy):
+ memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
+ memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
+ gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second;
+ break;
+
+ case sizeof(rtc_save.vba32):
+ gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds;
+ gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes;
+ gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours;
+ gb->rtc_real.days = rtc_save.vba32.rtc_real.days;
+ gb->rtc_real.high = rtc_save.vba32.rtc_real.high;
+ gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds;
+ gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes;
+ gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours;
+ gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days;
+ gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high;
+#ifdef GB_BIG_ENDIAN
+ gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second);
+#else
+ gb->last_rtc_second = rtc_save.vba32.last_rtc_second;
+#endif
+ break;
+
+ case sizeof(rtc_save.vba64):
+ gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds;
+ gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes;
+ gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours;
+ gb->rtc_real.days = rtc_save.vba64.rtc_real.days;
+ gb->rtc_real.high = rtc_save.vba64.rtc_real.high;
+ gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds;
+ gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes;
+ gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours;
+ gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days;
+ gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high;
+#ifdef GB_BIG_ENDIAN
+ gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second);
+#else
+ gb->last_rtc_second = rtc_save.vba64.last_rtc_second;
+#endif
+ break;
+
+ default:
+ goto reset_rtc;
+ }
+ if (gb->last_rtc_second > time(NULL)) {
+ /* We must reset RTC here, or it will not advance. */
+ goto reset_rtc;
+ }
+
+ if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time,
+ so if the value we read is lower it means it wasn't
+ really RTC data. */
+ goto reset_rtc;
+ }
+ goto exit;
+reset_rtc:
+ gb->last_rtc_second = time(NULL);
+ gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
+exit:
+ return;
+}
+
+/* Loading will silently stop if the format is incomplete */
+void GB_load_battery(GB_gameboy_t *gb, const char *path)
+{
+ FILE *f = fopen(path, "rb");
+ if (!f) {
+ return;
+ }
+
+ if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
+ goto reset_rtc;
+ }
+
+ GB_rtc_save_t rtc_save;
+ switch (fread(&rtc_save, 1, sizeof(rtc_save), f)) {
+ case sizeof(rtc_save.sameboy_legacy):
+ memcpy(&gb->rtc_real, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
+ memcpy(&gb->rtc_latched, &rtc_save.sameboy_legacy.rtc_real, sizeof(gb->rtc_real));
+ gb->last_rtc_second = rtc_save.sameboy_legacy.last_rtc_second;
+ break;
+
+ case sizeof(rtc_save.vba32):
+ gb->rtc_real.seconds = rtc_save.vba32.rtc_real.seconds;
+ gb->rtc_real.minutes = rtc_save.vba32.rtc_real.minutes;
+ gb->rtc_real.hours = rtc_save.vba32.rtc_real.hours;
+ gb->rtc_real.days = rtc_save.vba32.rtc_real.days;
+ gb->rtc_real.high = rtc_save.vba32.rtc_real.high;
+ gb->rtc_latched.seconds = rtc_save.vba32.rtc_latched.seconds;
+ gb->rtc_latched.minutes = rtc_save.vba32.rtc_latched.minutes;
+ gb->rtc_latched.hours = rtc_save.vba32.rtc_latched.hours;
+ gb->rtc_latched.days = rtc_save.vba32.rtc_latched.days;
+ gb->rtc_latched.high = rtc_save.vba32.rtc_latched.high;
+#ifdef GB_BIG_ENDIAN
+ gb->last_rtc_second = __builtin_bswap32(rtc_save.vba32.last_rtc_second);
+#else
+ gb->last_rtc_second = rtc_save.vba32.last_rtc_second;
+#endif
+ break;
+
+ case sizeof(rtc_save.vba64):
+ gb->rtc_real.seconds = rtc_save.vba64.rtc_real.seconds;
+ gb->rtc_real.minutes = rtc_save.vba64.rtc_real.minutes;
+ gb->rtc_real.hours = rtc_save.vba64.rtc_real.hours;
+ gb->rtc_real.days = rtc_save.vba64.rtc_real.days;
+ gb->rtc_real.high = rtc_save.vba64.rtc_real.high;
+ gb->rtc_latched.seconds = rtc_save.vba64.rtc_latched.seconds;
+ gb->rtc_latched.minutes = rtc_save.vba64.rtc_latched.minutes;
+ gb->rtc_latched.hours = rtc_save.vba64.rtc_latched.hours;
+ gb->rtc_latched.days = rtc_save.vba64.rtc_latched.days;
+ gb->rtc_latched.high = rtc_save.vba64.rtc_latched.high;
+#ifdef GB_BIG_ENDIAN
+ gb->last_rtc_second = __builtin_bswap64(rtc_save.vba64.last_rtc_second);
+#else
+ gb->last_rtc_second = rtc_save.vba64.last_rtc_second;
+#endif
+ break;
+
+ default:
+ goto reset_rtc;
+ }
+ if (gb->last_rtc_second > time(NULL)) {
+ /* We must reset RTC here, or it will not advance. */
+ goto reset_rtc;
+ }
+
+ if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time,
+ so if the value we read is lower it means it wasn't
+ really RTC data. */
+ goto reset_rtc;
+ }
+ goto exit;
+reset_rtc:
+ gb->last_rtc_second = time(NULL);
+ gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */
+exit:
+ fclose(f);
+ return;
+}
+
+uint8_t GB_run(GB_gameboy_t *gb)
+{
+ gb->vblank_just_occured = false;
+
+ if (gb->sgb && gb->sgb->intro_animation < 140) {
+ /* On the SGB, the GB is halted after finishing the boot ROM.
+ Then, after the boot animation is almost done, it's reset.
+ Since the SGB HLE does not perform any header validity checks,
+ we just halt the CPU (with hacky code) until the correct time.
+ This ensures the Nintendo logo doesn't flash on screen, and
+ the game does "run in background" while the animation is playing. */
+ GB_display_run(gb, 228);
+ gb->cycles_since_last_sync += 228;
+ return 228;
+ }
+
+ GB_debugger_run(gb);
+ gb->cycles_since_run = 0;
+ GB_cpu_run(gb);
+ if (gb->vblank_just_occured) {
+ GB_rtc_run(gb);
+ GB_debugger_handle_async_commands(gb);
+ GB_rewind_push(gb);
+ }
+ return gb->cycles_since_run;
+}
+
+uint64_t GB_run_frame(GB_gameboy_t *gb)
+{
+ /* Configure turbo temporarily, the user wants to handle FPS capping manually. */
+ bool old_turbo = gb->turbo;
+ bool old_dont_skip = gb->turbo_dont_skip;
+ gb->turbo = true;
+ gb->turbo_dont_skip = true;
+
+ gb->cycles_since_last_sync = 0;
+ while (true) {
+ GB_run(gb);
+ if (gb->vblank_just_occured) {
+ break;
+ }
+ }
+ gb->turbo = old_turbo;
+ gb->turbo_dont_skip = old_dont_skip;
+ return gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
+}
+
+void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output)
+{
+ gb->screen = output;
+}
+
+void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback)
+{
+ gb->vblank_callback = callback;
+}
+
+void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback)
+{
+ gb->log_callback = callback;
+}
+
+void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
+{
+#ifndef DISABLE_DEBUGGER
+ if (gb->input_callback == default_input_callback) {
+ gb->async_input_callback = NULL;
+ }
+ gb->input_callback = callback;
+#endif
+}
+
+void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback)
+{
+#ifndef DISABLE_DEBUGGER
+ gb->async_input_callback = callback;
+#endif
+}
+
+
+void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback)
+{
+ if (!gb->rgb_encode_callback && !GB_is_cgb(gb)) {
+ gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] =
+ callback(gb, 0xFF, 0xFF, 0xFF);
+ gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] =
+ callback(gb, 0xAA, 0xAA, 0xAA);
+ gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] =
+ callback(gb, 0x55, 0x55, 0x55);
+ gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] =
+ callback(gb, 0, 0, 0);
+ }
+
+ gb->rgb_encode_callback = callback;
+
+ for (unsigned i = 0; i < 32; i++) {
+ GB_palette_changed(gb, true, i * 2);
+ GB_palette_changed(gb, false, i * 2);
+ }
+}
+
+void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback)
+{
+ gb->infrared_callback = callback;
+}
+
+void GB_set_infrared_input(GB_gameboy_t *gb, bool state)
+{
+ gb->infrared_input = state;
+ gb->cycles_since_input_ir_change = 0;
+ gb->ir_queue_length = 0;
+}
+
+void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change)
+{
+ if (gb->ir_queue_length == GB_MAX_IR_QUEUE) {
+ GB_log(gb, "IR Queue is full\n");
+ return;
+ }
+ gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change};
+}
+
+void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback)
+{
+ gb->rumble_callback = callback;
+}
+
+void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback)
+{
+ gb->serial_transfer_bit_start_callback = callback;
+}
+
+void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback)
+{
+ gb->serial_transfer_bit_end_callback = callback;
+}
+
+bool GB_serial_get_data_bit(GB_gameboy_t *gb)
+{
+ if (gb->io_registers[GB_IO_SC] & 1) {
+ /* Internal Clock */
+ GB_log(gb, "Serial read request while using internal clock. \n");
+ return 0xFF;
+ }
+ return gb->io_registers[GB_IO_SB] & 0x80;
+}
+void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data)
+{
+ if (gb->io_registers[GB_IO_SC] & 1) {
+ /* Internal Clock */
+ GB_log(gb, "Serial write request while using internal clock. \n");
+ return;
+ }
+ gb->io_registers[GB_IO_SB] <<= 1;
+ gb->io_registers[GB_IO_SB] |= data;
+ gb->serial_count++;
+ if (gb->serial_count == 8) {
+ gb->io_registers[GB_IO_IF] |= 8;
+ gb->serial_count = 0;
+ }
+}
+
+void GB_disconnect_serial(GB_gameboy_t *gb)
+{
+ gb->serial_transfer_bit_start_callback = NULL;
+ gb->serial_transfer_bit_end_callback = NULL;
+
+ /* Reset any internally-emulated device. Currently, only the printer. */
+ memset(&gb->printer, 0, sizeof(gb->printer));
+}
+
+bool GB_is_inited(GB_gameboy_t *gb)
+{
+ return gb->magic == state_magic();
+}
+
+bool GB_is_cgb(GB_gameboy_t *gb)
+{
+ return (gb->model & GB_MODEL_FAMILY_MASK) == GB_MODEL_CGB_FAMILY;
+}
+
+bool GB_is_sgb(GB_gameboy_t *gb)
+{
+ return (gb->model & ~GB_MODEL_PAL_BIT & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB || (gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB2;
+}
+
+bool GB_is_hle_sgb(GB_gameboy_t *gb)
+{
+ return (gb->model & ~GB_MODEL_PAL_BIT) == GB_MODEL_SGB || gb->model == GB_MODEL_SGB2;
+}
+
+void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip)
+{
+ gb->turbo = on;
+ gb->turbo_dont_skip = no_frame_skip;
+}
+
+void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled)
+{
+ gb->disable_rendering = disabled;
+}
+
+void *GB_get_user_data(GB_gameboy_t *gb)
+{
+ return gb->user_data;
+}
+
+void GB_set_user_data(GB_gameboy_t *gb, void *data)
+{
+ gb->user_data = data;
+}
+
+static void reset_ram(GB_gameboy_t *gb)
+{
+ switch (gb->model) {
+ case GB_MODEL_CGB_E:
+ case GB_MODEL_AGB: /* Unverified */
+ for (unsigned i = 0; i < gb->ram_size; i++) {
+ gb->ram[i] = GB_random();
+ }
+ break;
+
+ case GB_MODEL_DMG_B:
+ case GB_MODEL_SGB_NTSC: /* Unverified*/
+ case GB_MODEL_SGB_PAL: /* Unverified */
+ case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
+ case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
+ for (unsigned i = 0; i < gb->ram_size; i++) {
+ gb->ram[i] = GB_random();
+ if (i & 0x100) {
+ gb->ram[i] &= GB_random();
+ }
+ else {
+ gb->ram[i] |= GB_random();
+ }
+ }
+ break;
+
+ case GB_MODEL_SGB2:
+ case GB_MODEL_SGB2_NO_SFC:
+ for (unsigned i = 0; i < gb->ram_size; i++) {
+ gb->ram[i] = 0x55;
+ gb->ram[i] ^= GB_random() & GB_random() & GB_random();
+ }
+ break;
+
+ case GB_MODEL_CGB_C:
+ for (unsigned i = 0; i < gb->ram_size; i++) {
+ if ((i & 0x808) == 0x800 || (i & 0x808) == 0x008) {
+ gb->ram[i] = 0;
+ }
+ else {
+ gb->ram[i] = GB_random() | GB_random() | GB_random() | GB_random();
+ }
+ }
+ break;
+ }
+
+ /* HRAM */
+ switch (gb->model) {
+ case GB_MODEL_CGB_C:
+ // case GB_MODEL_CGB_D:
+ case GB_MODEL_CGB_E:
+ case GB_MODEL_AGB:
+ for (unsigned i = 0; i < sizeof(gb->hram); i++) {
+ gb->hram[i] = GB_random();
+ }
+ break;
+
+ case GB_MODEL_DMG_B:
+ case GB_MODEL_SGB_NTSC: /* Unverified*/
+ case GB_MODEL_SGB_PAL: /* Unverified */
+ case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
+ case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
+ case GB_MODEL_SGB2:
+ case GB_MODEL_SGB2_NO_SFC:
+ for (unsigned i = 0; i < sizeof(gb->hram); i++) {
+ if (i & 1) {
+ gb->hram[i] = GB_random() | GB_random() | GB_random();
+ }
+ else {
+ gb->hram[i] = GB_random() & GB_random() & GB_random();
+ }
+ }
+ break;
+ }
+
+ /* OAM */
+ switch (gb->model) {
+ case GB_MODEL_CGB_C:
+ case GB_MODEL_CGB_E:
+ case GB_MODEL_AGB:
+ /* Zero'd out by boot ROM anyway*/
+ break;
+
+ case GB_MODEL_DMG_B:
+ case GB_MODEL_SGB_NTSC: /* Unverified */
+ case GB_MODEL_SGB_PAL: /* Unverified */
+ case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
+ case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
+ case GB_MODEL_SGB2:
+ case GB_MODEL_SGB2_NO_SFC:
+ for (unsigned i = 0; i < 8; i++) {
+ if (i & 2) {
+ gb->oam[i] = GB_random() & GB_random() & GB_random();
+ }
+ else {
+ gb->oam[i] = GB_random() | GB_random() | GB_random();
+ }
+ }
+ for (unsigned i = 8; i < sizeof(gb->oam); i++) {
+ gb->oam[i] = gb->oam[i - 8];
+ }
+ break;
+ }
+
+ /* Wave RAM */
+ switch (gb->model) {
+ case GB_MODEL_CGB_C:
+ case GB_MODEL_CGB_E:
+ case GB_MODEL_AGB:
+ /* Initialized by CGB-A and newer, 0s in CGB-0*/
+ break;
+
+ case GB_MODEL_DMG_B:
+ case GB_MODEL_SGB_NTSC: /* Unverified*/
+ case GB_MODEL_SGB_PAL: /* Unverified */
+ case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
+ case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
+ case GB_MODEL_SGB2:
+ case GB_MODEL_SGB2_NO_SFC: {
+ uint8_t temp;
+ for (unsigned i = 0; i < GB_IO_WAV_END - GB_IO_WAV_START; i++) {
+ if (i & 1) {
+ temp = GB_random() & GB_random() & GB_random();
+ }
+ else {
+ temp = GB_random() | GB_random() | GB_random();
+ }
+ gb->apu.wave_channel.wave_form[i * 2] = temp >> 4;
+ gb->apu.wave_channel.wave_form[i * 2 + 1] = temp & 0xF;
+ gb->io_registers[GB_IO_WAV_START + i] = temp;
+
+ }
+ break;
+ }
+ }
+
+ for (unsigned i = 0; i < sizeof(gb->extra_oam); i++) {
+ gb->extra_oam[i] = GB_random();
+ }
+
+ if (GB_is_cgb(gb)) {
+ for (unsigned i = 0; i < 64; i++) {
+ gb->background_palettes_data[i] = GB_random(); /* Doesn't really matter as the boot ROM overrides it anyway*/
+ gb->sprite_palettes_data[i] = GB_random();
+ }
+ for (unsigned i = 0; i < 32; i++) {
+ GB_palette_changed(gb, true, i * 2);
+ GB_palette_changed(gb, false, i * 2);
+ }
+ }
+}
+
+void GB_reset(GB_gameboy_t *gb)
+{
+ uint32_t mbc_ram_size = gb->mbc_ram_size;
+ GB_model_t model = gb->model;
+ memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved));
+ gb->model = model;
+ gb->version = GB_STRUCT_VERSION;
+
+ gb->mbc_rom_bank = 1;
+ gb->last_rtc_second = time(NULL);
+ gb->cgb_ram_bank = 1;
+ gb->io_registers[GB_IO_JOYP] = 0xCF;
+ gb->mbc_ram_size = mbc_ram_size;
+ if (GB_is_cgb(gb)) {
+ gb->ram_size = 0x1000 * 8;
+ gb->vram_size = 0x2000 * 2;
+ memset(gb->vram, 0, gb->vram_size);
+ gb->cgb_mode = true;
+ }
+ else {
+ gb->ram_size = 0x2000;
+ gb->vram_size = 0x2000;
+ memset(gb->vram, 0, gb->vram_size);
+
+ if (gb->rgb_encode_callback) {
+ gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] =
+ gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF);
+ gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] =
+ gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA);
+ gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] =
+ gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55);
+ gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] =
+ gb->rgb_encode_callback(gb, 0, 0, 0);
+ }
+ }
+ reset_ram(gb);
+
+ /* The serial interrupt always occur on the 0xF7th cycle of every 0x100 cycle since boot. */
+ gb->serial_cycles = 0x100-0xF7;
+ gb->io_registers[GB_IO_SC] = 0x7E;
+
+ /* These are not deterministic, but 00 (CGB) and FF (DMG) are the most common initial values by far */
+ gb->io_registers[GB_IO_DMA] = gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = GB_is_cgb(gb)? 0x00 : 0xFF;
+
+ gb->accessed_oam_row = -1;
+
+
+ if (GB_is_hle_sgb(gb)) {
+ if (!gb->sgb) {
+ gb->sgb = malloc(sizeof(*gb->sgb));
+ }
+ memset(gb->sgb, 0, sizeof(*gb->sgb));
+ memset(gb->sgb_intro_jingle_phases, 0, sizeof(gb->sgb_intro_jingle_phases));
+ gb->sgb_intro_sweep_phase = 0;
+ gb->sgb_intro_sweep_previous_sample = 0;
+ gb->sgb->intro_animation = -10;
+
+ gb->sgb->player_count = 1;
+ GB_sgb_load_default_data(gb);
+
+ }
+ else {
+ if (gb->sgb) {
+ free(gb->sgb);
+ gb->sgb = NULL;
+ }
+ }
+
+ /* Todo: Ugly, fixme, see comment in the timer state machine */
+ gb->div_state = 3;
+
+ GB_apu_update_cycles_per_sample(gb);
+
+ if (gb->nontrivial_jump_state) {
+ free(gb->nontrivial_jump_state);
+ gb->nontrivial_jump_state = NULL;
+ }
+
+ gb->magic = state_magic();
+}
+
+void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model)
+{
+ gb->model = model;
+ if (GB_is_cgb(gb)) {
+ gb->ram = realloc(gb->ram, gb->ram_size = 0x1000 * 8);
+ gb->vram = realloc(gb->vram, gb->vram_size = 0x2000 * 2);
+ }
+ else {
+ gb->ram = realloc(gb->ram, gb->ram_size = 0x2000);
+ gb->vram = realloc(gb->vram, gb->vram_size = 0x2000);
+ }
+ GB_rewind_free(gb);
+ GB_reset(gb);
+}
+
+void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank)
+{
+ /* Set size and bank to dummy pointers if not set */
+ size_t dummy_size;
+ uint16_t dummy_bank;
+ if (!size) {
+ size = &dummy_size;
+ }
+
+ if (!bank) {
+ bank = &dummy_bank;
+ }
+
+
+ switch (access) {
+ case GB_DIRECT_ACCESS_ROM:
+ *size = gb->rom_size;
+ *bank = gb->mbc_rom_bank;
+ return gb->rom;
+ case GB_DIRECT_ACCESS_RAM:
+ *size = gb->ram_size;
+ *bank = gb->cgb_ram_bank;
+ return gb->ram;
+ case GB_DIRECT_ACCESS_CART_RAM:
+ *size = gb->mbc_ram_size;
+ *bank = gb->mbc_ram_bank;
+ return gb->mbc_ram;
+ case GB_DIRECT_ACCESS_VRAM:
+ *size = gb->vram_size;
+ *bank = gb->cgb_vram_bank;
+ return gb->vram;
+ case GB_DIRECT_ACCESS_HRAM:
+ *size = sizeof(gb->hram);
+ *bank = 0;
+ return &gb->hram;
+ case GB_DIRECT_ACCESS_IO:
+ *size = sizeof(gb->io_registers);
+ *bank = 0;
+ return &gb->io_registers;
+ case GB_DIRECT_ACCESS_BOOTROM:
+ *size = GB_is_cgb(gb)? sizeof(gb->boot_rom) : 0x100;
+ *bank = 0;
+ return &gb->boot_rom;
+ case GB_DIRECT_ACCESS_OAM:
+ *size = sizeof(gb->oam);
+ *bank = 0;
+ return &gb->oam;
+ case GB_DIRECT_ACCESS_BGP:
+ *size = sizeof(gb->background_palettes_data);
+ *bank = 0;
+ return &gb->background_palettes_data;
+ case GB_DIRECT_ACCESS_OBP:
+ *size = sizeof(gb->sprite_palettes_data);
+ *bank = 0;
+ return &gb->sprite_palettes_data;
+ case GB_DIRECT_ACCESS_IE:
+ *size = sizeof(gb->interrupt_enable);
+ *bank = 0;
+ return &gb->interrupt_enable;
+ default:
+ *size = 0;
+ *bank = 0;
+ return NULL;
+ }
+}
+
+void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier)
+{
+ gb->clock_multiplier = multiplier;
+ GB_apu_update_cycles_per_sample(gb);
+}
+
+uint32_t GB_get_clock_rate(GB_gameboy_t *gb)
+{
+ if (gb->model & GB_MODEL_PAL_BIT) {
+ return SGB_PAL_FREQUENCY * gb->clock_multiplier;
+ }
+ if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) {
+ return SGB_NTSC_FREQUENCY * gb->clock_multiplier;
+ }
+ return CPU_FREQUENCY * gb->clock_multiplier;
+}
+
+unsigned GB_get_screen_width(GB_gameboy_t *gb)
+{
+ return GB_is_hle_sgb(gb)? 256 : 160;
+}
+
+unsigned GB_get_screen_height(GB_gameboy_t *gb)
+{
+ return GB_is_hle_sgb(gb)? 224 : 144;
+}
+
+unsigned GB_get_player_count(GB_gameboy_t *gb)
+{
+ return GB_is_hle_sgb(gb)? gb->sgb->player_count : 1;
+}
+
+void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback)
+{
+ gb->update_input_hint_callback = callback;
+}
+
+double GB_get_usual_frame_rate(GB_gameboy_t *gb)
+{
+ return GB_get_clock_rate(gb) / (double)LCDC_PERIOD;
+}
+
+void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback)
+{
+ gb->joyp_write_callback = callback;
+}
+
+void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback)
+{
+ gb->icd_pixel_callback = callback;
+}
+
+void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback)
+{
+ gb->icd_hreset_callback = callback;
+}
+
+
+void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback)
+{
+ gb->icd_vreset_callback = callback;
+}
diff --git a/gb/Core/gb.h b/gb/Core/gb.h
new file mode 100644
index 0000000..a561111
--- /dev/null
+++ b/gb/Core/gb.h
@@ -0,0 +1,725 @@
+#ifndef GB_h
+#define GB_h
+#define typeof __typeof__
+#include
+#include
+#include
+
+#include "gb_struct_def.h"
+#include "save_state.h"
+
+#include "apu.h"
+#include "camera.h"
+#include "debugger.h"
+#include "display.h"
+#include "joypad.h"
+#include "mbc.h"
+#include "memory.h"
+#include "printer.h"
+#include "timing.h"
+#include "rewind.h"
+#include "sm83_cpu.h"
+#include "symbol_hash.h"
+#include "sgb.h"
+
+#define GB_STRUCT_VERSION 13
+
+#define GB_MODEL_FAMILY_MASK 0xF00
+#define GB_MODEL_DMG_FAMILY 0x000
+#define GB_MODEL_MGB_FAMILY 0x100
+#define GB_MODEL_CGB_FAMILY 0x200
+#define GB_MODEL_PAL_BIT 0x1000
+#define GB_MODEL_NO_SFC_BIT 0x2000
+
+#ifdef GB_INTERNAL
+#if __clang__
+#define UNROLL _Pragma("unroll")
+#elif __GNUC__
+#define UNROLL _Pragma("GCC unroll 8")
+#else
+#define UNROLL
+#endif
+
+#endif
+
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define GB_BIG_ENDIAN
+#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define GB_LITTLE_ENDIAN
+#else
+#error Unable to detect endianess
+#endif
+
+typedef union {
+ struct {
+ uint8_t seconds;
+ uint8_t minutes;
+ uint8_t hours;
+ uint8_t days;
+ uint8_t high;
+ };
+ uint8_t data[5];
+} GB_rtc_time_t;
+
+
+typedef enum {
+ // GB_MODEL_DMG_0 = 0x000,
+ // GB_MODEL_DMG_A = 0x001,
+ GB_MODEL_DMG_B = 0x002,
+ // GB_MODEL_DMG_C = 0x003,
+ GB_MODEL_SGB = 0x004,
+ GB_MODEL_SGB_NTSC = GB_MODEL_SGB,
+ GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT,
+ GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT,
+ GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC,
+ GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT,
+ // GB_MODEL_MGB = 0x100,
+ GB_MODEL_SGB2 = 0x101,
+ GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT,
+ // GB_MODEL_CGB_0 = 0x200,
+ // GB_MODEL_CGB_A = 0x201,
+ // GB_MODEL_CGB_B = 0x202,
+ GB_MODEL_CGB_C = 0x203,
+ // GB_MODEL_CGB_D = 0x204,
+ GB_MODEL_CGB_E = 0x205,
+ GB_MODEL_AGB = 0x206,
+} GB_model_t;
+
+enum {
+ GB_REGISTER_AF,
+ GB_REGISTER_BC,
+ GB_REGISTER_DE,
+ GB_REGISTER_HL,
+ GB_REGISTER_SP,
+ GB_REGISTERS_16_BIT /* Count */
+};
+
+/* Todo: Actually use these! */
+enum {
+ GB_CARRY_FLAG = 16,
+ GB_HALF_CARRY_FLAG = 32,
+ GB_SUBSTRACT_FLAG = 64,
+ GB_ZERO_FLAG = 128,
+};
+
+#define GB_MAX_IR_QUEUE 256
+
+enum {
+ /* Joypad and Serial */
+ GB_IO_JOYP = 0x00, // Joypad (R/W)
+ GB_IO_SB = 0x01, // Serial transfer data (R/W)
+ GB_IO_SC = 0x02, // Serial Transfer Control (R/W)
+
+ /* Missing */
+
+ /* Timers */
+ GB_IO_DIV = 0x04, // Divider Register (R/W)
+ GB_IO_TIMA = 0x05, // Timer counter (R/W)
+ GB_IO_TMA = 0x06, // Timer Modulo (R/W)
+ GB_IO_TAC = 0x07, // Timer Control (R/W)
+
+ /* Missing */
+
+ GB_IO_IF = 0x0f, // Interrupt Flag (R/W)
+
+ /* Sound */
+ GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W)
+ GB_IO_NR11 = 0x11, // Channel 1 Sound length/Wave pattern duty (R/W)
+ GB_IO_NR12 = 0x12, // Channel 1 Volume Envelope (R/W)
+ GB_IO_NR13 = 0x13, // Channel 1 Frequency lo (Write Only)
+ GB_IO_NR14 = 0x14, // Channel 1 Frequency hi (R/W)
+ /* NR20 does not exist */
+ GB_IO_NR21 = 0x16, // Channel 2 Sound Length/Wave Pattern Duty (R/W)
+ GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W)
+ GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W)
+ GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W)
+ GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W)
+ GB_IO_NR31 = 0x1b, // Channel 3 Sound Length
+ GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W)
+ GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W)
+ GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W)
+ /* NR40 does not exist */
+ GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W)
+ GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W)
+ GB_IO_NR43 = 0x22, // Channel 4 Polynomial Counter (R/W)
+ GB_IO_NR44 = 0x23, // Channel 4 Counter/consecutive, Inital (R/W)
+ GB_IO_NR50 = 0x24, // Channel control / ON-OFF / Volume (R/W)
+ GB_IO_NR51 = 0x25, // Selection of Sound output terminal (R/W)
+ GB_IO_NR52 = 0x26, // Sound on/off
+
+ /* Missing */
+
+ GB_IO_WAV_START = 0x30, // Wave pattern start
+ GB_IO_WAV_END = 0x3f, // Wave pattern end
+
+ /* Graphics */
+ GB_IO_LCDC = 0x40, // LCD Control (R/W)
+ GB_IO_STAT = 0x41, // LCDC Status (R/W)
+ GB_IO_SCY = 0x42, // Scroll Y (R/W)
+ GB_IO_SCX = 0x43, // Scroll X (R/W)
+ GB_IO_LY = 0x44, // LCDC Y-Coordinate (R)
+ GB_IO_LYC = 0x45, // LY Compare (R/W)
+ GB_IO_DMA = 0x46, // DMA Transfer and Start Address (W)
+ GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only
+ GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only
+ GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only
+ GB_IO_WY = 0x4a, // Window Y Position (R/W)
+ GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W)
+ // Has some undocumented compatibility flags written at boot.
+ // Unfortunately it is not readable or writable after boot has finished, so research of this
+ // register is quite limited. The value written to this register, however, can be controlled
+ // in some cases.
+ GB_IO_DMG_EMULATION = 0x4c,
+
+ /* General CGB features */
+ GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch
+
+ /* Missing */
+
+ GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank
+ GB_IO_BIOS = 0x50, // Write to disable the BIOS mapping
+
+ /* CGB DMA */
+ GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High
+ GB_IO_HDMA2 = 0x52, // CGB Mode Only - New DMA Source, Low
+ GB_IO_HDMA3 = 0x53, // CGB Mode Only - New DMA Destination, High
+ GB_IO_HDMA4 = 0x54, // CGB Mode Only - New DMA Destination, Low
+ GB_IO_HDMA5 = 0x55, // CGB Mode Only - New DMA Length/Mode/Start
+
+ /* IR */
+ GB_IO_RP = 0x56, // CGB Mode Only - Infrared Communications Port
+
+ /* Missing */
+
+ /* CGB Paletts */
+ GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index
+ GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data
+ GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index
+ GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data
+
+ // 1 is written for DMG ROMs on a CGB. Does not appear to have an effect.
+ GB_IO_DMG_EMULATION_INDICATION = 0x6c, // (FEh) Bit 0 (Read/Write)
+
+ /* Missing */
+
+ GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank
+ GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write)
+ GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write)
+ GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only
+ GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write)
+ GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes
+ GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes
+ GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only
+};
+
+typedef enum {
+ GB_LOG_BOLD = 1,
+ GB_LOG_DASHED_UNDERLINE = 2,
+ GB_LOG_UNDERLINE = 4,
+ GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE
+} GB_log_attributes;
+
+#ifdef GB_INTERNAL
+#define LCDC_PERIOD 70224
+#define CPU_FREQUENCY 0x400000
+#define SGB_NTSC_FREQUENCY (21477272 / 5)
+#define SGB_PAL_FREQUENCY (21281370 / 5)
+#define DIV_CYCLES (0x100)
+#define INTERNAL_DIV_CYCLES (0x40000)
+
+#if !defined(MIN)
+#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
+#endif
+
+#if !defined(MAX)
+#define MAX(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __b : __a; })
+#endif
+#endif
+
+typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb);
+typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes);
+typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
+typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
+typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update);
+typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on);
+typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send);
+typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb);
+typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb);
+typedef void (*GB_joyp_write_callback_t)(GB_gameboy_t *gb, uint8_t value);
+typedef void (*GB_icd_pixel_callback_t)(GB_gameboy_t *gb, uint8_t row);
+typedef void (*GB_icd_hreset_callback_t)(GB_gameboy_t *gb);
+typedef void (*GB_icd_vreset_callback_t)(GB_gameboy_t *gb);
+
+typedef struct {
+ bool state;
+ long delay;
+} GB_ir_queue_item_t;
+
+struct GB_breakpoint_s;
+struct GB_watchpoint_s;
+
+typedef struct {
+ uint8_t pixel; // Color, 0-3
+ uint8_t palette; // Palette, 0 - 7 (CGB); 0-1 in DMG (or just 0 for BG)
+ uint8_t priority; // Sprite priority – 0 in DMG, OAM index in CGB
+ bool bg_priority; // For sprite FIFO – the BG priority bit. For the BG FIFO – the CGB attributes priority bit
+} GB_fifo_item_t;
+
+#define GB_FIFO_LENGTH 16
+typedef struct {
+ GB_fifo_item_t fifo[GB_FIFO_LENGTH];
+ uint8_t read_end;
+ uint8_t write_end;
+} GB_fifo_t;
+
+/* When state saving, each section is dumped independently of other sections.
+ This allows adding data to the end of the section without worrying about future compatibility.
+ Some other changes might be "safe" as well.
+ This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64
+ bit platforms. */
+
+#ifdef GB_INTERNAL
+struct GB_gameboy_s {
+#else
+struct GB_gameboy_internal_s {
+#endif
+ GB_SECTION(header,
+ /* The magic makes sure a state file is:
+ - Indeed a SameBoy state file.
+ - Has the same endianess has the current platform. */
+ volatile uint32_t magic;
+ /* The version field makes sure we don't load save state files with a completely different structure.
+ This happens when struct fields are removed/resized in an backward incompatible manner. */
+ uint32_t version;
+ );
+
+ GB_SECTION(core_state,
+ /* Registers */
+ uint16_t pc;
+ union {
+ uint16_t registers[GB_REGISTERS_16_BIT];
+ struct {
+ uint16_t af,
+ bc,
+ de,
+ hl,
+ sp;
+ };
+ struct {
+#ifdef GB_BIG_ENDIAN
+ uint8_t a, f,
+ b, c,
+ d, e,
+ h, l;
+#else
+ uint8_t f, a,
+ c, b,
+ e, d,
+ l, h;
+#endif
+ };
+
+ };
+ uint8_t ime;
+ uint8_t interrupt_enable;
+ uint8_t cgb_ram_bank;
+
+ /* CPU and General Hardware Flags*/
+ GB_model_t model;
+ bool cgb_mode;
+ bool cgb_double_speed;
+ bool halted;
+ bool stopped;
+ bool boot_rom_finished;
+ bool ime_toggle; /* ei has delayed a effect.*/
+ bool halt_bug;
+ bool just_halted;
+
+ /* Misc state */
+ bool infrared_input;
+ GB_printer_t printer;
+ uint8_t extra_oam[0xff00 - 0xfea0];
+ uint32_t ram_size; // Different between CGB and DMG
+ );
+
+ /* DMA and HDMA */
+ GB_SECTION(dma,
+ bool hdma_on;
+ bool hdma_on_hblank;
+ uint8_t hdma_steps_left;
+ int16_t hdma_cycles; // in 8MHz units
+ uint16_t hdma_current_src, hdma_current_dest;
+
+ uint8_t dma_steps_left;
+ uint8_t dma_current_dest;
+ uint16_t dma_current_src;
+ int16_t dma_cycles;
+ bool is_dma_restarting;
+ uint8_t last_opcode_read; /* Required to emulte HDMA reads from Exxx */
+ bool hdma_starting;
+ );
+
+ /* MBC */
+ GB_SECTION(mbc,
+ uint16_t mbc_rom_bank;
+ uint8_t mbc_ram_bank;
+ uint32_t mbc_ram_size;
+ bool mbc_ram_enable;
+ union {
+ struct {
+ uint8_t bank_low:5;
+ uint8_t bank_high:2;
+ uint8_t mode:1;
+ } mbc1;
+
+ struct {
+ uint8_t rom_bank:4;
+ } mbc2;
+
+ struct {
+ uint8_t rom_bank:7;
+ uint8_t padding:1;
+ uint8_t ram_bank:4;
+ } mbc3;
+
+ struct {
+ uint8_t rom_bank_low;
+ uint8_t rom_bank_high:1;
+ uint8_t ram_bank:4;
+ } mbc5;
+
+ struct {
+ uint8_t bank_low:6;
+ uint8_t bank_high:3;
+ uint8_t mode:1;
+ } huc1;
+
+ struct {
+ uint8_t rom_bank;
+ uint8_t ram_bank;
+ } huc3;
+ };
+ uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */
+ bool camera_registers_mapped;
+ uint8_t camera_registers[0x36];
+ bool rumble_state;
+ );
+
+
+ /* HRAM and HW Registers */
+ GB_SECTION(hram,
+ uint8_t hram[0xFFFF - 0xFF80];
+ uint8_t io_registers[0x80];
+ );
+
+ /* Timing */
+ GB_SECTION(timing,
+ GB_UNIT(display);
+ GB_UNIT(div);
+ uint16_t div_counter;
+ uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */
+ uint16_t serial_cycles;
+ uint16_t serial_length;
+ uint8_t double_speed_alignment;
+ uint8_t serial_count;
+ );
+
+ /* APU */
+ GB_SECTION(apu,
+ GB_apu_t apu;
+ );
+
+ /* RTC */
+ GB_SECTION(rtc,
+ GB_rtc_time_t rtc_real, rtc_latched;
+ uint64_t last_rtc_second;
+ bool rtc_latch;
+ );
+
+ /* Video Display */
+ GB_SECTION(video,
+ uint32_t vram_size; // Different between CGB and DMG
+ uint8_t cgb_vram_bank;
+ uint8_t oam[0xA0];
+ uint8_t background_palettes_data[0x40];
+ uint8_t sprite_palettes_data[0x40];
+ uint8_t position_in_line;
+ bool stat_interrupt_line;
+ uint8_t effective_scx;
+ uint8_t wy_diff;
+ /* The LCDC will skip the first frame it renders after turning it on.
+ On the CGB, a frame is not skipped if the previous frame was skipped as well.
+ See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */
+
+ /* TODO: Drop this and properly emulate the dropped vreset signal*/
+ enum {
+ GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state,
+ // on a CGB, the previous frame is repeated (which might be
+ // blank if the LCD was off for more than a few cycles)
+ GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG
+ GB_FRAMESKIP_SECOND_FRAME_RENDERED,
+ } frame_skip_state;
+ bool oam_read_blocked;
+ bool vram_read_blocked;
+ bool oam_write_blocked;
+ bool vram_write_blocked;
+ bool window_disabled_while_active;
+ uint8_t current_line;
+ uint16_t ly_for_comparison;
+ GB_fifo_t bg_fifo, oam_fifo;
+ uint8_t fetcher_x;
+ uint8_t fetcher_y;
+ uint16_t cycles_for_line;
+ uint8_t current_tile;
+ uint8_t current_tile_attributes;
+ uint8_t current_tile_data[2];
+ uint8_t fetcher_state;
+ bool bg_fifo_paused;
+ bool oam_fifo_paused;
+ bool in_window;
+ uint8_t visible_objs[10];
+ uint8_t obj_comparators[10];
+ uint8_t n_visible_objs;
+ uint8_t oam_search_index;
+ uint8_t accessed_oam_row;
+ uint8_t extra_penalty_for_sprite_at_0;
+ uint8_t mode_for_interrupt;
+ bool lyc_interrupt_line;
+ bool cgb_palettes_blocked;
+ uint8_t current_lcd_line; // The LCD can go out of sync since the vsync signal is skipped in some cases.
+ uint32_t cycles_in_stop_mode;
+ );
+
+ /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
+ /* This data is reserved on reset and must come last in the struct */
+ GB_SECTION(unsaved,
+ /* ROM */
+ uint8_t *rom;
+ uint32_t rom_size;
+ const GB_cartridge_t *cartridge_type;
+ enum {
+ GB_STANDARD_MBC1_WIRING,
+ GB_MBC1M_WIRING,
+ } mbc1_wiring;
+
+ unsigned pending_cycles;
+
+ /* Various RAMs */
+ uint8_t *ram;
+ uint8_t *vram;
+ uint8_t *mbc_ram;
+
+ /* I/O */
+ uint32_t *screen;
+ uint32_t background_palettes_rgb[0x20];
+ uint32_t sprite_palettes_rgb[0x20];
+ GB_color_correction_mode_t color_correction_mode;
+ bool keys[4][GB_KEY_MAX];
+
+ /* Timing */
+ uint64_t last_sync;
+ uint64_t cycles_since_last_sync; // In 8MHz units
+
+ /* Audio */
+ GB_apu_output_t apu_output;
+
+ /* Callbacks */
+ void *user_data;
+ GB_log_callback_t log_callback;
+ GB_input_callback_t input_callback;
+ GB_input_callback_t async_input_callback;
+ GB_rgb_encode_callback_t rgb_encode_callback;
+ GB_vblank_callback_t vblank_callback;
+ GB_infrared_callback_t infrared_callback;
+ GB_camera_get_pixel_callback_t camera_get_pixel_callback;
+ GB_camera_update_request_callback_t camera_update_request_callback;
+ GB_rumble_callback_t rumble_callback;
+ GB_serial_transfer_bit_start_callback_t serial_transfer_bit_start_callback;
+ GB_serial_transfer_bit_end_callback_t serial_transfer_bit_end_callback;
+ GB_update_input_hint_callback_t update_input_hint_callback;
+ GB_joyp_write_callback_t joyp_write_callback;
+ GB_icd_pixel_callback_t icd_pixel_callback;
+ GB_icd_vreset_callback_t icd_hreset_callback;
+ GB_icd_vreset_callback_t icd_vreset_callback;
+ GB_read_memory_callback_t read_memory_callback;
+
+ /* IR */
+ long cycles_since_ir_change; // In 8MHz units
+ long cycles_since_input_ir_change; // In 8MHz units
+ GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE];
+ size_t ir_queue_length;
+
+ /*** Debugger ***/
+ volatile bool debug_stopped, debug_disable;
+ bool debug_fin_command, debug_next_command;
+
+ /* Breakpoints */
+ uint16_t n_breakpoints;
+ struct GB_breakpoint_s *breakpoints;
+ bool has_jump_to_breakpoints;
+ void *nontrivial_jump_state;
+ bool non_trivial_jump_breakpoint_occured;
+
+ /* SLD (Todo: merge with backtrace) */
+ bool stack_leak_detection;
+ int debug_call_depth;
+ uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
+ uint16_t addr_for_call_depth[0x200];
+
+ /* Backtrace */
+ unsigned backtrace_size;
+ uint16_t backtrace_sps[0x200];
+ struct {
+ uint16_t bank;
+ uint16_t addr;
+ } backtrace_returns[0x200];
+
+ /* Watchpoints */
+ uint16_t n_watchpoints;
+ struct GB_watchpoint_s *watchpoints;
+
+ /* Symbol tables */
+ GB_symbol_map_t *bank_symbols[0x200];
+ GB_reversed_symbol_map_t reversed_symbol_map;
+
+ /* Ticks command */
+ unsigned long debugger_ticks;
+
+ /* Rewind */
+#define GB_REWIND_FRAMES_PER_KEY 255
+ size_t rewind_buffer_length;
+ struct {
+ uint8_t *key_state;
+ uint8_t *compressed_states[GB_REWIND_FRAMES_PER_KEY];
+ unsigned pos;
+ } *rewind_sequences; // lasts about 4 seconds
+ size_t rewind_pos;
+
+ /* SGB - saved and allocated optionally */
+ GB_sgb_t *sgb;
+
+ double sgb_intro_jingle_phases[7];
+ double sgb_intro_sweep_phase;
+ double sgb_intro_sweep_previous_sample;
+
+ /* Misc */
+ bool turbo;
+ bool turbo_dont_skip;
+ bool disable_rendering;
+ uint8_t boot_rom[0x900];
+ bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
+ uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
+ double clock_multiplier;
+ );
+};
+
+#ifndef GB_INTERNAL
+struct GB_gameboy_s {
+ char __internal[sizeof(struct GB_gameboy_internal_s)];
+};
+#endif
+
+
+#ifndef __printflike
+/* Missing from Linux headers. */
+#define __printflike(fmtarg, firstvararg) \
+__attribute__((__format__ (__printf__, fmtarg, firstvararg)))
+#endif
+
+void GB_init(GB_gameboy_t *gb, GB_model_t model);
+bool GB_is_inited(GB_gameboy_t *gb);
+bool GB_is_cgb(GB_gameboy_t *gb);
+bool GB_is_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2
+bool GB_is_hle_sgb(GB_gameboy_t *gb); // Returns true if the model is SGB or SGB2 and the SFC/SNES side is HLE'd
+GB_model_t GB_get_model(GB_gameboy_t *gb);
+void GB_free(GB_gameboy_t *gb);
+void GB_reset(GB_gameboy_t *gb);
+void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model);
+
+/* Returns the time passed, in 8MHz ticks. */
+uint8_t GB_run(GB_gameboy_t *gb);
+/* Returns the time passed since the last frame, in nanoseconds */
+uint64_t GB_run_frame(GB_gameboy_t *gb);
+
+typedef enum {
+ GB_DIRECT_ACCESS_ROM,
+ GB_DIRECT_ACCESS_RAM,
+ GB_DIRECT_ACCESS_CART_RAM,
+ GB_DIRECT_ACCESS_VRAM,
+ GB_DIRECT_ACCESS_HRAM,
+ GB_DIRECT_ACCESS_IO, /* Warning: Some registers can only be read/written correctly via GB_memory_read/write. */
+ GB_DIRECT_ACCESS_BOOTROM,
+ GB_DIRECT_ACCESS_OAM,
+ GB_DIRECT_ACCESS_BGP,
+ GB_DIRECT_ACCESS_OBP,
+ GB_DIRECT_ACCESS_IE,
+} GB_direct_access_t;
+
+/* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank
+ is returned at *bank, even if only a portion of the memory is banked. */
+void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank);
+
+void *GB_get_user_data(GB_gameboy_t *gb);
+void GB_set_user_data(GB_gameboy_t *gb, void *data);
+
+
+
+int GB_load_boot_rom(GB_gameboy_t *gb, const char *path);
+void GB_load_boot_rom_from_buffer(GB_gameboy_t *gb, const unsigned char *buffer, size_t size);
+int GB_load_rom(GB_gameboy_t *gb, const char *path);
+void GB_load_rom_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size);
+
+int GB_save_battery_size(GB_gameboy_t *gb);
+int GB_save_battery_to_buffer(GB_gameboy_t *gb, uint8_t *buffer, size_t size);
+int GB_save_battery(GB_gameboy_t *gb, const char *path);
+
+void GB_load_battery_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t size);
+void GB_load_battery(GB_gameboy_t *gb, const char *path);
+
+void GB_set_turbo_mode(GB_gameboy_t *gb, bool on, bool no_frame_skip);
+void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled);
+
+void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3);
+void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4);
+
+void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output);
+
+void GB_set_infrared_input(GB_gameboy_t *gb, bool state);
+void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); /* In 8MHz units*/
+
+void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback);
+void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback);
+void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback);
+void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback);
+void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback);
+void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback);
+void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback);
+void GB_set_update_input_hint_callback(GB_gameboy_t *gb, GB_update_input_hint_callback_t callback);
+
+/* These APIs are used when using internal clock */
+void GB_set_serial_transfer_bit_start_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_start_callback_t callback);
+void GB_set_serial_transfer_bit_end_callback(GB_gameboy_t *gb, GB_serial_transfer_bit_end_callback_t callback);
+
+/* These APIs are used when using external clock */
+bool GB_serial_get_data_bit(GB_gameboy_t *gb);
+void GB_serial_set_data_bit(GB_gameboy_t *gb, bool data);
+
+void GB_disconnect_serial(GB_gameboy_t *gb);
+
+/* For integration with SFC/SNES emulators */
+void GB_set_joyp_write_callback(GB_gameboy_t *gb, GB_joyp_write_callback_t callback);
+void GB_set_icd_pixel_callback(GB_gameboy_t *gb, GB_icd_pixel_callback_t callback);
+void GB_set_icd_hreset_callback(GB_gameboy_t *gb, GB_icd_hreset_callback_t callback);
+void GB_set_icd_vreset_callback(GB_gameboy_t *gb, GB_icd_vreset_callback_t callback);
+
+#ifdef GB_INTERNAL
+uint32_t GB_get_clock_rate(GB_gameboy_t *gb);
+#endif
+void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier);
+
+unsigned GB_get_screen_width(GB_gameboy_t *gb);
+unsigned GB_get_screen_height(GB_gameboy_t *gb);
+double GB_get_usual_frame_rate(GB_gameboy_t *gb);
+unsigned GB_get_player_count(GB_gameboy_t *gb);
+
+#endif /* GB_h */
diff --git a/gb/Core/gb_struct_def.h b/gb/Core/gb_struct_def.h
new file mode 100644
index 0000000..0e0ebd1
--- /dev/null
+++ b/gb/Core/gb_struct_def.h
@@ -0,0 +1,5 @@
+#ifndef gb_struct_def_h
+#define gb_struct_def_h
+struct GB_gameboy_s;
+typedef struct GB_gameboy_s GB_gameboy_t;
+#endif
diff --git a/gb/Core/joypad.c b/gb/Core/joypad.c
new file mode 100644
index 0000000..b8d4fdb
--- /dev/null
+++ b/gb/Core/joypad.c
@@ -0,0 +1,92 @@
+#include "gb.h"
+#include
+
+void GB_update_joyp(GB_gameboy_t *gb)
+{
+ if (gb->model & GB_MODEL_NO_SFC_BIT) return;
+
+ uint8_t key_selection = 0;
+ uint8_t previous_state = 0;
+
+ /* Todo: add delay to key selection */
+ previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
+ key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
+ gb->io_registers[GB_IO_JOYP] &= 0xF0;
+ uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0;
+ switch (key_selection) {
+ case 3:
+ if (gb->sgb && gb->sgb->player_count > 1) {
+ gb->io_registers[GB_IO_JOYP] |= 0xF - current_player;
+ }
+ else {
+ /* Nothing is wired, all up */
+ gb->io_registers[GB_IO_JOYP] |= 0x0F;
+ }
+ break;
+
+ case 2:
+ /* Direction keys */
+ for (uint8_t i = 0; i < 4; i++) {
+ gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i]) << i;
+ }
+ /* Forbid pressing two opposing keys, this breaks a lot of games; even if it's somewhat possible. */
+ if (!(gb->io_registers[GB_IO_JOYP] & 1)) {
+ gb->io_registers[GB_IO_JOYP] |= 2;
+ }
+ if (!(gb->io_registers[GB_IO_JOYP] & 4)) {
+ gb->io_registers[GB_IO_JOYP] |= 8;
+ }
+ break;
+
+ case 1:
+ /* Other keys */
+ for (uint8_t i = 0; i < 4; i++) {
+ gb->io_registers[GB_IO_JOYP] |= (!gb->keys[current_player][i + 4]) << i;
+ }
+ break;
+
+ case 0:
+ for (uint8_t i = 0; i < 4; i++) {
+ gb->io_registers[GB_IO_JOYP] |= (!(gb->keys[current_player][i] || gb->keys[current_player][i + 4])) << i;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Todo: This assumes the keys *always* bounce, which is incorrect when emulating an SGB */
+ if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) {
+ /* The joypad interrupt DOES occur on CGB (Tested on CGB-E), unlike what some documents say. */
+ gb->io_registers[GB_IO_IF] |= 0x10;
+ }
+
+ gb->io_registers[GB_IO_JOYP] |= 0xC0;
+}
+
+void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value)
+{
+ uint8_t previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
+ gb->io_registers[GB_IO_JOYP] &= 0xF0;
+ gb->io_registers[GB_IO_JOYP] |= value & 0xF;
+
+ if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) {
+ gb->io_registers[GB_IO_IF] |= 0x10;
+ }
+ gb->io_registers[GB_IO_JOYP] |= 0xC0;
+}
+
+void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed)
+{
+ assert(index >= 0 && index < GB_KEY_MAX);
+ gb->keys[0][index] = pressed;
+ GB_update_joyp(gb);
+}
+
+void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed)
+{
+ assert(index >= 0 && index < GB_KEY_MAX);
+ assert(player < 4);
+ gb->keys[player][index] = pressed;
+ GB_update_joyp(gb);
+}
diff --git a/gb/Core/joypad.h b/gb/Core/joypad.h
new file mode 100644
index 0000000..21fad53
--- /dev/null
+++ b/gb/Core/joypad.h
@@ -0,0 +1,25 @@
+#ifndef joypad_h
+#define joypad_h
+#include "gb_struct_def.h"
+#include
+
+typedef enum {
+ GB_KEY_RIGHT,
+ GB_KEY_LEFT,
+ GB_KEY_UP,
+ GB_KEY_DOWN,
+ GB_KEY_A,
+ GB_KEY_B,
+ GB_KEY_SELECT,
+ GB_KEY_START,
+ GB_KEY_MAX
+} GB_key_t;
+
+void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed);
+void GB_set_key_state_for_player(GB_gameboy_t *gb, GB_key_t index, unsigned player, bool pressed);
+void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value);
+
+#ifdef GB_INTERNAL
+void GB_update_joyp(GB_gameboy_t *gb);
+#endif
+#endif /* joypad_h */
diff --git a/gb/Core/mbc.c b/gb/Core/mbc.c
new file mode 100644
index 0000000..d3791a1
--- /dev/null
+++ b/gb/Core/mbc.c
@@ -0,0 +1,154 @@
+#include
+#include
+#include
+#include "gb.h"
+
+const GB_cartridge_t GB_cart_defs[256] = {
+ // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type
+ /* MBC SUBTYPE RAM BAT. RTC RUMB. */
+ { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY
+ { GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1
+ { GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM
+ { GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY
+ [5] =
+ { GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2
+ { GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY
+ [8] =
+ { GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM
+ { GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY
+ [0xB] =
+ /* Todo: Not supported yet */
+ { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01
+ { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM
+ { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY
+ [0xF] =
+ { GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY
+ { GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY
+ { GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3
+ { GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM
+ { GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY
+ [0x19] =
+ { GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5
+ { GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM
+ { GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY
+ { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE
+ { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM
+ { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY
+ [0xFC] =
+ { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA
+ { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported)
+ { GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only)
+ { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings)
+};
+
+void GB_update_mbc_mappings(GB_gameboy_t *gb)
+{
+ switch (gb->cartridge_type->mbc_type) {
+ case GB_NO_MBC: return;
+ case GB_MBC1:
+ switch (gb->mbc1_wiring) {
+ case GB_STANDARD_MBC1_WIRING:
+ gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5);
+ if (gb->mbc1.mode == 0) {
+ gb->mbc_ram_bank = 0;
+ gb->mbc_rom0_bank = 0;
+ }
+ else {
+ gb->mbc_ram_bank = gb->mbc1.bank_high;
+ gb->mbc_rom0_bank = gb->mbc1.bank_high << 5;
+ }
+ if ((gb->mbc_rom_bank & 0x1F) == 0) {
+ gb->mbc_rom_bank++;
+ }
+ break;
+ case GB_MBC1M_WIRING:
+ gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4);
+ if (gb->mbc1.mode == 0) {
+ gb->mbc_ram_bank = 0;
+ gb->mbc_rom0_bank = 0;
+ }
+ else {
+ gb->mbc_rom0_bank = gb->mbc1.bank_high << 4;
+ gb->mbc_ram_bank = 0;
+ }
+ if ((gb->mbc1.bank_low & 0x1F) == 0) {
+ gb->mbc_rom_bank++;
+ }
+ break;
+ }
+ break;
+ case GB_MBC2:
+ gb->mbc_rom_bank = gb->mbc2.rom_bank;
+ if ((gb->mbc_rom_bank & 0xF) == 0) {
+ gb->mbc_rom_bank = 1;
+ }
+ break;
+ case GB_MBC3:
+ gb->mbc_rom_bank = gb->mbc3.rom_bank;
+ gb->mbc_ram_bank = gb->mbc3.ram_bank;
+ if (gb->mbc_rom_bank == 0) {
+ gb->mbc_rom_bank = 1;
+ }
+ break;
+ case GB_MBC5:
+ gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8);
+ gb->mbc_ram_bank = gb->mbc5.ram_bank;
+ break;
+ case GB_HUC1:
+ if (gb->huc1.mode == 0) {
+ gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6);
+ gb->mbc_ram_bank = 0;
+ }
+ else {
+ gb->mbc_rom_bank = gb->huc1.bank_low;
+ gb->mbc_ram_bank = gb->huc1.bank_high;
+ }
+ break;
+ case GB_HUC3:
+ gb->mbc_rom_bank = gb->huc3.rom_bank;
+ gb->mbc_ram_bank = gb->huc3.ram_bank;
+ break;
+ }
+}
+
+void GB_configure_cart(GB_gameboy_t *gb)
+{
+ gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]];
+
+ if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) {
+ GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n");
+ gb->cartridge_type = &GB_cart_defs[0x11];
+ }
+ else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) {
+ GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]);
+ }
+
+ if (gb->cartridge_type->has_ram) {
+ if (gb->cartridge_type->mbc_type == GB_MBC2) {
+ gb->mbc_ram_size = 0x200;
+ }
+ else {
+ static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000};
+ gb->mbc_ram_size = ram_sizes[gb->rom[0x149]];
+ }
+ gb->mbc_ram = malloc(gb->mbc_ram_size);
+
+ /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */
+ memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size);
+ }
+
+ /* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these).
+ See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */
+
+ /* Attempt to "guess" wiring */
+ if (gb->cartridge_type->mbc_type == GB_MBC1) {
+ if (gb->rom_size >= 0x44000 && memcmp(gb->rom + 0x104, gb->rom + 0x40104, 0x30) == 0) {
+ gb->mbc1_wiring = GB_MBC1M_WIRING;
+ }
+ }
+
+ /* Set MBC5's bank to 1 correctly */
+ if (gb->cartridge_type->mbc_type == GB_MBC5) {
+ gb->mbc5.rom_bank_low = 1;
+ }
+}
diff --git a/gb/Core/mbc.h b/gb/Core/mbc.h
new file mode 100644
index 0000000..7e9b47f
--- /dev/null
+++ b/gb/Core/mbc.h
@@ -0,0 +1,32 @@
+#ifndef MBC_h
+#define MBC_h
+#include "gb_struct_def.h"
+#include
+
+typedef struct {
+ enum {
+ GB_NO_MBC,
+ GB_MBC1,
+ GB_MBC2,
+ GB_MBC3,
+ GB_MBC5,
+ GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */
+ GB_HUC3,
+ } mbc_type;
+ enum {
+ GB_STANDARD_MBC,
+ GB_CAMERA,
+ } mbc_subtype;
+ bool has_ram;
+ bool has_battery;
+ bool has_rtc;
+ bool has_rumble;
+} GB_cartridge_t;
+
+#ifdef GB_INTERNAL
+extern const GB_cartridge_t GB_cart_defs[256];
+void GB_update_mbc_mappings(GB_gameboy_t *gb);
+void GB_configure_cart(GB_gameboy_t *gb);
+#endif
+
+#endif /* MBC_h */
diff --git a/gb/Core/memory.c b/gb/Core/memory.c
new file mode 100644
index 0000000..003bb77
--- /dev/null
+++ b/gb/Core/memory.c
@@ -0,0 +1,1015 @@
+#include
+#include
+#include "gb.h"
+
+typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr);
+typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
+
+typedef enum {
+ GB_BUS_MAIN, /* In DMG: Cart and RAM. In CGB: Cart only */
+ GB_BUS_RAM, /* In CGB only. */
+ GB_BUS_VRAM,
+ GB_BUS_INTERNAL, /* Anything in highram. Might not be the most correct name. */
+} GB_bus_t;
+
+static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (addr < 0x8000) {
+ return GB_BUS_MAIN;
+ }
+ if (addr < 0xA000) {
+ return GB_BUS_VRAM;
+ }
+ if (addr < 0xC000) {
+ return GB_BUS_MAIN;
+ }
+ if (addr < 0xFE00) {
+ return GB_is_cgb(gb)? GB_BUS_RAM : GB_BUS_MAIN;
+ }
+ return GB_BUS_INTERNAL;
+}
+
+static uint8_t bitwise_glitch(uint8_t a, uint8_t b, uint8_t c)
+{
+ return ((a ^ c) & (b ^ c)) ^ c;
+}
+
+static uint8_t bitwise_glitch_read(uint8_t a, uint8_t b, uint8_t c)
+{
+ return b | (a & c);
+}
+
+static uint8_t bitwise_glitch_read_increase(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
+{
+ return (b & (a | c | d)) | (a & c & d);
+}
+
+void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address)
+{
+ if (GB_is_cgb(gb)) return;
+
+ if (address >= 0xFE00 && address < 0xFF00) {
+ if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) {
+ gb->oam[gb->accessed_oam_row] = bitwise_glitch(gb->oam[gb->accessed_oam_row],
+ gb->oam[gb->accessed_oam_row - 8],
+ gb->oam[gb->accessed_oam_row - 4]);
+ gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch(gb->oam[gb->accessed_oam_row + 1],
+ gb->oam[gb->accessed_oam_row - 7],
+ gb->oam[gb->accessed_oam_row - 3]);
+ for (unsigned i = 2; i < 8; i++) {
+ gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i];
+ }
+ }
+ }
+}
+
+void GB_trigger_oam_bug_read(GB_gameboy_t *gb, uint16_t address)
+{
+ if (GB_is_cgb(gb)) return;
+
+ if (address >= 0xFE00 && address < 0xFF00) {
+ if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 8) {
+ gb->oam[gb->accessed_oam_row - 8] =
+ gb->oam[gb->accessed_oam_row] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row],
+ gb->oam[gb->accessed_oam_row - 8],
+ gb->oam[gb->accessed_oam_row - 4]);
+ gb->oam[gb->accessed_oam_row - 7] =
+ gb->oam[gb->accessed_oam_row + 1] = bitwise_glitch_read(gb->oam[gb->accessed_oam_row + 1],
+ gb->oam[gb->accessed_oam_row - 7],
+ gb->oam[gb->accessed_oam_row - 3]);
+ for (unsigned i = 2; i < 8; i++) {
+ gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 8 + i];
+ }
+ }
+ }
+}
+
+void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address)
+{
+ if (GB_is_cgb(gb)) return;
+
+ if (address >= 0xFE00 && address < 0xFF00) {
+ if (gb->accessed_oam_row != 0xff && gb->accessed_oam_row >= 0x20 && gb->accessed_oam_row < 0x98) {
+ gb->oam[gb->accessed_oam_row - 0x8] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x10],
+ gb->oam[gb->accessed_oam_row - 0x08],
+ gb->oam[gb->accessed_oam_row ],
+ gb->oam[gb->accessed_oam_row - 0x04]
+ );
+ gb->oam[gb->accessed_oam_row - 0x7] = bitwise_glitch_read_increase(gb->oam[gb->accessed_oam_row - 0x0f],
+ gb->oam[gb->accessed_oam_row - 0x07],
+ gb->oam[gb->accessed_oam_row + 0x01],
+ gb->oam[gb->accessed_oam_row - 0x03]
+ );
+ for (unsigned i = 0; i < 8; i++) {
+ gb->oam[gb->accessed_oam_row + i] = gb->oam[gb->accessed_oam_row - 0x10 + i] = gb->oam[gb->accessed_oam_row - 0x08 + i];
+ }
+ }
+ }
+}
+
+static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting) || addr >= 0xFE00) return false;
+ return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src);
+}
+
+static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (addr < 0x100 && !gb->boot_rom_finished) {
+ return gb->boot_rom[addr];
+ }
+
+ if (addr >= 0x200 && addr < 0x900 && GB_is_cgb(gb) && !gb->boot_rom_finished) {
+ return gb->boot_rom[addr];
+ }
+
+ if (!gb->rom_size) {
+ return 0xFF;
+ }
+ unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000;
+ return gb->rom[effective_address & (gb->rom_size - 1)];
+}
+
+static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr)
+{
+ unsigned effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000;
+ return gb->rom[effective_address & (gb->rom_size - 1)];
+}
+
+static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (gb->vram_read_blocked) {
+ return 0xFF;
+ }
+ return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000];
+}
+
+static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr)
+{
+ if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) &&
+ gb->cartridge_type->mbc_subtype != GB_CAMERA &&
+ gb->cartridge_type->mbc_type != GB_HUC1) return 0xFF;
+
+ if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
+ /* RTC read */
+ gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */
+ return gb->rtc_latched.data[gb->mbc_ram_bank - 8];
+ }
+
+ if (gb->camera_registers_mapped) {
+ return GB_camera_read_register(gb, addr);
+ }
+
+ if (!gb->mbc_ram) {
+ return 0xFF;
+ }
+
+ if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) {
+ return GB_camera_read_image(gb, addr - 0xa100);
+ }
+
+ uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)];
+ if (gb->cartridge_type->mbc_type == GB_MBC2) {
+ ret |= 0xF0;
+ }
+ return ret;
+}
+
+static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr)
+{
+ return gb->ram[addr & 0x0FFF];
+}
+
+static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr)
+{
+ return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000];
+}
+
+static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
+{
+
+ if (gb->hdma_on) {
+ return gb->last_opcode_read;
+ }
+
+ if (addr < 0xFE00) {
+ return read_banked_ram(gb, addr);
+ }
+
+ if (addr < 0xFF00) {
+ if (gb->oam_write_blocked && !GB_is_cgb(gb)) {
+ GB_trigger_oam_bug_read(gb, addr);
+ return 0xff;
+ }
+
+ if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
+ /* Todo: Does reading from OAM during DMA causes the OAM bug? */
+ return 0xff;
+ }
+
+ if (gb->oam_read_blocked) {
+ if (!GB_is_cgb(gb)) {
+ if (addr < 0xFEA0) {
+ if (gb->accessed_oam_row == 0) {
+ gb->oam[(addr & 0xf8)] =
+ gb->oam[0] = bitwise_glitch_read(gb->oam[0],
+ gb->oam[(addr & 0xf8)],
+ gb->oam[(addr & 0xfe)]);
+ gb->oam[(addr & 0xf8) + 1] =
+ gb->oam[1] = bitwise_glitch_read(gb->oam[1],
+ gb->oam[(addr & 0xf8) + 1],
+ gb->oam[(addr & 0xfe) | 1]);
+ for (unsigned i = 2; i < 8; i++) {
+ gb->oam[i] = gb->oam[(addr & 0xf8) + i];
+ }
+ }
+ else if (gb->accessed_oam_row == 0xa0) {
+ gb->oam[0x9e] = bitwise_glitch_read(gb->oam[0x9c],
+ gb->oam[0x9e],
+ gb->oam[(addr & 0xf8) | 6]);
+ gb->oam[0x9f] = bitwise_glitch_read(gb->oam[0x9d],
+ gb->oam[0x9f],
+ gb->oam[(addr & 0xf8) | 7]);
+
+ for (unsigned i = 0; i < 8; i++) {
+ gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i];
+ }
+ }
+ }
+ }
+ return 0xff;
+ }
+
+ if (addr < 0xFEA0) {
+ return gb->oam[addr & 0xFF];
+ }
+
+ if (gb->oam_read_blocked) {
+ return 0xFF;
+ }
+
+ switch (gb->model) {
+ case GB_MODEL_CGB_E:
+ case GB_MODEL_AGB:
+ return (addr & 0xF0) | ((addr >> 4) & 0xF);
+
+ /*
+ case GB_MODEL_CGB_D:
+ if (addr > 0xfec0) {
+ addr |= 0xf0;
+ }
+ return gb->extra_oam[addr - 0xfea0];
+ */
+
+ case GB_MODEL_CGB_C:
+ /*
+ case GB_MODEL_CGB_B:
+ case GB_MODEL_CGB_A:
+ case GB_MODEL_CGB_0:
+ */
+ addr &= ~0x18;
+ return gb->extra_oam[addr - 0xfea0];
+
+ case GB_MODEL_DMG_B:
+ case GB_MODEL_SGB_NTSC:
+ case GB_MODEL_SGB_PAL:
+ case GB_MODEL_SGB_NTSC_NO_SFC:
+ case GB_MODEL_SGB_PAL_NO_SFC:
+ case GB_MODEL_SGB2:
+ case GB_MODEL_SGB2_NO_SFC:
+ ;
+ }
+ }
+
+ if (addr < 0xFF00) {
+
+ return 0;
+
+ }
+
+ if (addr < 0xFF80) {
+ switch (addr & 0xFF) {
+ case GB_IO_IF:
+ return gb->io_registers[GB_IO_IF] | 0xE0;
+ case GB_IO_TAC:
+ return gb->io_registers[GB_IO_TAC] | 0xF8;
+ case GB_IO_STAT:
+ return gb->io_registers[GB_IO_STAT] | 0x80;
+ case GB_IO_DMG_EMULATION_INDICATION:
+ if (!gb->cgb_mode) {
+ return 0xFF;
+ }
+ return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE;
+
+ case GB_IO_PCM_12:
+ if (!GB_is_cgb(gb)) return 0xFF;
+ return (gb->apu.is_active[GB_SQUARE_2] ? (gb->apu.samples[GB_SQUARE_2] << 4) : 0) |
+ (gb->apu.is_active[GB_SQUARE_1] ? (gb->apu.samples[GB_SQUARE_1]) : 0);
+ case GB_IO_PCM_34:
+ if (!GB_is_cgb(gb)) return 0xFF;
+ return (gb->apu.is_active[GB_NOISE] ? (gb->apu.samples[GB_NOISE] << 4) : 0) |
+ (gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0);
+ case GB_IO_JOYP:
+ GB_timing_sync(gb);
+ case GB_IO_TMA:
+ case GB_IO_LCDC:
+ case GB_IO_SCY:
+ case GB_IO_SCX:
+ case GB_IO_LY:
+ case GB_IO_LYC:
+ case GB_IO_BGP:
+ case GB_IO_OBP0:
+ case GB_IO_OBP1:
+ case GB_IO_WY:
+ case GB_IO_WX:
+ case GB_IO_SC:
+ case GB_IO_SB:
+ case GB_IO_DMA:
+ return gb->io_registers[addr & 0xFF];
+ case GB_IO_TIMA:
+ if (gb->tima_reload_state == GB_TIMA_RELOADING) {
+ return 0;
+ }
+ return gb->io_registers[GB_IO_TIMA];
+ case GB_IO_DIV:
+ return gb->div_counter >> 8;
+ case GB_IO_HDMA5:
+ if (!gb->cgb_mode) return 0xFF;
+ return ((gb->hdma_on || gb->hdma_on_hblank)? 0 : 0x80) | ((gb->hdma_steps_left - 1) & 0x7F);
+ case GB_IO_SVBK:
+ if (!gb->cgb_mode) {
+ return 0xFF;
+ }
+ return gb->cgb_ram_bank | ~0x7;
+ case GB_IO_VBK:
+ if (!GB_is_cgb(gb)) {
+ return 0xFF;
+ }
+ return gb->cgb_vram_bank | ~0x1;
+
+ /* Todo: It seems that a CGB in DMG mode can access BGPI and OBPI, but not BGPD and OBPD? */
+ case GB_IO_BGPI:
+ case GB_IO_OBPI:
+ if (!GB_is_cgb(gb)) {
+ return 0xFF;
+ }
+ return gb->io_registers[addr & 0xFF] | 0x40;
+
+ case GB_IO_BGPD:
+ case GB_IO_OBPD:
+ {
+ if (!gb->cgb_mode && gb->boot_rom_finished) {
+ return 0xFF;
+ }
+ if (gb->cgb_palettes_blocked) {
+ return 0xFF;
+ }
+ uint8_t index_reg = (addr & 0xFF) - 1;
+ return ((addr & 0xFF) == GB_IO_BGPD?
+ gb->background_palettes_data :
+ gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F];
+ }
+
+ case GB_IO_KEY1:
+ if (!gb->cgb_mode) {
+ return 0xFF;
+ }
+ return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E);
+
+ case GB_IO_RP: {
+ if (!gb->cgb_mode) return 0xFF;
+ /* You will read your own IR LED if it's on. */
+ bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1);
+ uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C;
+ if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) {
+ ret |= 2;
+ }
+ return ret;
+ }
+ case GB_IO_UNKNOWN2:
+ case GB_IO_UNKNOWN3:
+ return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] : 0xFF;
+ case GB_IO_UNKNOWN4:
+ return gb->cgb_mode? gb->io_registers[addr & 0xFF] : 0xFF;
+ case GB_IO_UNKNOWN5:
+ return GB_is_cgb(gb)? gb->io_registers[addr & 0xFF] | 0x8F : 0xFF;
+ default:
+ if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) {
+ return GB_apu_read(gb, addr & 0xFF);
+ }
+ return 0xFF;
+ }
+ /* Hardware registers */
+ return 0;
+ }
+
+ if (addr == 0xFFFF) {
+ /* Interrupt Mask */
+ return gb->interrupt_enable;
+ }
+
+ /* HRAM */
+ return gb->hram[addr - 0xFF80];
+}
+
+static GB_read_function_t * const read_map[] =
+{
+ read_rom, read_rom, read_rom, read_rom, /* 0XXX, 1XXX, 2XXX, 3XXX */
+ read_mbc_rom, read_mbc_rom, read_mbc_rom, read_mbc_rom, /* 4XXX, 5XXX, 6XXX, 7XXX */
+ read_vram, read_vram, /* 8XXX, 9XXX */
+ read_mbc_ram, read_mbc_ram, /* AXXX, BXXX */
+ read_ram, read_banked_ram, /* CXXX, DXXX */
+ read_ram, read_high_memory, /* EXXX FXXX */
+};
+
+void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback)
+{
+ gb->read_memory_callback = callback;
+}
+
+uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (gb->n_watchpoints) {
+ GB_debugger_test_read_watchpoint(gb, addr);
+ }
+ if (is_addr_in_dma_use(gb, addr)) {
+ addr = gb->dma_current_src;
+ }
+ if (gb->read_memory_callback) {
+ uint8_t data = read_map[addr >> 12](gb, addr);
+ data = gb->read_memory_callback(gb, addr, data);
+ return data;
+ }
+ return read_map[addr >> 12](gb, addr);
+}
+
+static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ switch (gb->cartridge_type->mbc_type) {
+ case GB_NO_MBC: return;
+ case GB_MBC1:
+ switch (addr & 0xF000) {
+ case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
+ case 0x2000: case 0x3000: gb->mbc1.bank_low = value; break;
+ case 0x4000: case 0x5000: gb->mbc1.bank_high = value; break;
+ case 0x6000: case 0x7000: gb->mbc1.mode = value; break;
+ }
+ break;
+ case GB_MBC2:
+ switch (addr & 0xF000) {
+ case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
+ case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break;
+ }
+ break;
+ case GB_MBC3:
+ switch (addr & 0xF000) {
+ case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
+ case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break;
+ case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break;
+ case 0x6000: case 0x7000:
+ if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */
+ memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real));
+ }
+ gb->rtc_latch = value & 1;
+ break;
+ }
+ break;
+ case GB_MBC5:
+ switch (addr & 0xF000) {
+ case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
+ case 0x2000: gb->mbc5.rom_bank_low = value; break;
+ case 0x3000: gb->mbc5.rom_bank_high = value; break;
+ case 0x4000: case 0x5000:
+ if (gb->cartridge_type->has_rumble) {
+ if (!!(value & 8) != gb->rumble_state) {
+ gb->rumble_state = !gb->rumble_state;
+ if (gb->rumble_callback) {
+ gb->rumble_callback(gb, gb->rumble_state);
+ }
+ }
+ value &= 7;
+ }
+ gb->mbc5.ram_bank = value;
+ gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA;
+ break;
+ }
+ break;
+ case GB_HUC1:
+ switch (addr & 0xF000) {
+ case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
+ case 0x2000: case 0x3000: gb->huc1.bank_low = value; break;
+ case 0x4000: case 0x5000: gb->huc1.bank_high = value; break;
+ case 0x6000: case 0x7000: gb->huc1.mode = value; break;
+ }
+ break;
+ case GB_HUC3:
+ switch (addr & 0xF000) {
+ case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break;
+ case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break;
+ case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break;
+ }
+ break;
+ }
+ GB_update_mbc_mappings(gb);
+}
+
+static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ if (gb->vram_write_blocked) {
+ //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr);
+ return;
+ }
+ gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value;
+}
+
+static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ if (gb->camera_registers_mapped) {
+ GB_camera_write_register(gb, addr, value);
+ return;
+ }
+
+ if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return;
+
+ if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) {
+ gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value;
+ return;
+ }
+
+ if (!gb->mbc_ram) {
+ return;
+ }
+
+ gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value;
+}
+
+static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ gb->ram[addr & 0x0FFF] = value;
+}
+
+static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value;
+}
+
+static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ if (addr < 0xFE00) {
+ GB_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr);
+ write_banked_ram(gb, addr, value);
+ return;
+ }
+
+ if (addr < 0xFF00) {
+ if (gb->oam_write_blocked) {
+ GB_trigger_oam_bug(gb, addr);
+ return;
+ }
+
+ if ((gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) {
+ /* Todo: Does writing to OAM during DMA causes the OAM bug? */
+ return;
+ }
+
+ if (GB_is_cgb(gb)) {
+ if (addr < 0xFEA0) {
+ gb->oam[addr & 0xFF] = value;
+ }
+ switch (gb->model) {
+ /*
+ case GB_MODEL_CGB_D:
+ if (addr > 0xfec0) {
+ addr |= 0xf0;
+ }
+ gb->extra_oam[addr - 0xfea0] = value;
+ break;
+ */
+ case GB_MODEL_CGB_C:
+ /*
+ case GB_MODEL_CGB_B:
+ case GB_MODEL_CGB_A:
+ case GB_MODEL_CGB_0:
+ */
+ addr &= ~0x18;
+ gb->extra_oam[addr - 0xfea0] = value;
+ break;
+ case GB_MODEL_DMG_B:
+ case GB_MODEL_SGB_NTSC:
+ case GB_MODEL_SGB_PAL:
+ case GB_MODEL_SGB_NTSC_NO_SFC:
+ case GB_MODEL_SGB_PAL_NO_SFC:
+ case GB_MODEL_SGB2:
+ case GB_MODEL_SGB2_NO_SFC:
+ case GB_MODEL_CGB_E:
+ case GB_MODEL_AGB:
+ break;
+ }
+ return;
+ }
+
+ if (addr < 0xFEA0) {
+ if (gb->accessed_oam_row == 0xa0) {
+ for (unsigned i = 0; i < 8; i++) {
+ if ((i & 6) != (addr & 6)) {
+ gb->oam[(addr & 0xf8) + i] = gb->oam[0x98 + i];
+ }
+ else {
+ gb->oam[(addr & 0xf8) + i] = bitwise_glitch(gb->oam[(addr & 0xf8) + i], gb->oam[0x9c], gb->oam[0x98 + i]);
+ }
+ }
+ }
+
+ gb->oam[addr & 0xFF] = value;
+
+ if (gb->accessed_oam_row == 0) {
+ gb->oam[0] = bitwise_glitch(gb->oam[0],
+ gb->oam[(addr & 0xf8)],
+ gb->oam[(addr & 0xfe)]);
+ gb->oam[1] = bitwise_glitch(gb->oam[1],
+ gb->oam[(addr & 0xf8) + 1],
+ gb->oam[(addr & 0xfe) | 1]);
+ for (unsigned i = 2; i < 8; i++) {
+ gb->oam[i] = gb->oam[(addr & 0xf8) + i];
+ }
+ }
+ }
+ else if (gb->accessed_oam_row == 0) {
+ gb->oam[addr & 0x7] = value;
+ }
+ return;
+ }
+
+ /* Todo: Clean this code up: use a function table and move relevant code to display.c and timing.c
+ (APU read and writes are already at apu.c) */
+ if (addr < 0xFF80) {
+ /* Hardware registers */
+ switch (addr & 0xFF) {
+ case GB_IO_WX:
+ GB_window_related_write(gb, addr & 0xFF, value);
+ break;
+ case GB_IO_IF:
+ case GB_IO_SCX:
+ case GB_IO_SCY:
+ case GB_IO_BGP:
+ case GB_IO_OBP0:
+ case GB_IO_OBP1:
+ case GB_IO_WY:
+ case GB_IO_SB:
+ case GB_IO_DMG_EMULATION_INDICATION:
+ case GB_IO_UNKNOWN2:
+ case GB_IO_UNKNOWN3:
+ case GB_IO_UNKNOWN4:
+ case GB_IO_UNKNOWN5:
+ gb->io_registers[addr & 0xFF] = value;
+ return;
+ case GB_IO_LYC:
+
+ /* TODO: Probably completely wrong in double speed mode */
+
+ /* TODO: This hack is disgusting */
+ if (gb->display_state == 29 && GB_is_cgb(gb)) {
+ gb->ly_for_comparison = 153;
+ GB_STAT_update(gb);
+ gb->ly_for_comparison = 0;
+ }
+
+ gb->io_registers[addr & 0xFF] = value;
+
+ /* These are the states when LY changes, let the display routine call GB_STAT_update for use
+ so it correctly handles T-cycle accurate LYC writes */
+ if (!GB_is_cgb(gb) || (
+ gb->display_state != 35 &&
+ gb->display_state != 26 &&
+ gb->display_state != 15 &&
+ gb->display_state != 16)) {
+
+ /* More hacks to make LYC write conflicts work */
+ if (gb->display_state == 14 && GB_is_cgb(gb)) {
+ gb->ly_for_comparison = 153;
+ GB_STAT_update(gb);
+ gb->ly_for_comparison = -1;
+ }
+ else {
+ GB_STAT_update(gb);
+ }
+ }
+ return;
+
+ case GB_IO_TIMA:
+ if (gb->tima_reload_state != GB_TIMA_RELOADED) {
+ gb->io_registers[GB_IO_TIMA] = value;
+ }
+ return;
+
+ case GB_IO_TMA:
+ gb->io_registers[GB_IO_TMA] = value;
+ if (gb->tima_reload_state != GB_TIMA_RUNNING) {
+ gb->io_registers[GB_IO_TIMA] = value;
+ }
+ return;
+
+ case GB_IO_TAC:
+ GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value);
+ gb->io_registers[GB_IO_TAC] = value;
+ return;
+
+
+ case GB_IO_LCDC:
+ if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) {
+ gb->display_cycles = 0;
+ gb->display_state = 0;
+ if (GB_is_sgb(gb)) {
+ gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED;
+ }
+ else if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) {
+ gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON;
+ }
+ }
+ else if (!(value & 0x80) && (gb->io_registers[GB_IO_LCDC] & 0x80)) {
+ /* Sync after turning off LCD */
+ GB_timing_sync(gb);
+ GB_lcd_off(gb);
+ }
+ /* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */
+ GB_window_related_write(gb, addr & 0xFF, value);
+ return;
+
+ case GB_IO_STAT:
+ /* Delete previous R/W bits */
+ gb->io_registers[GB_IO_STAT] &= 7;
+ /* Set them by value */
+ gb->io_registers[GB_IO_STAT] |= value & ~7;
+ /* Set unused bit to 1 */
+ gb->io_registers[GB_IO_STAT] |= 0x80;
+
+ GB_STAT_update(gb);
+ return;
+
+ case GB_IO_DIV:
+ /* Reset the div state machine */
+ gb->div_state = 0;
+ gb->div_cycles = 0;
+ return;
+
+ case GB_IO_JOYP:
+ if (gb->joyp_write_callback) {
+ gb->joyp_write_callback(gb, value);
+ GB_update_joyp(gb);
+ }
+ else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) {
+ GB_sgb_write(gb, value);
+ gb->io_registers[GB_IO_JOYP] = value & 0xF0;
+ GB_update_joyp(gb);
+ }
+ return;
+
+ case GB_IO_BIOS:
+ gb->boot_rom_finished = true;
+ return;
+
+ case GB_IO_DMG_EMULATION:
+ if (GB_is_cgb(gb) && !gb->boot_rom_finished) {
+ gb->cgb_mode = !(value & 0xC); /* The real "contents" of this register aren't quite known yet. */
+ }
+ return;
+
+ case GB_IO_DMA:
+ if (gb->dma_steps_left) {
+ /* This is not correct emulation, since we're not really delaying the second DMA.
+ One write that should have happened in the first DMA will not happen. However,
+ since that byte will be overwritten by the second DMA before it can actually be
+ read, it doesn't actually matter. */
+ gb->is_dma_restarting = true;
+ }
+ gb->dma_cycles = -7;
+ gb->dma_current_dest = 0;
+ gb->dma_current_src = value << 8;
+ gb->dma_steps_left = 0xa0;
+ gb->io_registers[GB_IO_DMA] = value;
+ return;
+ case GB_IO_SVBK:
+ if (!gb->cgb_mode) {
+ return;
+ }
+ gb->cgb_ram_bank = value & 0x7;
+ if (!gb->cgb_ram_bank) {
+ gb->cgb_ram_bank++;
+ }
+ return;
+ case GB_IO_VBK:
+ if (!gb->cgb_mode) {
+ return;
+ }
+ gb->cgb_vram_bank = value & 0x1;
+ return;
+
+ case GB_IO_BGPI:
+ case GB_IO_OBPI:
+ if (!GB_is_cgb(gb)) {
+ return;
+ }
+ gb->io_registers[addr & 0xFF] = value;
+ return;
+ case GB_IO_BGPD:
+ case GB_IO_OBPD:
+ if (!gb->cgb_mode && gb->boot_rom_finished) {
+ /* Todo: Due to the behavior of a broken Game & Watch Gallery 2 ROM on a real CGB. A proper test ROM
+ is required. */
+ return;
+ }
+
+ uint8_t index_reg = (addr & 0xFF) - 1;
+ if (gb->cgb_palettes_blocked) {
+ if (gb->io_registers[index_reg] & 0x80) {
+ gb->io_registers[index_reg]++;
+ gb->io_registers[index_reg] |= 0x80;
+ }
+ return;
+ }
+ ((addr & 0xFF) == GB_IO_BGPD?
+ gb->background_palettes_data :
+ gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F] = value;
+ GB_palette_changed(gb, (addr & 0xFF) == GB_IO_BGPD, gb->io_registers[index_reg] & 0x3F);
+ if (gb->io_registers[index_reg] & 0x80) {
+ gb->io_registers[index_reg]++;
+ gb->io_registers[index_reg] |= 0x80;
+ }
+ return;
+ case GB_IO_KEY1:
+ if (!gb->cgb_mode) {
+ return;
+ }
+ gb->io_registers[GB_IO_KEY1] = value;
+ return;
+ case GB_IO_HDMA1:
+ if (gb->cgb_mode) {
+ gb->hdma_current_src &= 0xF0;
+ gb->hdma_current_src |= value << 8;
+ }
+ return;
+ case GB_IO_HDMA2:
+ if (gb->cgb_mode) {
+ gb->hdma_current_src &= 0xFF00;
+ gb->hdma_current_src |= value & 0xF0;
+ }
+ return;
+ case GB_IO_HDMA3:
+ if (gb->cgb_mode) {
+ gb->hdma_current_dest &= 0xF0;
+ gb->hdma_current_dest |= value << 8;
+ }
+ return;
+ case GB_IO_HDMA4:
+ if (gb->cgb_mode) {
+ gb->hdma_current_dest &= 0x1F00;
+ gb->hdma_current_dest |= value & 0xF0;
+ }
+ return;
+ case GB_IO_HDMA5:
+ if (!gb->cgb_mode) return;
+ if ((value & 0x80) == 0 && gb->hdma_on_hblank) {
+ gb->hdma_on_hblank = false;
+ return;
+ }
+ gb->hdma_on = (value & 0x80) == 0;
+ gb->hdma_on_hblank = (value & 0x80) != 0;
+ if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) {
+ gb->hdma_on = true;
+ }
+ gb->io_registers[GB_IO_HDMA5] = value;
+ gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1;
+ /* Todo: Verify this. Gambatte's DMA tests require this. */
+ if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) {
+ gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4;
+ }
+ gb->hdma_cycles = -12;
+ return;
+
+ /* Todo: what happens when starting a transfer during a transfer?
+ What happens when starting a transfer during external clock?
+ */
+ case GB_IO_SC:
+ if (!gb->cgb_mode) {
+ value |= 2;
+ }
+ gb->io_registers[GB_IO_SC] = value | (~0x83);
+ if ((value & 0x80) && (value & 0x1) ) {
+ gb->serial_length = gb->cgb_mode && (value & 2)? 16 : 512;
+ gb->serial_count = 0;
+ /* Todo: This is probably incorrect for CGB's faster clock mode. */
+ gb->serial_cycles &= 0xFF;
+ if (gb->serial_transfer_bit_start_callback) {
+ gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
+ }
+ }
+ else {
+ gb->serial_length = 0;
+ }
+ return;
+
+ case GB_IO_RP: {
+ if (!GB_is_cgb(gb)) {
+ return;
+ }
+ if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) {
+ if (gb->infrared_callback) {
+ gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change);
+ gb->cycles_since_ir_change = 0;
+ }
+ }
+ gb->io_registers[GB_IO_RP] = value;
+ return;
+ }
+
+ default:
+ if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) {
+ GB_apu_write(gb, addr & 0xFF, value);
+ return;
+ }
+ GB_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr);
+ return;
+ }
+ }
+
+ if (addr == 0xFFFF) {
+ /* Interrupt mask */
+ gb->interrupt_enable = value;
+ return;
+ }
+
+ /* HRAM */
+ gb->hram[addr - 0xFF80] = value;
+}
+
+
+
+static GB_write_function_t * const write_map[] =
+{
+ write_mbc, write_mbc, write_mbc, write_mbc, /* 0XXX, 1XXX, 2XXX, 3XXX */
+ write_mbc, write_mbc, write_mbc, write_mbc, /* 4XXX, 5XXX, 6XXX, 7XXX */
+ write_vram, write_vram, /* 8XXX, 9XXX */
+ write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */
+ write_ram, write_banked_ram, /* CXXX, DXXX */
+ write_ram, write_high_memory, /* EXXX FXXX */
+};
+
+void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ if (gb->n_watchpoints) {
+ GB_debugger_test_write_watchpoint(gb, addr, value);
+ }
+ if (is_addr_in_dma_use(gb, addr)) {
+ /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */
+ return;
+ }
+ write_map[addr >> 12](gb, addr, value);
+}
+
+void GB_dma_run(GB_gameboy_t *gb)
+{
+ while (gb->dma_cycles >= 4 && gb->dma_steps_left) {
+ /* Todo: measure this value */
+ gb->dma_cycles -= 4;
+ gb->dma_steps_left--;
+
+ if (gb->dma_current_src < 0xe000) {
+ gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src);
+ }
+ else {
+ /* Todo: Not correct on the CGB */
+ gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src & ~0x2000);
+ }
+
+ /* dma_current_src must be the correct value during GB_read_memory */
+ gb->dma_current_src++;
+ if (!gb->dma_steps_left) {
+ gb->is_dma_restarting = false;
+ }
+ }
+}
+
+void GB_hdma_run(GB_gameboy_t *gb)
+{
+ if (!gb->hdma_on) return;
+
+ while (gb->hdma_cycles >= 0x4) {
+ gb->hdma_cycles -= 0x4;
+
+ GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++)));
+
+ if ((gb->hdma_current_dest & 0xf) == 0) {
+ if (--gb->hdma_steps_left == 0) {
+ gb->hdma_on = false;
+ gb->hdma_on_hblank = false;
+ gb->hdma_starting = false;
+ gb->io_registers[GB_IO_HDMA5] &= 0x7F;
+ break;
+ }
+ if (gb->hdma_on_hblank) {
+ gb->hdma_on = false;
+ break;
+ }
+ }
+ }
+}
diff --git a/gb/Core/memory.h b/gb/Core/memory.h
new file mode 100644
index 0000000..f0d0390
--- /dev/null
+++ b/gb/Core/memory.h
@@ -0,0 +1,18 @@
+#ifndef memory_h
+#define memory_h
+#include "gb_struct_def.h"
+#include
+
+typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data);
+void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback);
+
+uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr);
+void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value);
+#ifdef GB_INTERNAL
+void GB_dma_run(GB_gameboy_t *gb);
+void GB_hdma_run(GB_gameboy_t *gb);
+void GB_trigger_oam_bug(GB_gameboy_t *gb, uint16_t address);
+void GB_trigger_oam_bug_read_increase(GB_gameboy_t *gb, uint16_t address);
+#endif
+
+#endif /* memory_h */
diff --git a/gb/Core/printer.c b/gb/Core/printer.c
new file mode 100644
index 0000000..add1f86
--- /dev/null
+++ b/gb/Core/printer.c
@@ -0,0 +1,216 @@
+#include "gb.h"
+
+/* TODO: Emulation is VERY basic and assumes the ROM correctly uses the printer's interface.
+ Incorrect usage is not correctly emulated, as it's not well documented, nor do I
+ have my own GB Printer to figure it out myself.
+
+ It also does not currently emulate communication timeout, which means that a bug
+ might prevent the printer operation until the GameBoy is restarted.
+
+ Also, field mask values are assumed. */
+
+static void handle_command(GB_gameboy_t *gb)
+{
+
+ switch (gb->printer.command_id) {
+ case GB_PRINTER_INIT_COMMAND:
+ gb->printer.status = 0;
+ gb->printer.image_offset = 0;
+ break;
+
+ case GB_PRINTER_START_COMMAND:
+ if (gb->printer.command_length == 4) {
+ gb->printer.status = 6; /* Printing */
+ uint32_t image[gb->printer.image_offset];
+ uint8_t palette = gb->printer.command_data[2];
+ uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff),
+ gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa),
+ gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55),
+ gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)};
+ for (unsigned i = 0; i < gb->printer.image_offset; i++) {
+ image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3];
+ }
+
+ if (gb->printer.callback) {
+ gb->printer.callback(gb, image, gb->printer.image_offset / 160,
+ gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7,
+ gb->printer.command_data[3] & 0x7F);
+ }
+
+ gb->printer.image_offset = 0;
+ }
+ break;
+
+ case GB_PRINTER_DATA_COMMAND:
+ if (gb->printer.command_length == GB_PRINTER_DATA_SIZE) {
+ gb->printer.image_offset %= sizeof(gb->printer.image);
+ gb->printer.status = 8; /* Received 0x280 bytes */
+
+ uint8_t *byte = gb->printer.command_data;
+
+ for (unsigned row = 2; row--; ) {
+ for (unsigned tile_x = 0; tile_x < 160 / 8; tile_x++) {
+ for (unsigned y = 0; y < 8; y++, byte += 2) {
+ for (unsigned x_pixel = 0; x_pixel < 8; x_pixel++) {
+ gb->printer.image[gb->printer.image_offset + tile_x * 8 + x_pixel + y * 160] =
+ ((*byte) >> 7) | (((*(byte + 1)) >> 7) << 1);
+ (*byte) <<= 1;
+ (*(byte + 1)) <<= 1;
+ }
+ }
+ }
+
+ gb->printer.image_offset += 8 * 160;
+ }
+ }
+
+ case GB_PRINTER_NOP_COMMAND:
+ default:
+ break;
+ }
+}
+
+
+static void byte_reieve_completed(GB_gameboy_t *gb, uint8_t byte_received)
+{
+ gb->printer.byte_to_send = 0;
+ switch (gb->printer.command_state) {
+ case GB_PRINTER_COMMAND_MAGIC1:
+ if (byte_received != 0x88) {
+ return;
+ }
+ gb->printer.status &= ~1;
+ gb->printer.command_length = 0;
+ gb->printer.checksum = 0;
+ break;
+
+ case GB_PRINTER_COMMAND_MAGIC2:
+ if (byte_received != 0x33) {
+ if (byte_received != 0x88) {
+ gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
+ }
+ return;
+ }
+ break;
+
+ case GB_PRINTER_COMMAND_ID:
+ gb->printer.command_id = byte_received & 0xF;
+ break;
+
+ case GB_PRINTER_COMMAND_COMPRESSION:
+ gb->printer.compression = byte_received & 1;
+ break;
+
+ case GB_PRINTER_COMMAND_LENGTH_LOW:
+ gb->printer.length_left = byte_received;
+ break;
+
+ case GB_PRINTER_COMMAND_LENGTH_HIGH:
+ gb->printer.length_left |= (byte_received & 3) << 8;
+ break;
+
+ case GB_PRINTER_COMMAND_DATA:
+ if (gb->printer.command_length != GB_PRINTER_MAX_COMMAND_LENGTH) {
+ if (gb->printer.compression) {
+ if (!gb->printer.compression_run_lenth) {
+ gb->printer.compression_run_is_compressed = byte_received & 0x80;
+ gb->printer.compression_run_lenth = (byte_received & 0x7F) + 1 + gb->printer.compression_run_is_compressed;
+ }
+ else if (gb->printer.compression_run_is_compressed) {
+ while (gb->printer.compression_run_lenth) {
+ gb->printer.command_data[gb->printer.command_length++] = byte_received;
+ gb->printer.compression_run_lenth--;
+ if (gb->printer.command_length == GB_PRINTER_MAX_COMMAND_LENGTH) {
+ gb->printer.compression_run_lenth = 0;
+ }
+ }
+ }
+ else {
+ gb->printer.command_data[gb->printer.command_length++] = byte_received;
+ gb->printer.compression_run_lenth--;
+ }
+ }
+ else {
+ gb->printer.command_data[gb->printer.command_length++] = byte_received;
+ }
+ }
+ gb->printer.length_left--;
+ break;
+
+ case GB_PRINTER_COMMAND_CHECKSUM_LOW:
+ gb->printer.checksum ^= byte_received;
+ break;
+
+ case GB_PRINTER_COMMAND_CHECKSUM_HIGH:
+ gb->printer.checksum ^= byte_received << 8;
+ if (gb->printer.checksum) {
+ gb->printer.status |= 1; /* Checksum error*/
+ gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
+ return;
+ }
+ gb->printer.byte_to_send = 0x81;
+
+ break;
+ case GB_PRINTER_COMMAND_ACTIVE:
+ if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) {
+ /* Games expect INIT commands to return 0? */
+ gb->printer.byte_to_send = 0;
+ }
+ else {
+ gb->printer.byte_to_send = gb->printer.status;
+ }
+ break;
+ case GB_PRINTER_COMMAND_STATUS:
+
+ /* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */
+ if (gb->printer.status == 6) {
+ gb->printer.status = 4; /* Done */
+ }
+
+ gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1;
+ handle_command(gb);
+ return;
+ }
+
+ if (gb->printer.command_state >= GB_PRINTER_COMMAND_ID && gb->printer.command_state < GB_PRINTER_COMMAND_CHECKSUM_LOW) {
+ gb->printer.checksum += byte_received;
+ }
+
+ if (gb->printer.command_state != GB_PRINTER_COMMAND_DATA) {
+ gb->printer.command_state++;
+ }
+
+ if (gb->printer.command_state == GB_PRINTER_COMMAND_DATA) {
+ if (gb->printer.length_left == 0) {
+ gb->printer.command_state++;
+ }
+ }
+}
+
+static void serial_start(GB_gameboy_t *gb, bool bit_received)
+{
+ gb->printer.byte_being_recieved <<= 1;
+ gb->printer.byte_being_recieved |= bit_received;
+ gb->printer.bits_recieved++;
+ if (gb->printer.bits_recieved == 8) {
+ byte_reieve_completed(gb, gb->printer.byte_being_recieved);
+ gb->printer.bits_recieved = 0;
+ gb->printer.byte_being_recieved = 0;
+ }
+}
+
+static bool serial_end(GB_gameboy_t *gb)
+{
+ bool ret = gb->printer.bit_to_send;
+ gb->printer.bit_to_send = gb->printer.byte_to_send & 0x80;
+ gb->printer.byte_to_send <<= 1;
+ return ret;
+}
+
+void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback)
+{
+ memset(&gb->printer, 0, sizeof(gb->printer));
+ GB_set_serial_transfer_bit_start_callback(gb, serial_start);
+ GB_set_serial_transfer_bit_end_callback(gb, serial_end);
+ gb->printer.callback = callback;
+}
diff --git a/gb/Core/printer.h b/gb/Core/printer.h
new file mode 100644
index 0000000..7cf179e
--- /dev/null
+++ b/gb/Core/printer.h
@@ -0,0 +1,63 @@
+#ifndef printer_h
+#define printer_h
+#include
+#include
+#include "gb_struct_def.h"
+#define GB_PRINTER_MAX_COMMAND_LENGTH 0x280
+#define GB_PRINTER_DATA_SIZE 0x280
+
+typedef void (*GB_print_image_callback_t)(GB_gameboy_t *gb,
+ uint32_t *image,
+ uint8_t height,
+ uint8_t top_margin,
+ uint8_t bottom_margin,
+ uint8_t exposure);
+
+
+typedef struct
+{
+ /* Communication state machine */
+
+ enum {
+ GB_PRINTER_COMMAND_MAGIC1,
+ GB_PRINTER_COMMAND_MAGIC2,
+ GB_PRINTER_COMMAND_ID,
+ GB_PRINTER_COMMAND_COMPRESSION,
+ GB_PRINTER_COMMAND_LENGTH_LOW,
+ GB_PRINTER_COMMAND_LENGTH_HIGH,
+ GB_PRINTER_COMMAND_DATA,
+ GB_PRINTER_COMMAND_CHECKSUM_LOW,
+ GB_PRINTER_COMMAND_CHECKSUM_HIGH,
+ GB_PRINTER_COMMAND_ACTIVE,
+ GB_PRINTER_COMMAND_STATUS,
+ } command_state : 8;
+ enum {
+ GB_PRINTER_INIT_COMMAND = 1,
+ GB_PRINTER_START_COMMAND = 2,
+ GB_PRINTER_DATA_COMMAND = 4,
+ GB_PRINTER_NOP_COMMAND = 0xF,
+ } command_id : 8;
+ bool compression;
+ uint16_t length_left;
+ uint8_t command_data[GB_PRINTER_MAX_COMMAND_LENGTH];
+ uint16_t command_length;
+ uint16_t checksum;
+ uint8_t status;
+ uint8_t byte_to_send;
+
+ uint8_t image[160 * 200];
+ uint16_t image_offset;
+
+ GB_print_image_callback_t callback;
+
+ uint8_t compression_run_lenth;
+ bool compression_run_is_compressed;
+
+ uint8_t bits_recieved;
+ uint8_t byte_being_recieved;
+ bool bit_to_send;
+} GB_printer_t;
+
+
+void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback);
+#endif
diff --git a/gb/Core/random.c b/gb/Core/random.c
new file mode 100644
index 0000000..cc4d4d3
--- /dev/null
+++ b/gb/Core/random.c
@@ -0,0 +1,38 @@
+#include "random.h"
+#include
+
+static uint64_t seed;
+static bool enabled = true;
+
+uint8_t GB_random(void)
+{
+ if (!enabled) return 0;
+
+ seed *= 0x27BB2EE687B0B0FDL;
+ seed += 0xB504F32D;
+ return seed >> 56;
+}
+
+uint32_t GB_random32(void)
+{
+ GB_random();
+ return seed >> 32;
+}
+
+void GB_random_seed(uint64_t new_seed)
+{
+ seed = new_seed;
+}
+
+void GB_random_set_enabled(bool enable)
+{
+ enabled = enable;
+}
+
+static void __attribute__((constructor)) init_seed(void)
+{
+ seed = time(NULL);
+ for (unsigned i = 64; i--;) {
+ GB_random();
+ }
+}
diff --git a/gb/Core/random.h b/gb/Core/random.h
new file mode 100644
index 0000000..8ab0e50
--- /dev/null
+++ b/gb/Core/random.h
@@ -0,0 +1,12 @@
+#ifndef random_h
+#define random_h
+
+#include
+#include
+
+uint8_t GB_random(void);
+uint32_t GB_random32(void);
+void GB_random_seed(uint64_t seed);
+void GB_random_set_enabled(bool enable);
+
+#endif /* random_h */
diff --git a/gb/Core/rewind.c b/gb/Core/rewind.c
new file mode 100644
index 0000000..c3900d6
--- /dev/null
+++ b/gb/Core/rewind.c
@@ -0,0 +1,208 @@
+#include "gb.h"
+#include
+#include
+#include
+#include
+
+static uint8_t *state_compress(const uint8_t *prev, const uint8_t *data, size_t uncompressed_size)
+{
+ size_t malloc_size = 0x1000;
+ uint8_t *compressed = malloc(malloc_size);
+ size_t counter_pos = 0;
+ size_t data_pos = sizeof(uint16_t);
+ bool prev_mode = true;
+ *(uint16_t *)compressed = 0;
+#define COUNTER (*(uint16_t *)&compressed[counter_pos])
+#define DATA (compressed[data_pos])
+
+ while (uncompressed_size) {
+ if (prev_mode) {
+ if (*data == *prev && COUNTER != 0xffff) {
+ COUNTER++;
+ data++;
+ prev++;
+ uncompressed_size--;
+ }
+ else {
+ prev_mode = false;
+ counter_pos += sizeof(uint16_t);
+ data_pos = counter_pos + sizeof(uint16_t);
+ if (data_pos >= malloc_size) {
+ malloc_size *= 2;
+ compressed = realloc(compressed, malloc_size);
+ }
+ COUNTER = 0;
+ }
+ }
+ else {
+ if (*data != *prev && COUNTER != 0xffff) {
+ COUNTER++;
+ DATA = *data;
+ data_pos++;
+ data++;
+ prev++;
+ uncompressed_size--;
+ if (data_pos >= malloc_size) {
+ malloc_size *= 2;
+ compressed = realloc(compressed, malloc_size);
+ }
+ }
+ else {
+ prev_mode = true;
+ counter_pos = data_pos;
+ data_pos = counter_pos + sizeof(uint16_t);
+ if (counter_pos >= malloc_size - 1) {
+ malloc_size *= 2;
+ compressed = realloc(compressed, malloc_size);
+ }
+ COUNTER = 0;
+ }
+ }
+ }
+
+ return realloc(compressed, data_pos);
+#undef DATA
+#undef COUNTER
+}
+
+
+static void state_decompress(const uint8_t *prev, uint8_t *data, uint8_t *dest, size_t uncompressed_size)
+{
+ size_t counter_pos = 0;
+ size_t data_pos = sizeof(uint16_t);
+ bool prev_mode = true;
+#define COUNTER (*(uint16_t *)&data[counter_pos])
+#define DATA (data[data_pos])
+
+ while (uncompressed_size) {
+ if (prev_mode) {
+ if (COUNTER) {
+ COUNTER--;
+ *(dest++) = *(prev++);
+ uncompressed_size--;
+ }
+ else {
+ prev_mode = false;
+ counter_pos += sizeof(uint16_t);
+ data_pos = counter_pos + sizeof(uint16_t);
+ }
+ }
+ else {
+ if (COUNTER) {
+ COUNTER--;
+ *(dest++) = DATA;
+ data_pos++;
+ prev++;
+ uncompressed_size--;
+ }
+ else {
+ prev_mode = true;
+ counter_pos = data_pos;
+ data_pos += sizeof(uint16_t);
+ }
+ }
+ }
+#undef DATA
+#undef COUNTER
+}
+
+void GB_rewind_push(GB_gameboy_t *gb)
+{
+ const size_t save_size = GB_get_save_state_size(gb);
+ if (!gb->rewind_sequences) {
+ if (gb->rewind_buffer_length) {
+ gb->rewind_sequences = malloc(sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
+ memset(gb->rewind_sequences, 0, sizeof(*gb->rewind_sequences) * gb->rewind_buffer_length);
+ gb->rewind_pos = 0;
+ }
+ else {
+ return;
+ }
+ }
+
+ if (gb->rewind_sequences[gb->rewind_pos].pos == GB_REWIND_FRAMES_PER_KEY) {
+ gb->rewind_pos++;
+ if (gb->rewind_pos == gb->rewind_buffer_length) {
+ gb->rewind_pos = 0;
+ }
+ if (gb->rewind_sequences[gb->rewind_pos].key_state) {
+ free(gb->rewind_sequences[gb->rewind_pos].key_state);
+ gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
+ }
+ for (unsigned i = 0; i < GB_REWIND_FRAMES_PER_KEY; i++) {
+ if (gb->rewind_sequences[gb->rewind_pos].compressed_states[i]) {
+ free(gb->rewind_sequences[gb->rewind_pos].compressed_states[i]);
+ gb->rewind_sequences[gb->rewind_pos].compressed_states[i] = 0;
+ }
+ }
+ gb->rewind_sequences[gb->rewind_pos].pos = 0;
+ }
+
+ if (!gb->rewind_sequences[gb->rewind_pos].key_state) {
+ gb->rewind_sequences[gb->rewind_pos].key_state = malloc(save_size);
+ GB_save_state_to_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state);
+ }
+ else {
+ uint8_t *save_state = malloc(save_size);
+ GB_save_state_to_buffer(gb, save_state);
+ gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos++] =
+ state_compress(gb->rewind_sequences[gb->rewind_pos].key_state, save_state, save_size);
+ free(save_state);
+ }
+
+}
+
+bool GB_rewind_pop(GB_gameboy_t *gb)
+{
+ if (!gb->rewind_sequences || !gb->rewind_sequences[gb->rewind_pos].key_state) {
+ return false;
+ }
+
+ const size_t save_size = GB_get_save_state_size(gb);
+ if (gb->rewind_sequences[gb->rewind_pos].pos == 0) {
+ GB_load_state_from_buffer(gb, gb->rewind_sequences[gb->rewind_pos].key_state, save_size);
+ free(gb->rewind_sequences[gb->rewind_pos].key_state);
+ gb->rewind_sequences[gb->rewind_pos].key_state = NULL;
+ gb->rewind_pos = gb->rewind_pos == 0? gb->rewind_buffer_length - 1 : gb->rewind_pos - 1;
+ return true;
+ }
+
+ uint8_t *save_state = malloc(save_size);
+ state_decompress(gb->rewind_sequences[gb->rewind_pos].key_state,
+ gb->rewind_sequences[gb->rewind_pos].compressed_states[--gb->rewind_sequences[gb->rewind_pos].pos],
+ save_state,
+ save_size);
+ free(gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos]);
+ gb->rewind_sequences[gb->rewind_pos].compressed_states[gb->rewind_sequences[gb->rewind_pos].pos] = NULL;
+ GB_load_state_from_buffer(gb, save_state, save_size);
+ free(save_state);
+ return true;
+}
+
+void GB_rewind_free(GB_gameboy_t *gb)
+{
+ if (!gb->rewind_sequences) return;
+ for (unsigned i = 0; i < gb->rewind_buffer_length; i++) {
+ if (gb->rewind_sequences[i].key_state) {
+ free(gb->rewind_sequences[i].key_state);
+ }
+ for (unsigned j = 0; j < GB_REWIND_FRAMES_PER_KEY; j++) {
+ if (gb->rewind_sequences[i].compressed_states[j]) {
+ free(gb->rewind_sequences[i].compressed_states[j]);
+ }
+ }
+ }
+ free(gb->rewind_sequences);
+ gb->rewind_sequences = NULL;
+}
+
+void GB_set_rewind_length(GB_gameboy_t *gb, double seconds)
+{
+ GB_rewind_free(gb);
+ if (seconds == 0) {
+ gb->rewind_buffer_length = 0;
+ }
+ else {
+ gb->rewind_buffer_length = (size_t) ceil(seconds * CPU_FREQUENCY / LCDC_PERIOD / GB_REWIND_FRAMES_PER_KEY);
+ }
+}
diff --git a/gb/Core/rewind.h b/gb/Core/rewind.h
new file mode 100644
index 0000000..ad54841
--- /dev/null
+++ b/gb/Core/rewind.h
@@ -0,0 +1,14 @@
+#ifndef rewind_h
+#define rewind_h
+
+#include
+#include "gb_struct_def.h"
+
+#ifdef GB_INTERNAL
+void GB_rewind_push(GB_gameboy_t *gb);
+void GB_rewind_free(GB_gameboy_t *gb);
+#endif
+bool GB_rewind_pop(GB_gameboy_t *gb);
+void GB_set_rewind_length(GB_gameboy_t *gb, double seconds);
+
+#endif
diff --git a/gb/Core/save_state.c b/gb/Core/save_state.c
new file mode 100644
index 0000000..8ef99ae
--- /dev/null
+++ b/gb/Core/save_state.c
@@ -0,0 +1,377 @@
+#include "gb.h"
+#include
+#include
+
+static bool dump_section(FILE *f, const void *src, uint32_t size)
+{
+ if (fwrite(&size, 1, sizeof(size), f) != sizeof(size)) {
+ return false;
+ }
+
+ if (fwrite(src, 1, size, f) != size) {
+ return false;
+ }
+
+ return true;
+}
+
+#define DUMP_SECTION(gb, f, section) dump_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
+
+/* Todo: we need a sane and protable save state format. */
+int GB_save_state(GB_gameboy_t *gb, const char *path)
+{
+ FILE *f = fopen(path, "wb");
+ if (!f) {
+ GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
+ return errno;
+ }
+
+ if (fwrite(GB_GET_SECTION(gb, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
+ if (!DUMP_SECTION(gb, f, core_state)) goto error;
+ if (!DUMP_SECTION(gb, f, dma )) goto error;
+ if (!DUMP_SECTION(gb, f, mbc )) goto error;
+ if (!DUMP_SECTION(gb, f, hram )) goto error;
+ if (!DUMP_SECTION(gb, f, timing )) goto error;
+ if (!DUMP_SECTION(gb, f, apu )) goto error;
+ if (!DUMP_SECTION(gb, f, rtc )) goto error;
+ if (!DUMP_SECTION(gb, f, video )) goto error;
+
+ if (GB_is_hle_sgb(gb)) {
+ if (!dump_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
+ }
+
+
+ if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) {
+ goto error;
+ }
+
+ if (fwrite(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
+ goto error;
+ }
+
+ if (fwrite(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
+ goto error;
+ }
+
+ errno = 0;
+
+error:
+ fclose(f);
+ return errno;
+}
+
+#undef DUMP_SECTION
+
+size_t GB_get_save_state_size(GB_gameboy_t *gb)
+{
+ return GB_SECTION_SIZE(header)
+ + GB_SECTION_SIZE(core_state) + sizeof(uint32_t)
+ + GB_SECTION_SIZE(dma ) + sizeof(uint32_t)
+ + GB_SECTION_SIZE(mbc ) + sizeof(uint32_t)
+ + GB_SECTION_SIZE(hram ) + sizeof(uint32_t)
+ + GB_SECTION_SIZE(timing ) + sizeof(uint32_t)
+ + GB_SECTION_SIZE(apu ) + sizeof(uint32_t)
+ + GB_SECTION_SIZE(rtc ) + sizeof(uint32_t)
+ + GB_SECTION_SIZE(video ) + sizeof(uint32_t)
+ + (GB_is_hle_sgb(gb)? sizeof(*gb->sgb) + sizeof(uint32_t) : 0)
+ + gb->mbc_ram_size
+ + gb->ram_size
+ + gb->vram_size;
+}
+
+/* A write-line function for memory copying */
+static void buffer_write(const void *src, size_t size, uint8_t **dest)
+{
+ memcpy(*dest, src, size);
+ *dest += size;
+}
+
+static void buffer_dump_section(uint8_t **buffer, const void *src, uint32_t size)
+{
+ buffer_write(&size, sizeof(size), buffer);
+ buffer_write(src, size, buffer);
+}
+
+#define DUMP_SECTION(gb, buffer, section) buffer_dump_section(&buffer, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
+void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer)
+{
+ buffer_write(GB_GET_SECTION(gb, header), GB_SECTION_SIZE(header), &buffer);
+ DUMP_SECTION(gb, buffer, core_state);
+ DUMP_SECTION(gb, buffer, dma );
+ DUMP_SECTION(gb, buffer, mbc );
+ DUMP_SECTION(gb, buffer, hram );
+ DUMP_SECTION(gb, buffer, timing );
+ DUMP_SECTION(gb, buffer, apu );
+ DUMP_SECTION(gb, buffer, rtc );
+ DUMP_SECTION(gb, buffer, video );
+
+ if (GB_is_hle_sgb(gb)) {
+ buffer_dump_section(&buffer, gb->sgb, sizeof(*gb->sgb));
+ }
+
+
+ buffer_write(gb->mbc_ram, gb->mbc_ram_size, &buffer);
+ buffer_write(gb->ram, gb->ram_size, &buffer);
+ buffer_write(gb->vram, gb->vram_size, &buffer);
+}
+
+/* Best-effort read function for maximum future compatibility. */
+static bool read_section(FILE *f, void *dest, uint32_t size)
+{
+ uint32_t saved_size = 0;
+ if (fread(&saved_size, 1, sizeof(size), f) != sizeof(size)) {
+ return false;
+ }
+
+ if (saved_size <= size) {
+ if (fread(dest, 1, saved_size, f) != saved_size) {
+ return false;
+ }
+ }
+ else {
+ if (fread(dest, 1, size, f) != size) {
+ return false;
+ }
+ fseek(f, saved_size - size, SEEK_CUR);
+ }
+
+ return true;
+}
+#undef DUMP_SECTION
+
+static bool verify_state_compatibility(GB_gameboy_t *gb, GB_gameboy_t *save)
+{
+ if (gb->magic != save->magic) {
+ GB_log(gb, "The file is not a save state, or is from an incompatible operating system.\n");
+ return false;
+ }
+
+ if (gb->version != save->version) {
+ GB_log(gb, "The save state is for a different version of SameBoy.\n");
+ return false;
+ }
+
+ if (gb->mbc_ram_size < save->mbc_ram_size) {
+ GB_log(gb, "The save state has non-matching MBC RAM size.\n");
+ return false;
+ }
+
+ if (gb->vram_size != save->vram_size) {
+ GB_log(gb, "The save state has non-matching VRAM size. Try changing the emulated model.\n");
+ return false;
+ }
+
+ if (GB_is_hle_sgb(gb) != GB_is_hle_sgb(save)) {
+ GB_log(gb, "The save state is %sfor a Super Game Boy. Try changing the emulated model.\n", GB_is_hle_sgb(save)? "" : "not ");
+ return false;
+ }
+
+ if (gb->ram_size != save->ram_size) {
+ if (gb->ram_size == 0x1000 * 8 && save->ram_size == 0x2000 * 8) {
+ /* A bug in versions prior to 0.12 made CGB instances allocate twice the ammount of RAM.
+ Ignore this issue to retain compatibility with older, 0.11, save states. */
+ }
+ else {
+ GB_log(gb, "The save state has non-matching RAM size. Try changing the emulated model.\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#define READ_SECTION(gb, f, section) read_section(f, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
+
+int GB_load_state(GB_gameboy_t *gb, const char *path)
+{
+ GB_gameboy_t save;
+
+ /* Every unread value should be kept the same. */
+ memcpy(&save, gb, sizeof(save));
+ /* ...Except ram size, we use it to detect old saves with incorrect ram sizes */
+ save.ram_size = 0;
+
+ FILE *f = fopen(path, "rb");
+ if (!f) {
+ GB_log(gb, "Could not open save state: %s.\n", strerror(errno));
+ return errno;
+ }
+
+ if (fread(GB_GET_SECTION(&save, header), 1, GB_SECTION_SIZE(header), f) != GB_SECTION_SIZE(header)) goto error;
+ if (!READ_SECTION(&save, f, core_state)) goto error;
+ if (!READ_SECTION(&save, f, dma )) goto error;
+ if (!READ_SECTION(&save, f, mbc )) goto error;
+ if (!READ_SECTION(&save, f, hram )) goto error;
+ if (!READ_SECTION(&save, f, timing )) goto error;
+ if (!READ_SECTION(&save, f, apu )) goto error;
+ if (!READ_SECTION(&save, f, rtc )) goto error;
+ if (!READ_SECTION(&save, f, video )) goto error;
+
+ if (save.ram_size == 0) {
+ /* Save doesn't have ram size specified, it's a pre 0.12 save state with potentially
+ incorrect RAM amount if it's a CGB instance */
+ if (GB_is_cgb(&save)) {
+ save.ram_size = 0x2000 * 8; // Incorrect RAM size
+ }
+ else {
+ save.ram_size = gb->ram_size;
+ }
+ }
+
+ if (!verify_state_compatibility(gb, &save)) {
+ errno = -1;
+ goto error;
+ }
+
+ if (GB_is_hle_sgb(gb)) {
+ if (!read_section(f, gb->sgb, sizeof(*gb->sgb))) goto error;
+ }
+
+ memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
+ if (fread(gb->mbc_ram, 1, save.mbc_ram_size, f) != save.mbc_ram_size) {
+ fclose(f);
+ return EIO;
+ }
+
+ if (fread(gb->ram, 1, gb->ram_size, f) != gb->ram_size) {
+ fclose(f);
+ return EIO;
+ }
+
+ /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */
+ fseek(f, save.ram_size - gb->ram_size, SEEK_CUR);
+
+ if (fread(gb->vram, 1, gb->vram_size, f) != gb->vram_size) {
+ fclose(f);
+ return EIO;
+ }
+
+ size_t orig_ram_size = gb->ram_size;
+ memcpy(gb, &save, sizeof(save));
+ gb->ram_size = orig_ram_size;
+
+ errno = 0;
+
+ if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
+ gb->rumble_callback(gb, gb->rumble_state);
+ }
+
+ for (unsigned i = 0; i < 32; i++) {
+ GB_palette_changed(gb, false, i * 2);
+ GB_palette_changed(gb, true, i * 2);
+ }
+
+ gb->bg_fifo.read_end &= 0xF;
+ gb->bg_fifo.write_end &= 0xF;
+ gb->oam_fifo.read_end &= 0xF;
+ gb->oam_fifo.write_end &= 0xF;
+
+error:
+ fclose(f);
+ return errno;
+}
+
+#undef READ_SECTION
+
+/* An read-like function for buffer-copying */
+static size_t buffer_read(void *dest, size_t length, const uint8_t **buffer, size_t *buffer_length)
+{
+ if (length > *buffer_length) {
+ length = *buffer_length;
+ }
+
+ memcpy(dest, *buffer, length);
+ *buffer += length;
+ *buffer_length -= length;
+
+ return length;
+}
+
+static bool buffer_read_section(const uint8_t **buffer, size_t *buffer_length, void *dest, uint32_t size)
+{
+ uint32_t saved_size = 0;
+ if (buffer_read(&saved_size, sizeof(size), buffer, buffer_length) != sizeof(size)) {
+ return false;
+ }
+
+ if (saved_size > *buffer_length) return false;
+
+ if (saved_size <= size) {
+ if (buffer_read(dest, saved_size, buffer, buffer_length) != saved_size) {
+ return false;
+ }
+ }
+ else {
+ if (buffer_read(dest, size, buffer, buffer_length) != size) {
+ return false;
+ }
+ *buffer += saved_size - size;
+ *buffer_length -= saved_size - size;
+ }
+
+ return true;
+}
+
+#define READ_SECTION(gb, buffer, length, section) buffer_read_section(&buffer, &length, GB_GET_SECTION(gb, section), GB_SECTION_SIZE(section))
+int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length)
+{
+ GB_gameboy_t save;
+
+ /* Every unread value should be kept the same. */
+ memcpy(&save, gb, sizeof(save));
+
+ if (buffer_read(GB_GET_SECTION(&save, header), GB_SECTION_SIZE(header), &buffer, &length) != GB_SECTION_SIZE(header)) return -1;
+ if (!READ_SECTION(&save, buffer, length, core_state)) return -1;
+ if (!READ_SECTION(&save, buffer, length, dma )) return -1;
+ if (!READ_SECTION(&save, buffer, length, mbc )) return -1;
+ if (!READ_SECTION(&save, buffer, length, hram )) return -1;
+ if (!READ_SECTION(&save, buffer, length, timing )) return -1;
+ if (!READ_SECTION(&save, buffer, length, apu )) return -1;
+ if (!READ_SECTION(&save, buffer, length, rtc )) return -1;
+ if (!READ_SECTION(&save, buffer, length, video )) return -1;
+
+ if (!verify_state_compatibility(gb, &save)) {
+ return -1;
+ }
+
+ if (GB_is_hle_sgb(gb)) {
+ if (!buffer_read_section(&buffer, &length, gb->sgb, sizeof(*gb->sgb))) return -1;
+ }
+
+ memset(gb->mbc_ram + save.mbc_ram_size, 0xFF, gb->mbc_ram_size - save.mbc_ram_size);
+ if (buffer_read(gb->mbc_ram, save.mbc_ram_size, &buffer, &length) != save.mbc_ram_size) {
+ return -1;
+ }
+
+ if (buffer_read(gb->ram, gb->ram_size, &buffer, &length) != gb->ram_size) {
+ return -1;
+ }
+
+ if (buffer_read(gb->vram,gb->vram_size, &buffer, &length) != gb->vram_size) {
+ return -1;
+ }
+
+ /* Fix for 0.11 save states that allocate twice the amount of RAM in CGB instances */
+ buffer += save.ram_size - gb->ram_size;
+ length -= save.ram_size - gb->ram_size;
+
+ memcpy(gb, &save, sizeof(save));
+
+ if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
+ gb->rumble_callback(gb, gb->rumble_state);
+ }
+
+ for (unsigned i = 0; i < 32; i++) {
+ GB_palette_changed(gb, false, i * 2);
+ GB_palette_changed(gb, true, i * 2);
+ }
+
+ gb->bg_fifo.read_end &= 0xF;
+ gb->bg_fifo.write_end &= 0xF;
+ gb->oam_fifo.read_end &= 0xF;
+ gb->oam_fifo.write_end &= 0xF;
+
+ return 0;
+}
+
+#undef READ_SECTION
diff --git a/gb/Core/save_state.h b/gb/Core/save_state.h
new file mode 100644
index 0000000..546ac2d
--- /dev/null
+++ b/gb/Core/save_state.h
@@ -0,0 +1,24 @@
+/* Macros to make the GB_gameboy_t struct more future compatible when state saving */
+#ifndef save_state_h
+#define save_state_h
+#include
+
+#define GB_PADDING(type, old_usage) type old_usage##__do_not_use
+
+#define GB_SECTION(name, ...) __attribute__ ((aligned (8))) struct {} name##_section_start; __VA_ARGS__; struct {} name##_section_end
+#define GB_SECTION_OFFSET(name) (offsetof(GB_gameboy_t, name##_section_start))
+#define GB_SECTION_SIZE(name) (offsetof(GB_gameboy_t, name##_section_end) - offsetof(GB_gameboy_t, name##_section_start))
+#define GB_GET_SECTION(gb, name) ((void*)&((gb)->name##_section_start))
+
+#define GB_aligned_double __attribute__ ((aligned (8))) double
+
+
+/* Public calls related to save states */
+int GB_save_state(GB_gameboy_t *gb, const char *path);
+size_t GB_get_save_state_size(GB_gameboy_t *gb);
+/* Assumes buffer is big enough to contain the save state. Use with GB_get_save_state_size(). */
+void GB_save_state_to_buffer(GB_gameboy_t *gb, uint8_t *buffer);
+
+int GB_load_state(GB_gameboy_t *gb, const char *path);
+int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t length);
+#endif /* save_state_h */
diff --git a/gb/Core/sgb.c b/gb/Core/sgb.c
new file mode 100644
index 0000000..7ebeae0
--- /dev/null
+++ b/gb/Core/sgb.c
@@ -0,0 +1,842 @@
+#include "gb.h"
+#include "random.h"
+#include
+#include
+
+#ifndef M_PI
+ #define M_PI 3.14159265358979323846
+#endif
+
+#define INTRO_ANIMATION_LENGTH 200
+
+enum {
+ PAL01 = 0x00,
+ PAL23 = 0x01,
+ PAL03 = 0x02,
+ PAL12 = 0x03,
+ ATTR_BLK = 0x04,
+ ATTR_LIN = 0x05,
+ ATTR_DIV = 0x06,
+ PAL_SET = 0x0A,
+ PAL_TRN = 0x0B,
+ DATA_SND = 0x0F,
+ MLT_REQ = 0x11,
+ CHR_TRN = 0x13,
+ PCT_TRN = 0x14,
+ ATTR_TRN = 0x15,
+ ATTR_SET = 0x16,
+ MASK_EN = 0x17,
+};
+
+typedef enum {
+ MASK_DISABLED,
+ MASK_FREEZE,
+ MASK_BLACK,
+ MASK_COLOR_0,
+} mask_mode_t;
+
+typedef enum {
+ TRANSFER_LOW_TILES,
+ TRANSFER_HIGH_TILES,
+ TRANSFER_BORDER_DATA,
+ TRANSFER_PALETTES,
+ TRANSFER_ATTRIBUTES,
+} transfer_dest_t;
+
+#define SGB_PACKET_SIZE 16
+static inline void pal_command(GB_gameboy_t *gb, unsigned first, unsigned second)
+{
+ gb->sgb->effective_palettes[0] = gb->sgb->effective_palettes[4] =
+ gb->sgb->effective_palettes[8] = gb->sgb->effective_palettes[12] =
+ gb->sgb->command[1] | (gb->sgb->command[2] << 8);
+
+ for (unsigned i = 0; i < 3; i++) {
+ gb->sgb->effective_palettes[first * 4 + i + 1] = gb->sgb->command[3 + i * 2] | (gb->sgb->command[4 + i * 2] << 8);
+ }
+
+ for (unsigned i = 0; i < 3; i++) {
+ gb->sgb->effective_palettes[second * 4 + i + 1] = gb->sgb->command[9 + i * 2] | (gb->sgb->command[10 + i * 2] << 8);
+ }
+}
+
+static inline void load_attribute_file(GB_gameboy_t *gb, unsigned file_index)
+{
+ if (file_index > 0x2C) return;
+ uint8_t *output = gb->sgb->attribute_map;
+ for (unsigned i = 0; i < 90; i++) {
+ uint8_t byte = gb->sgb->attribute_files[file_index * 90 + i];
+ for (unsigned j = 4; j--;) {
+ *(output++) = byte >> 6;
+ byte <<= 2;
+ }
+ }
+}
+
+static const uint16_t built_in_palettes[] =
+{
+ 0x67BF, 0x265B, 0x10B5, 0x2866,
+ 0x637B, 0x3AD9, 0x0956, 0x0000,
+ 0x7F1F, 0x2A7D, 0x30F3, 0x4CE7,
+ 0x57FF, 0x2618, 0x001F, 0x006A,
+ 0x5B7F, 0x3F0F, 0x222D, 0x10EB,
+ 0x7FBB, 0x2A3C, 0x0015, 0x0900,
+ 0x2800, 0x7680, 0x01EF, 0x2FFF,
+ 0x73BF, 0x46FF, 0x0110, 0x0066,
+ 0x533E, 0x2638, 0x01E5, 0x0000,
+ 0x7FFF, 0x2BBF, 0x00DF, 0x2C0A,
+ 0x7F1F, 0x463D, 0x74CF, 0x4CA5,
+ 0x53FF, 0x03E0, 0x00DF, 0x2800,
+ 0x433F, 0x72D2, 0x3045, 0x0822,
+ 0x7FFA, 0x2A5F, 0x0014, 0x0003,
+ 0x1EED, 0x215C, 0x42FC, 0x0060,
+ 0x7FFF, 0x5EF7, 0x39CE, 0x0000,
+ 0x4F5F, 0x630E, 0x159F, 0x3126,
+ 0x637B, 0x121C, 0x0140, 0x0840,
+ 0x66BC, 0x3FFF, 0x7EE0, 0x2C84,
+ 0x5FFE, 0x3EBC, 0x0321, 0x0000,
+ 0x63FF, 0x36DC, 0x11F6, 0x392A,
+ 0x65EF, 0x7DBF, 0x035F, 0x2108,
+ 0x2B6C, 0x7FFF, 0x1CD9, 0x0007,
+ 0x53FC, 0x1F2F, 0x0E29, 0x0061,
+ 0x36BE, 0x7EAF, 0x681A, 0x3C00,
+ 0x7BBE, 0x329D, 0x1DE8, 0x0423,
+ 0x739F, 0x6A9B, 0x7293, 0x0001,
+ 0x5FFF, 0x6732, 0x3DA9, 0x2481,
+ 0x577F, 0x3EBC, 0x456F, 0x1880,
+ 0x6B57, 0x6E1B, 0x5010, 0x0007,
+ 0x0F96, 0x2C97, 0x0045, 0x3200,
+ 0x67FF, 0x2F17, 0x2230, 0x1548,
+};
+
+static const struct {
+ char name[16];
+ unsigned palette_index;
+} palette_assignments[] =
+{
+ {"ZELDA", 5},
+ {"SUPER MARIOLAND", 6},
+ {"MARIOLAND2", 0x14},
+ {"SUPERMARIOLAND3", 2},
+ {"KIRBY DREAM LAND", 0xB},
+ {"HOSHINOKA-BI", 0xB},
+ {"KIRBY'S PINBALL", 3},
+ {"YOSSY NO TAMAGO", 0xC},
+ {"MARIO & YOSHI", 0xC},
+ {"YOSSY NO COOKIE", 4},
+ {"YOSHI'S COOKIE", 4},
+ {"DR.MARIO", 0x12},
+ {"TETRIS", 0x11},
+ {"YAKUMAN", 0x13},
+ {"METROID2", 0x1F},
+ {"KAERUNOTAMENI", 9},
+ {"GOLF", 0x18},
+ {"ALLEY WAY", 0x16},
+ {"BASEBALL", 0xF},
+ {"TENNIS", 0x17},
+ {"F1RACE", 0x1E},
+ {"KID ICARUS", 0xE},
+ {"QIX", 0x19},
+ {"SOLARSTRIKER", 7},
+ {"X", 0x1C},
+ {"GBWARS", 0x15},
+};
+
+static void command_ready(GB_gameboy_t *gb)
+{
+ /* SGB header commands are used to send the contents of the header to the SNES CPU.
+ A header command looks like this:
+ Command ID: 0b1111xxx1, where xxx is the packet index. (e.g. F1 for [0x104, 0x112), F3 for [0x112, 0x120))
+ Checksum: Simple one byte sum for the following content bytes
+ 0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */
+
+ if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
+ if(gb->boot_rom_finished) return;
+
+ uint8_t checksum = 0;
+ for (unsigned i = 2; i < 0x10; i++) {
+ checksum += gb->sgb->command[i];
+ }
+ if (checksum != gb->sgb->command[1]) {
+ GB_log(gb, "Failed checksum for SGB header command, disabling SGB features\n");
+ gb->sgb->disable_commands = true;
+ return;
+ }
+ unsigned index = (gb->sgb->command[0] >> 1) & 7;
+ if (index > 5) {
+ return;
+ }
+ memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14);
+ if (gb->sgb->command[0] == 0xfb) {
+ if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) {
+ gb->sgb->disable_commands = true;
+ for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) {
+ if (memcmp(palette_assignments[i].name, &gb->sgb->received_header[0x30], sizeof(palette_assignments[i].name)) == 0) {
+ gb->sgb->effective_palettes[0] = built_in_palettes[palette_assignments[i].palette_index * 4 - 4];
+ gb->sgb->effective_palettes[1] = built_in_palettes[palette_assignments[i].palette_index * 4 + 1 - 4];
+ gb->sgb->effective_palettes[2] = built_in_palettes[palette_assignments[i].palette_index * 4 + 2 - 4];
+ gb->sgb->effective_palettes[3] = built_in_palettes[palette_assignments[i].palette_index * 4 + 3 - 4];
+ break;
+ }
+ }
+ }
+ }
+ return;
+ }
+
+ /* Ignore malformed commands (0 length)*/
+ if ((gb->sgb->command[0] & 7) == 0) return;
+
+ switch (gb->sgb->command[0] >> 3) {
+ case PAL01:
+ pal_command(gb, 0, 1);
+ break;
+ case PAL23:
+ pal_command(gb, 2, 3);
+ break;
+ case PAL03:
+ pal_command(gb, 0, 3);
+ break;
+ case PAL12:
+ pal_command(gb, 1, 2);
+ break;
+ case ATTR_BLK: {
+ struct {
+ uint8_t count;
+ struct {
+ uint8_t control;
+ uint8_t palettes;
+ uint8_t left, top, right, bottom;
+ } data[];
+ } *command = (void *)(gb->sgb->command + 1);
+ if (command->count > 0x12) return;
+
+ for (unsigned i = 0; i < command->count; i++) {
+ bool inside = command->data[i].control & 1;
+ bool middle = command->data[i].control & 2;
+ bool outside = command->data[i].control & 4;
+ uint8_t inside_palette = command->data[i].palettes & 0x3;
+ uint8_t middle_palette = (command->data[i].palettes >> 2) & 0x3;
+ uint8_t outside_palette = (command->data[i].palettes >> 4) & 0x3;
+
+ if (inside && !middle && !outside) {
+ middle = true;
+ middle_palette = inside_palette;
+ }
+ else if (outside && !middle && !inside) {
+ middle = true;
+ middle_palette = outside_palette;
+ }
+
+ command->data[i].left &= 0x1F;
+ command->data[i].top &= 0x1F;
+ command->data[i].right &= 0x1F;
+ command->data[i].bottom &= 0x1F;
+
+ for (unsigned y = 0; y < 18; y++) {
+ for (unsigned x = 0; x < 20; x++) {
+ if (x < command->data[i].left || x > command->data[i].right ||
+ y < command->data[i].top || y > command->data[i].bottom) {
+ if (outside) {
+ gb->sgb->attribute_map[x + 20 * y] = outside_palette;
+ }
+ }
+ else if (x > command->data[i].left && x < command->data[i].right &&
+ y > command->data[i].top && y < command->data[i].bottom) {
+ if (inside) {
+ gb->sgb->attribute_map[x + 20 * y] = inside_palette;
+ }
+ }
+ else if(middle) {
+ gb->sgb->attribute_map[x + 20 * y] = middle_palette;
+ }
+ }
+ }
+ }
+ break;
+ }
+ case ATTR_LIN: {
+ struct {
+ uint8_t count;
+ uint8_t data[];
+ } *command = (void *)(gb->sgb->command + 1);
+ if (command->count > sizeof(gb->sgb->command) - 2) return;
+
+ for (unsigned i = 0; i < command->count; i++) {
+ bool horizontal = command->data[i] & 0x80;
+ uint8_t palette = (command->data[i] >> 5) & 0x3;
+ uint8_t line = (command->data[i]) & 0x1F;
+
+ if (horizontal) {
+ if (line > 18) continue;
+ for (unsigned x = 0; x < 20; x++) {
+ gb->sgb->attribute_map[x + 20 * line] = palette;
+ }
+ }
+ else {
+ if (line > 20) continue;
+ for (unsigned y = 0; y < 18; y++) {
+ gb->sgb->attribute_map[line + 20 * y] = palette;
+ }
+ }
+ }
+ break;
+ }
+ case ATTR_DIV: {
+ uint8_t high_palette = gb->sgb->command[1] & 3;
+ uint8_t low_palette = (gb->sgb->command[1] >> 2) & 3;
+ uint8_t middle_palette = (gb->sgb->command[1] >> 4) & 3;
+ bool horizontal = gb->sgb->command[1] & 0x40;
+ uint8_t line = gb->sgb->command[2] & 0x1F;
+
+ for (unsigned y = 0; y < 18; y++) {
+ for (unsigned x = 0; x < 20; x++) {
+ if ((horizontal? y : x) < line) {
+ gb->sgb->attribute_map[x + 20 * y] = low_palette;
+ }
+ else if ((horizontal? y : x) == line) {
+ gb->sgb->attribute_map[x + 20 * y] = middle_palette;
+ }
+ else {
+ gb->sgb->attribute_map[x + 20 * y] = high_palette;
+ }
+ }
+ }
+
+ break;
+ }
+ case PAL_SET:
+ memcpy(&gb->sgb->effective_palettes[0],
+ &gb->sgb->ram_palettes[4 * (gb->sgb->command[1] + (gb->sgb->command[2] & 1) * 0x100)],
+ 8);
+ memcpy(&gb->sgb->effective_palettes[4],
+ &gb->sgb->ram_palettes[4 * (gb->sgb->command[3] + (gb->sgb->command[4] & 1) * 0x100)],
+ 8);
+ memcpy(&gb->sgb->effective_palettes[8],
+ &gb->sgb->ram_palettes[4 * (gb->sgb->command[5] + (gb->sgb->command[6] & 1) * 0x100)],
+ 8);
+ memcpy(&gb->sgb->effective_palettes[12],
+ &gb->sgb->ram_palettes[4 * (gb->sgb->command[7] + (gb->sgb->command[8] & 1) * 0x100)],
+ 8);
+
+ gb->sgb->effective_palettes[12] = gb->sgb->effective_palettes[8] =
+ gb->sgb->effective_palettes[4] = gb->sgb->effective_palettes[0];
+
+ if (gb->sgb->command[9] & 0x80) {
+ load_attribute_file(gb, gb->sgb->command[9] & 0x3F);
+ }
+
+ if (gb->sgb->command[9] & 0x40) {
+ gb->sgb->mask_mode = MASK_DISABLED;
+ }
+ break;
+ case PAL_TRN:
+ gb->sgb->vram_transfer_countdown = 2;
+ gb->sgb->transfer_dest = TRANSFER_PALETTES;
+ break;
+ case DATA_SND:
+ // Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this
+ break;
+ case MLT_REQ:
+ if (gb->sgb->player_count == 1) {
+ gb->sgb->current_player = 0;
+ }
+ gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility,
+ fix this to be 0 based. */
+ if (gb->sgb->player_count == 3) {
+ gb->sgb->current_player++;
+ }
+ gb->sgb->mlt_lock = true;
+ break;
+ case CHR_TRN:
+ gb->sgb->vram_transfer_countdown = 2;
+ gb->sgb->transfer_dest = (gb->sgb->command[1] & 1)? TRANSFER_HIGH_TILES : TRANSFER_LOW_TILES;
+ break;
+ case PCT_TRN:
+ gb->sgb->vram_transfer_countdown = 2;
+ gb->sgb->transfer_dest = TRANSFER_BORDER_DATA;
+ break;
+ case ATTR_TRN:
+ gb->sgb->vram_transfer_countdown = 2;
+ gb->sgb->transfer_dest = TRANSFER_ATTRIBUTES;
+ break;
+ case ATTR_SET:
+ load_attribute_file(gb, gb->sgb->command[0] & 0x3F);
+
+ if (gb->sgb->command[0] & 0x40) {
+ gb->sgb->mask_mode = MASK_DISABLED;
+ }
+ break;
+ case MASK_EN:
+ gb->sgb->mask_mode = gb->sgb->command[1] & 3;
+ break;
+ default:
+ if ((gb->sgb->command[0] >> 3) == 8 &&
+ (gb->sgb->command[1] & ~0x80) == 0 &&
+ (gb->sgb->command[2] & ~0x80) == 0) {
+ /* Mute/dummy sound commands, ignore this command as it's used by many games at startup */
+ break;
+ }
+ GB_log(gb, "Unimplemented SGB command %x: ", gb->sgb->command[0] >> 3);
+ for (unsigned i = 0; i < gb->sgb->command_write_index / 8; i++) {
+ GB_log(gb, "%02x ", gb->sgb->command[i]);
+ }
+ GB_log(gb, "\n");
+ }
+}
+
+void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
+{
+ if (!GB_is_sgb(gb)) return;
+ if (!GB_is_hle_sgb(gb)) {
+ /* Notify via callback */
+ return;
+ }
+ if (gb->sgb->disable_commands) return;
+ if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) {
+ return;
+ }
+
+ uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8;
+ if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
+ command_size = SGB_PACKET_SIZE * 8;
+ }
+
+ if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) {
+ gb->sgb->mlt_lock ^= true;
+ }
+
+ switch ((value >> 4) & 3) {
+ case 3:
+ gb->sgb->ready_for_pulse = true;
+ if ((gb->sgb->player_count & 1) == 0 && !gb->sgb->mlt_lock) {
+ gb->sgb->current_player++;
+ gb->sgb->current_player &= 3;
+ gb->sgb->mlt_lock = true;
+ }
+ break;
+
+ case 2: // Zero
+ if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return;
+ if (gb->sgb->ready_for_stop) {
+ if (gb->sgb->command_write_index == command_size) {
+ command_ready(gb);
+ gb->sgb->command_write_index = 0;
+ memset(gb->sgb->command, 0, sizeof(gb->sgb->command));
+ }
+ gb->sgb->ready_for_pulse = false;
+ gb->sgb->ready_for_write = false;
+ gb->sgb->ready_for_stop = false;
+ }
+ else {
+ gb->sgb->command_write_index++;
+ gb->sgb->ready_for_pulse = false;
+ if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
+ gb->sgb->ready_for_stop = true;
+ }
+ }
+ break;
+ case 1: // One
+ if (!gb->sgb->ready_for_pulse || !gb->sgb->ready_for_write) return;
+ if (gb->sgb->ready_for_stop) {
+ GB_log(gb, "Corrupt SGB command.\n");
+ gb->sgb->ready_for_pulse = false;
+ gb->sgb->ready_for_write = false;
+ gb->sgb->command_write_index = 0;
+ memset(gb->sgb->command, 0, sizeof(gb->sgb->command));
+ }
+ else {
+ gb->sgb->command[gb->sgb->command_write_index / 8] |= 1 << (gb->sgb->command_write_index & 7);
+ gb->sgb->command_write_index++;
+ gb->sgb->ready_for_pulse = false;
+ if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) == 0) {
+ gb->sgb->ready_for_stop = true;
+ }
+ }
+ break;
+
+ case 0:
+ if (!gb->sgb->ready_for_pulse) return;
+ gb->sgb->ready_for_write = true;
+ gb->sgb->ready_for_pulse = false;
+ if (((gb->sgb->command_write_index) & (SGB_PACKET_SIZE * 8 - 1)) != 0 ||
+ gb->sgb->command_write_index == 0 ||
+ gb->sgb->ready_for_stop) {
+ gb->sgb->command_write_index = 0;
+ memset(gb->sgb->command, 0, sizeof(gb->sgb->command));
+ gb->sgb->ready_for_stop = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color)
+{
+ return GB_convert_rgb15(gb, color);
+}
+
+static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade)
+{
+ uint8_t r = ((color) & 0x1F) - fade;
+ uint8_t g = ((color >> 5) & 0x1F) - fade;
+ uint8_t b = ((color >> 10) & 0x1F) - fade;
+
+ if (r >= 0x20) r = 0;
+ if (g >= 0x20) g = 0;
+ if (b >= 0x20) b = 0;
+
+ color = r | (g << 5) | (b << 10);
+
+ return GB_convert_rgb15(gb, color);
+}
+
+#include
+static void render_boot_animation (GB_gameboy_t *gb)
+{
+#include "sgb_animation_logo.inc"
+ uint32_t *output = &gb->screen[48 + 40 * 256];
+ uint8_t *input = animation_logo;
+ unsigned fade_blue = 0;
+ unsigned fade_red = 0;
+ if (gb->sgb->intro_animation < 80 - 32) {
+ fade_blue = 32;
+ }
+ else if (gb->sgb->intro_animation < 80) {
+ fade_blue = 80 - gb->sgb->intro_animation;
+ }
+ else if (gb->sgb->intro_animation > INTRO_ANIMATION_LENGTH - 32) {
+ fade_red = fade_blue = gb->sgb->intro_animation - INTRO_ANIMATION_LENGTH + 32;
+ }
+ uint32_t colors[] = {
+ convert_rgb15(gb, 0),
+ convert_rgb15_with_fade(gb, 0x14A5, fade_blue),
+ convert_rgb15_with_fade(gb, 0x54E0, fade_blue),
+ convert_rgb15_with_fade(gb, 0x0019, fade_red),
+ convert_rgb15(gb, 0x0011),
+ convert_rgb15(gb, 0x0009),
+ };
+ unsigned y_min = (144 - animation_logo_height) / 2;
+ unsigned y_max = y_min + animation_logo_height;
+ for (unsigned y = 0; y < 144; y++) {
+ for (unsigned x = 0; x < 160; x++) {
+ if (y < y_min || y >= y_max) {
+ *(output++) = colors[0];
+ }
+ else {
+ uint8_t color = *input;
+ if (color >= 3) {
+ if (color == gb->sgb->intro_animation / 2 - 3) {
+ color = 5;
+ }
+ else if (color == gb->sgb->intro_animation / 2 - 4) {
+ color = 4;
+ }
+ else if (color < gb->sgb->intro_animation / 2 - 4) {
+ color = 3;
+ }
+ else {
+ color = 0;
+ }
+ }
+ *(output++) = colors[color];
+ input++;
+ }
+ }
+ output += 256 - 160;
+ }
+}
+
+static void render_jingle(GB_gameboy_t *gb, size_t count);
+void GB_sgb_render(GB_gameboy_t *gb)
+{
+ if (gb->apu_output.sample_rate) {
+ render_jingle(gb, gb->apu_output.sample_rate / GB_get_usual_frame_rate(gb));
+ }
+
+ if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) gb->sgb->intro_animation++;
+
+ if (!gb->screen || !gb->rgb_encode_callback) return;
+
+ if (gb->sgb->mask_mode != MASK_FREEZE) {
+ memcpy(gb->sgb->effective_screen_buffer,
+ gb->sgb->screen_buffer,
+ sizeof(gb->sgb->effective_screen_buffer));
+ }
+
+ if (gb->sgb->vram_transfer_countdown) {
+ if (--gb->sgb->vram_transfer_countdown == 0) {
+ if (gb->sgb->transfer_dest == TRANSFER_LOW_TILES || gb->sgb->transfer_dest == TRANSFER_HIGH_TILES) {
+ uint8_t *base = &gb->sgb->pending_border.tiles[gb->sgb->transfer_dest == TRANSFER_HIGH_TILES ? 0x80 * 8 * 8 : 0];
+ for (unsigned tile = 0; tile < 0x80; tile++) {
+ unsigned tile_x = (tile % 10) * 16;
+ unsigned tile_y = (tile / 10) * 8;
+ for (unsigned y = 0; y < 0x8; y++) {
+ for (unsigned x = 0; x < 0x8; x++) {
+ base[tile * 8 * 8 + y * 8 + x] = gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] +
+ gb->sgb->screen_buffer[(tile_x + x + 8) + (tile_y + y) * 160] * 4;
+ }
+ }
+ }
+
+ }
+ else {
+ unsigned size = 0;
+ uint16_t *data = NULL;
+
+ switch (gb->sgb->transfer_dest) {
+ case TRANSFER_PALETTES:
+ size = 0x100;
+ data = gb->sgb->ram_palettes;
+ break;
+ case TRANSFER_BORDER_DATA:
+ size = 0x88;
+ data = gb->sgb->pending_border.raw_data;
+ break;
+ case TRANSFER_ATTRIBUTES:
+ size = 0xFE;
+ data = (uint16_t *)gb->sgb->attribute_files;
+ break;
+ default:
+ return; // Corrupt state?
+ }
+
+ for (unsigned tile = 0; tile < size; tile++) {
+ unsigned tile_x = (tile % 20) * 8;
+ unsigned tile_y = (tile / 20) * 8;
+ for (unsigned y = 0; y < 0x8; y++) {
+ static const uint16_t pixel_to_bits[4] = {0x0000, 0x0080, 0x8000, 0x8080};
+ *data = 0;
+ for (unsigned x = 0; x < 8; x++) {
+ *data |= pixel_to_bits[gb->sgb->screen_buffer[(tile_x + x) + (tile_y + y) * 160] & 3] >> x;
+ }
+#ifdef GB_BIG_ENDIAN
+ if (gb->sgb->transfer_dest == TRANSFER_ATTRIBUTES) {
+ *data = __builtin_bswap16(*data);
+ }
+#endif
+ data++;
+ }
+ }
+ if (gb->sgb->transfer_dest == TRANSFER_BORDER_DATA) {
+ gb->sgb->border_animation = 64;
+ }
+ }
+ }
+ }
+
+ uint32_t colors[4 * 4];
+ for (unsigned i = 0; i < 4 * 4; i++) {
+ colors[i] = convert_rgb15(gb, gb->sgb->effective_palettes[i]);
+ }
+
+ if (gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) {
+ render_boot_animation(gb);
+ }
+ else {
+ uint32_t *output = &gb->screen[48 + 40 * 256];
+ uint8_t *input = gb->sgb->effective_screen_buffer;
+ switch ((mask_mode_t) gb->sgb->mask_mode) {
+ case MASK_DISABLED:
+ case MASK_FREEZE: {
+ for (unsigned y = 0; y < 144; y++) {
+ for (unsigned x = 0; x < 160; x++) {
+ uint8_t palette = gb->sgb->attribute_map[x / 8 + y / 8 * 20] & 3;
+ *(output++) = colors[(*(input++) & 3) + palette * 4];
+ }
+ output += 256 - 160;
+ }
+ break;
+ }
+ case MASK_BLACK:
+ {
+ uint32_t black = convert_rgb15(gb, 0);
+ for (unsigned y = 0; y < 144; y++) {
+ for (unsigned x = 0; x < 160; x++) {
+ *(output++) = black;
+ }
+ output += 256 - 160;
+ }
+ break;
+ }
+ case MASK_COLOR_0:
+ {
+ for (unsigned y = 0; y < 144; y++) {
+ for (unsigned x = 0; x < 160; x++) {
+ *(output++) = colors[0];
+ }
+ output += 256 - 160;
+ }
+ break;
+ }
+ }
+ }
+
+ uint32_t border_colors[16 * 4];
+ if (gb->sgb->border_animation == 0 || gb->sgb->intro_animation < INTRO_ANIMATION_LENGTH) {
+ for (unsigned i = 0; i < 16 * 4; i++) {
+ border_colors[i] = convert_rgb15(gb, gb->sgb->border.palette[i]);
+ }
+ }
+ else if (gb->sgb->border_animation > 32) {
+ gb->sgb->border_animation--;
+ for (unsigned i = 0; i < 16 * 4; i++) {
+ border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], 64 - gb->sgb->border_animation);
+ }
+ }
+ else {
+ gb->sgb->border_animation--;
+ for (unsigned i = 0; i < 16 * 4; i++) {
+ border_colors[i] = convert_rgb15_with_fade(gb, gb->sgb->border.palette[i], gb->sgb->border_animation);
+ }
+ }
+
+
+ if (gb->sgb->border_animation == 32) {
+ memcpy(&gb->sgb->border, &gb->sgb->pending_border, sizeof(gb->sgb->border));
+ }
+
+ for (unsigned tile_y = 0; tile_y < 28; tile_y++) {
+ for (unsigned tile_x = 0; tile_x < 32; tile_x++) {
+ bool gb_area = false;
+ if (tile_x >= 6 && tile_x < 26 && tile_y >= 5 && tile_y < 23) {
+ gb_area = true;
+ }
+ uint16_t tile = gb->sgb->border.map[tile_x + tile_y * 32];
+ uint8_t flip_x = (tile & 0x4000)? 0x7 : 0;
+ uint8_t flip_y = (tile & 0x8000)? 0x7 : 0;
+ uint8_t palette = (tile >> 10) & 3;
+ for (unsigned y = 0; y < 8; y++) {
+ for (unsigned x = 0; x < 8; x++) {
+ uint8_t color = gb->sgb->border.tiles[(tile & 0xFF) * 64 + (x ^ flip_x) + (y ^ flip_y) * 8] & 0xF;
+ if (color == 0) {
+ if (gb_area) continue;
+ gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = colors[0];
+ }
+ else {
+ gb->screen[tile_x * 8 + x + (tile_y * 8 + y) * 0x100] = border_colors[color + palette * 16];
+ }
+ }
+ }
+ }
+ }
+}
+
+void GB_sgb_load_default_data(GB_gameboy_t *gb)
+{
+
+#include "sgb_border.inc"
+
+ memcpy(gb->sgb->border.map, tilemap, sizeof(tilemap));
+ memcpy(gb->sgb->border.palette, palette, sizeof(palette));
+
+ /* Expend tileset */
+ for (unsigned tile = 0; tile < sizeof(tiles) / 32; tile++) {
+ for (unsigned y = 0; y < 8; y++) {
+ for (unsigned x = 0; x < 8; x++) {
+ gb->sgb->border.tiles[tile * 8 * 8 + y * 8 + x] =
+ (tiles[tile * 32 + y * 2 + 0] & (1 << (7 ^ x)) ? 1 : 0) |
+ (tiles[tile * 32 + y * 2 + 1] & (1 << (7 ^ x)) ? 2 : 0) |
+ (tiles[tile * 32 + y * 2 + 16] & (1 << (7 ^ x)) ? 4 : 0) |
+ (tiles[tile * 32 + y * 2 + 17] & (1 << (7 ^ x)) ? 8 : 0);
+ }
+ }
+ }
+
+ if (gb->model != GB_MODEL_SGB2) {
+ /* Delete the "2" */
+ gb->sgb->border.map[25 * 32 + 25] = gb->sgb->border.map[25 * 32 + 26] =
+ gb->sgb->border.map[26 * 32 + 25] = gb->sgb->border.map[26 * 32 + 26] =
+ gb->sgb->border.map[27 * 32 + 25] = gb->sgb->border.map[27 * 32 + 26] =
+ gb->sgb->border.map[0];
+
+ /* Re-center */
+ memmove(&gb->sgb->border.map[25 * 32 + 1], &gb->sgb->border.map[25 * 32], (32 * 3 - 1) * sizeof(gb->sgb->border.map[0]));
+ }
+ gb->sgb->effective_palettes[0] = built_in_palettes[0];
+ gb->sgb->effective_palettes[1] = built_in_palettes[1];
+ gb->sgb->effective_palettes[2] = built_in_palettes[2];
+ gb->sgb->effective_palettes[3] = built_in_palettes[3];
+}
+
+static double fm_synth(double phase)
+{
+ return (sin(phase * M_PI * 2) +
+ sin(phase * M_PI * 2 + sin(phase * M_PI * 2)) +
+ sin(phase * M_PI * 2 + sin(phase * M_PI * 3)) +
+ sin(phase * M_PI * 2 + sin(phase * M_PI * 4))) / 4;
+}
+
+static double fm_sweep(double phase)
+{
+ double ret = 0;
+ for (unsigned i = 0; i < 8; i++) {
+ ret += sin((phase * M_PI * 2 + sin(phase * M_PI * 8) / 4) * pow(1.25, i)) * (8 - i) / 36;
+ }
+ return ret;
+}
+static double random_double(void)
+{
+ return ((signed)(GB_random32() % 0x10001) - 0x8000) / (double) 0x8000;
+}
+
+static void render_jingle(GB_gameboy_t *gb, size_t count)
+{
+ const double frequencies[7] = {
+ 466.16, // Bb4
+ 587.33, // D5
+ 698.46, // F5
+ 830.61, // Ab5
+ 1046.50, // C6
+ 1244.51, // Eb6
+ 1567.98, // G6
+ };
+
+ assert(gb->apu_output.sample_callback);
+
+ if (gb->sgb->intro_animation < 0) {
+ GB_sample_t sample = {0, 0};
+ for (unsigned i = 0; i < count; i++) {
+ gb->apu_output.sample_callback(gb, &sample);
+ }
+ return;
+ }
+
+ if (gb->sgb->intro_animation >= INTRO_ANIMATION_LENGTH) return;
+
+ signed jingle_stage = (gb->sgb->intro_animation - 64) / 3;
+ double sweep_cutoff_ratio = 2000.0 * pow(2, gb->sgb->intro_animation / 20.0) / gb->apu_output.sample_rate;
+ double sweep_phase_shift = 1000.0 * pow(2, gb->sgb->intro_animation / 40.0) / gb->apu_output.sample_rate;
+ if (sweep_cutoff_ratio > 1) {
+ sweep_cutoff_ratio = 1;
+ }
+
+ GB_sample_t stereo;
+ for (unsigned i = 0; i < count; i++) {
+ double sample = 0;
+ for (signed f = 0; f < 7 && f < jingle_stage; f++) {
+ sample += fm_synth(gb->sgb_intro_jingle_phases[f]) *
+ (0.75 * pow(0.5, jingle_stage - f) + 0.25) / 5.0;
+ gb->sgb_intro_jingle_phases[f] += frequencies[f] / gb->apu_output.sample_rate;
+ }
+ if (gb->sgb->intro_animation > 100) {
+ sample *= pow((INTRO_ANIMATION_LENGTH - gb->sgb->intro_animation) / (INTRO_ANIMATION_LENGTH - 100.0), 3);
+ }
+
+ if (gb->sgb->intro_animation < 120) {
+ double next = fm_sweep(gb->sgb_intro_sweep_phase) * 0.3 + random_double() * 0.7;
+ gb->sgb_intro_sweep_phase += sweep_phase_shift;
+
+ gb->sgb_intro_sweep_previous_sample = next * (sweep_cutoff_ratio) +
+ gb->sgb_intro_sweep_previous_sample * (1 - sweep_cutoff_ratio);
+ sample += gb->sgb_intro_sweep_previous_sample * pow((120 - gb->sgb->intro_animation) / 120.0, 2) * 0.8;
+ }
+
+ stereo.left = stereo.right = sample * 0x7000;
+ gb->apu_output.sample_callback(gb, &stereo);
+ }
+
+ return;
+}
+
diff --git a/gb/Core/sgb.h b/gb/Core/sgb.h
new file mode 100644
index 0000000..df90253
--- /dev/null
+++ b/gb/Core/sgb.h
@@ -0,0 +1,66 @@
+#ifndef sgb_h
+#define sgb_h
+#include "gb_struct_def.h"
+#include
+#include
+
+typedef struct GB_sgb_s GB_sgb_t;
+
+#ifdef GB_INTERNAL
+struct GB_sgb_s {
+ uint8_t command[16 * 7];
+ uint16_t command_write_index;
+ bool ready_for_pulse;
+ bool ready_for_write;
+ bool ready_for_stop;
+ bool disable_commands;
+
+ /* Screen buffer */
+ uint8_t screen_buffer[160 * 144]; // Live image from the Game Boy
+ uint8_t effective_screen_buffer[160 * 144]; // Image actually rendered to the screen
+
+ /* Multiplayer Input */
+ uint8_t player_count, current_player;
+
+ /* Mask */
+ uint8_t mask_mode;
+
+ /* Data Transfer */
+ uint8_t vram_transfer_countdown, transfer_dest;
+
+ /* Border */
+ struct {
+ uint8_t tiles[0x100 * 8 * 8]; /* High nibble not used*/
+ union {
+ struct {
+ uint16_t map[32 * 32];
+ uint16_t palette[16 * 4];
+ };
+ uint16_t raw_data[0x440];
+ };
+ } border, pending_border;
+ uint8_t border_animation;
+
+ /* Colorization */
+ uint16_t effective_palettes[4 * 4];
+ uint16_t ram_palettes[4 * 512];
+ uint8_t attribute_map[20 * 18];
+ uint8_t attribute_files[0xFE0];
+
+ /* Intro */
+ int16_t intro_animation;
+
+ /* GB Header */
+ uint8_t received_header[0x54];
+
+ /* Multiplayer (cont) */
+ bool mlt_lock;
+};
+
+void GB_sgb_write(GB_gameboy_t *gb, uint8_t value);
+void GB_sgb_render(GB_gameboy_t *gb);
+void GB_sgb_load_default_data(GB_gameboy_t *gb);
+
+#endif
+
+#endif
diff --git a/gb/Core/sgb_animation_logo.inc b/gb/Core/sgb_animation_logo.inc
new file mode 100644
index 0000000..75075f4
--- /dev/null
+++ b/gb/Core/sgb_animation_logo.inc
@@ -0,0 +1,563 @@
+static uint8_t animation_logo[] = {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x1, 0x1, 0x3, 0x3, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x1, 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x3, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4,
+ 0x4, 0x4, 0x4, 0x3, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4,
+ 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE, 0xE, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4,
+ 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x3, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4,
+ 0x4, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x1,
+ 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x7, 0x0, 0x0, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1,
+ 0x0, 0x0, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4,
+ 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x3, 0x3, 0x1, 0x1, 0x0, 0x0, 0x5,
+ 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x7, 0x7, 0x7, 0x9, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x0, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0x1,
+ 0x1, 0xE, 0xE, 0xE, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x3, 0x3, 0x1, 0x0, 0x0, 0x5, 0x5,
+ 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0x7, 0x7, 0x7, 0x9, 0x1, 0x1, 0x1, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC,
+ 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0xC, 0xC, 0x0, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE,
+ 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x1, 0x1, 0x0, 0x1, 0x5, 0x5,
+ 0x5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7,
+ 0x7, 0x7, 0x9, 0x1, 0x1, 0x0, 0x0, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0x0, 0xC, 0xC,
+ 0xC, 0xC, 0x1, 0x1, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xE, 0xE, 0xE, 0xE, 0xE,
+ 0xE, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x5, 0x5, 0x5,
+ 0x5, 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7,
+ 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x1, 0x9, 0x9, 0x9, 0x0, 0x0, 0x0, 0x1, 0xC, 0xC,
+ 0xC, 0x1, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0x1, 0xD, 0x1, 0x1, 0x1,
+ 0x1, 0xE, 0xE, 0xE, 0xE, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4,
+ 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7,
+ 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0x0, 0xC, 0xC, 0xC,
+ 0xC, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0,
+ 0xE, 0xE, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x4,
+ 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5,
+ 0x1, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7,
+ 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC,
+ 0x1, 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0xF,
+ 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4,
+ 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x5, 0x5, 0x5, 0x1,
+ 0x1, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xC, 0xC, 0xC,
+ 0x1, 0x0, 0x1, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x1, 0xF,
+ 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1,
+ 0x0, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x7,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x1, 0xC, 0xC, 0xB, 0xB,
+ 0xC, 0xC, 0xC, 0xC, 0xC, 0xC, 0x1, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0xF, 0xF,
+ 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0,
+ 0x0, 0x0, 0x1, 0x6, 0x6, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x1, 0x1, 0x0, 0xC, 0xC, 0xB, 0xB, 0x1,
+ 0xC, 0xC, 0xC, 0xC, 0x1, 0x1, 0x1, 0x0, 0x1, 0xD, 0x1, 0x1, 0x0, 0x1, 0xF, 0xF,
+ 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x6, 0x6, 0x6, 0x1, 0x1, 0x0,
+ 0x0, 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x9, 0x9, 0x9, 0x9, 0x1, 0x0, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1,
+ 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x4, 0x1, 0x0, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0,
+ 0x0, 0x6, 0x6, 0x7, 0x7, 0x1, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0x1,
+ 0x0, 0x0, 0x0, 0x1, 0x9, 0x9, 0xA, 0x1, 0x1, 0x0, 0xC, 0xB, 0xB, 0xB, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0xF, 0xF, 0xF,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x6, 0x1, 0x0, 0x0,
+ 0x1, 0x6, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x0, 0x7, 0x7, 0x7, 0x8, 0x8, 0xA, 0x1,
+ 0x0, 0x0, 0x0, 0x9, 0x9, 0xA, 0xA, 0x1, 0x0, 0x1, 0xB, 0xB, 0xB, 0xD, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF,
+ 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x6, 0x6, 0x1, 0x1, 0x0, 0x1,
+ 0x6, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0xA, 0xA, 0x1,
+ 0x0, 0x0, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0x0, 0xB, 0xB, 0x1, 0xD, 0xD, 0xD, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF,
+ 0x1, 0x0, 0x0, 0x0, 0xF, 0xF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x5, 0x5, 0x5, 0x5,
+ 0x5, 0x1, 0x0, 0x0, 0x1, 0x4, 0x4, 0x1, 0x1, 0x0, 0x6, 0x6, 0x1, 0x0, 0x1, 0x6,
+ 0x1, 0x1, 0x7, 0x7, 0x7, 0x1, 0x0, 0x1, 0x7, 0x7, 0x8, 0x8, 0x1, 0x1, 0xA, 0xA,
+ 0x1, 0xA, 0xA, 0xA, 0xA, 0x1, 0x1, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0xD, 0xD, 0x1,
+ 0x0, 0x0, 0x0, 0x1, 0xD, 0xD, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF, 0xF,
+ 0x1, 0x0, 0x1, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5,
+ 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x1, 0x1, 0x0, 0x0, 0x6, 0x6, 0x6, 0x6, 0x6, 0x1,
+ 0x1, 0x0, 0x1, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x8, 0x8, 0x1, 0x0, 0x1, 0xA,
+ 0xA, 0xA, 0xA, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x0, 0x0, 0xD, 0xD, 0xD,
+ 0xD, 0xD, 0xD, 0xD, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0xF,
+ 0xF, 0xF, 0xF, 0xF, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x5,
+ 0x5, 0x5, 0x5, 0x5, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x6, 0x1, 0x1, 0x1,
+ 0x0, 0x0, 0x0, 0x1, 0x7, 0x7, 0x7, 0x1, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0,
+ 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0xB, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0xD,
+ 0xD, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0xF, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x8, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x8, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0,
+ 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x8, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2,
+ 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2,
+ 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0,
+ 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0,
+ 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0,
+ 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0,
+ 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0,
+ 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0,
+ 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0,
+ 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0,
+ 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0,
+ 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0,
+ 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0,
+ 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0,
+ 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0,
+ 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x0, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
+ 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1,
+ 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0,
+ 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1,
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1,
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
+};
+static const unsigned animation_logo_height = sizeof(animation_logo) / 160;
diff --git a/gb/Core/sgb_border.inc b/gb/Core/sgb_border.inc
new file mode 100644
index 0000000..d7d0a5c
--- /dev/null
+++ b/gb/Core/sgb_border.inc
@@ -0,0 +1,658 @@
+static const uint16_t palette[] = {
+ 0x0000, 0x0011, 0x18C6, 0x001A, 0x318C, 0x39CE, 0x5294, 0x5AD6,
+ 0x739C, 0x45A8, 0x4520, 0x18A5, 0x4631, 0x2033, 0x20EC, 0x18B7
+};
+
+static const uint16_t tilemap[] = {
+ 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001,
+ 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001,
+ 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001,
+ 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001,
+ 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002,
+ 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002,
+ 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002,
+ 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002, 0x1002,
+ 0x1001, 0x1003, 0x1004, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005,
+ 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005,
+ 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x1005,
+ 0x1005, 0x1005, 0x1005, 0x1005, 0x1005, 0x5004, 0x5003, 0x1001,
+ 0x1001, 0x1006, 0x1007, 0x1007, 0x1007, 0x1008, 0x1009, 0x100A,
+ 0x100B, 0x100C, 0x100D, 0x100E, 0x100F, 0x1010, 0x1011, 0x1012,
+ 0x1013, 0x1014, 0x1015, 0x100E, 0x1016, 0x1017, 0x1018, 0x1019,
+ 0x101A, 0x101B, 0x101C, 0x1007, 0x1007, 0x1007, 0x5006, 0x1001,
+ 0x1001, 0x101D, 0x101E, 0x101E, 0x101E, 0x101F, 0x1020, 0x1021,
+ 0x1022, 0x1023, 0x1024, 0x1025, 0x5024, 0x1026, 0x1025, 0x1025,
+ 0x1027, 0x1028, 0x1029, 0x102A, 0x102B, 0x102C, 0x102D, 0x102E,
+ 0x102F, 0x1030, 0x1031, 0x101E, 0x101E, 0x101E, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1034, 0x1035, 0x5034, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x8034, 0x1036, 0xC034, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0xC01D, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1037, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1032, 0x1038, 0x1001,
+ 0x1001, 0x101D, 0x1032, 0x1032, 0x1032, 0x1033, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000, 0x1000,
+ 0x1000, 0x1000, 0xC033, 0x1032, 0x1032, 0x1039, 0x103A, 0x1001,
+ 0x1001, 0x103B, 0x103C, 0x1032, 0x1032, 0xC03C, 0x103D, 0x103D,
+ 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D,
+ 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D, 0x103D,
+ 0x103D, 0x103D, 0x103E, 0x103F, 0x1040, 0x1041, 0x1001, 0x1001,
+ 0x1001, 0x1042, 0x1043, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044,
+ 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044,
+ 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044, 0x1044,
+ 0x1044, 0x1044, 0x1045, 0x1046, 0x1001, 0x1001, 0x1001, 0x1001,
+ 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1047, 0x1048, 0x1049,
+ 0x104A, 0x104B, 0x104C, 0x104D, 0x104E, 0x104F, 0x1050, 0x1051,
+ 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, 0x1058, 0x1059,
+ 0x105A, 0x105B, 0x105C, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001,
+ 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x105D, 0x105E, 0x105F,
+ 0x1060, 0x1061, 0x1062, 0x1063, 0x1064, 0x1065, 0x1066, 0x1067,
+ 0x1068, 0x1069, 0x106A, 0x106B, 0x106C, 0x106D, 0x106E, 0x106F,
+ 0x1070, 0x1071, 0x1072, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001,
+ 0x1001, 0x1001, 0x1001, 0x1001, 0x1001, 0x1073, 0x1074, 0x1075,
+ 0x1076, 0x1077, 0x1078, 0x1079, 0x107A, 0x107B, 0x107C, 0x107D,
+ 0x107E, 0x107F, 0x1080, 0x1081, 0x1082, 0x1083, 0x507A, 0x1084,
+ 0x1001, 0x1085, 0x507A, 0x1001, 0x1001, 0x1001, 0x1001, 0x1001,
+};
+
+const uint8_t tiles[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0xFF, 0x01, 0xFE, 0x06, 0xF9, 0x08, 0xF7,
+ 0x11, 0xEF, 0x22, 0xDB, 0x20, 0xDB, 0x40, 0xB7,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFE, 0x00, 0xF8, 0x00,
+ 0xF1, 0x00, 0xE6, 0x04, 0xE4, 0x00, 0xC8, 0x00,
+ 0x7F, 0x80, 0x80, 0x7F, 0x00, 0xFF, 0x7F, 0xFF,
+ 0x80, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0x80, 0x00, 0x00, 0x00, 0x7F, 0x00,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0xB7, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
+ 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
+ 0xC8, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
+ 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x03, 0xFF, 0x02, 0xFF,
+ 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF, 0x02, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00,
+ 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xDD, 0x00, 0xC9,
+ 0x14, 0xFF, 0x14, 0xFF, 0x14, 0xFF, 0x00, 0xC9,
+ 0x00, 0x00, 0x00, 0x00, 0xE3, 0x00, 0x36, 0x22,
+ 0x14, 0x00, 0x14, 0x00, 0x14, 0x00, 0x36, 0x22,
+ 0x00, 0xFF, 0x00, 0xFF, 0xC7, 0xDF, 0x01, 0xCF,
+ 0x11, 0xFF, 0x11, 0xFF, 0x11, 0xFF, 0x01, 0xCF,
+ 0x00, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x31, 0x20,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x31, 0x20,
+ 0x00, 0xFF, 0x00, 0xFF, 0xC2, 0xFF, 0x03, 0xFF,
+ 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFF, 0x02, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0xC2, 0x00, 0x03, 0x00,
+ 0x03, 0x01, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xFF, 0x18, 0xFF,
+ 0x08, 0x4E, 0x08, 0x4E, 0x09, 0x1F, 0x08, 0x1C,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00,
+ 0xB9, 0x10, 0xB9, 0xA1, 0xE9, 0xA0, 0xEB, 0x41,
+ 0x00, 0xFF, 0x00, 0xFF, 0x4F, 0xFF, 0x02, 0x1F,
+ 0x02, 0x4F, 0x02, 0x4F, 0xF2, 0xFF, 0x02, 0xE7,
+ 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0xE2, 0xA0,
+ 0xB2, 0xA0, 0xB2, 0x10, 0xF2, 0x00, 0x1A, 0x10,
+ 0x00, 0xFF, 0x00, 0xFF, 0xBC, 0xFD, 0x22, 0xFB,
+ 0x22, 0xFB, 0x3C, 0xFD, 0x24, 0xFF, 0x26, 0xF9,
+ 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x26, 0x00,
+ 0x26, 0x00, 0x3E, 0x00, 0x24, 0x00, 0x26, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x50, 0xFF, 0x49, 0xEF,
+ 0x49, 0xF0, 0x46, 0xFF, 0x49, 0xF0, 0x49, 0xEF,
+ 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x59, 0x00,
+ 0x4F, 0x06, 0x46, 0x00, 0x4F, 0x06, 0x59, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x88, 0xFF, 0x00, 0x72,
+ 0x00, 0xF2, 0x05, 0xFF, 0x00, 0xF8, 0x00, 0x78,
+ 0x00, 0x00, 0x00, 0x00, 0x88, 0x00, 0x8D, 0x08,
+ 0x0D, 0x05, 0x05, 0x00, 0x07, 0x05, 0x87, 0x02,
+ 0x00, 0xFF, 0x00, 0xFF, 0x8A, 0xFF, 0x02, 0x27,
+ 0x02, 0x27, 0x52, 0xFF, 0x02, 0x8F, 0x02, 0x8F,
+ 0x00, 0x00, 0x00, 0x00, 0x8A, 0x00, 0xDA, 0x88,
+ 0xDA, 0x50, 0x52, 0x00, 0x72, 0x50, 0x72, 0x20,
+ 0x00, 0xFF, 0x00, 0xFF, 0xFA, 0xFF, 0x22, 0xFF,
+ 0x22, 0xFF, 0x23, 0xFF, 0x22, 0xFF, 0x22, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x22, 0x00,
+ 0x22, 0x00, 0x23, 0x00, 0x22, 0x00, 0x22, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x20, 0xFF, 0x20, 0xFF,
+ 0x20, 0xFF, 0xE0, 0xFF, 0x20, 0xFF, 0x20, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x20, 0x00,
+ 0x20, 0x00, 0xE0, 0x00, 0x20, 0x00, 0x20, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x33, 0x37, 0x00, 0x77,
+ 0x80, 0xFF, 0x20, 0x27, 0x08, 0xFF, 0x00, 0x77,
+ 0x00, 0x00, 0x00, 0x00, 0xFB, 0x40, 0x88, 0x88,
+ 0x80, 0x00, 0xF8, 0x50, 0x08, 0x00, 0x88, 0x88,
+ 0x00, 0xFF, 0x00, 0xFF, 0xEF, 0xFF, 0x88, 0xFF,
+ 0x88, 0xFF, 0x8F, 0xFF, 0x88, 0xFF, 0x88, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0xEF, 0x00, 0x88, 0x00,
+ 0x88, 0x00, 0x8F, 0x00, 0x88, 0x00, 0x88, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0xF9, 0xFD, 0x80, 0xF9,
+ 0x84, 0xFF, 0xF4, 0xFF, 0x84, 0xFF, 0x80, 0xF9,
+ 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x86, 0x02,
+ 0x84, 0x00, 0xF4, 0x00, 0x84, 0x00, 0x86, 0x02,
+ 0x00, 0xFF, 0x00, 0xFF, 0xC0, 0xDF, 0x00, 0xCF,
+ 0x10, 0xFF, 0x10, 0xFF, 0x10, 0xFF, 0x00, 0xCF,
+ 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x30, 0x20,
+ 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x30, 0x20,
+ 0x00, 0xFF, 0x00, 0xFF, 0x30, 0x36, 0x00, 0x74,
+ 0x82, 0xFF, 0x22, 0x27, 0x0A, 0xFF, 0x00, 0x74,
+ 0x00, 0x00, 0x00, 0x00, 0xF9, 0x40, 0x8B, 0x89,
+ 0x82, 0x00, 0xFA, 0x50, 0x0A, 0x00, 0x8B, 0x89,
+ 0x00, 0xFF, 0x00, 0xFF, 0xE2, 0xEF, 0x02, 0xE7,
+ 0x0A, 0xFF, 0x0A, 0xFF, 0x0A, 0xFF, 0x00, 0xE4,
+ 0x00, 0x00, 0x00, 0x00, 0xF2, 0x00, 0x1A, 0x10,
+ 0x0A, 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x1B, 0x12,
+ 0x00, 0xFF, 0x00, 0xFF, 0x14, 0xFF, 0x16, 0xFF,
+ 0x14, 0xFC, 0x15, 0xFE, 0x14, 0xFF, 0x04, 0xCF,
+ 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x16, 0x00,
+ 0x17, 0x01, 0x15, 0x00, 0x14, 0x00, 0x34, 0x10,
+ 0x00, 0xFF, 0x00, 0xFF, 0x2F, 0xFF, 0x28, 0xFF,
+ 0x28, 0xFF, 0xA8, 0x7F, 0x28, 0x3F, 0x68, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x28, 0x00,
+ 0x28, 0x00, 0xA8, 0x00, 0xE8, 0x80, 0x68, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x3F,
+ 0x40, 0xFF, 0x40, 0xFF, 0x40, 0xFF, 0x00, 0x3F,
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0xC0, 0x80,
+ 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x80,
+ 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
+ 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF,
+ 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
+ 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x01, 0xFF,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0xC1, 0xDD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xC1, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xE1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x02, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x4A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x0A, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x22, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x82, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x60, 0x67, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xF8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x8F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xA2, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xA2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xF9, 0xFD, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xC0, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x60, 0x66, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xF9, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xE0, 0xEE, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xF1, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xC4, 0xDF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0xE4, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x2F, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0xFF,
+ 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF,
+ 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x3C, 0xFF,
+ 0x7E, 0xFF, 0xE7, 0xE7, 0xFF, 0x7E, 0xFF, 0x7E,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x7E,
+ 0x81, 0xC3, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00,
+ 0xFF, 0x7E, 0xFF, 0x3C, 0xFF, 0x00, 0x7E, 0x81,
+ 0x3C, 0xC3, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0xC3, 0x81,
+ 0x7E, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7, 0x00, 0xF7,
+ 0x00, 0xF7, 0x00, 0xED, 0x00, 0xED, 0x00, 0xED,
+ 0x09, 0x00, 0x09, 0x00, 0x09, 0x00, 0x09, 0x00,
+ 0x09, 0x00, 0x11, 0x02, 0x11, 0x02, 0x11, 0x02,
+ 0x00, 0xED, 0x00, 0xDB, 0x00, 0xDB, 0x00, 0xDB,
+ 0x00, 0xB7, 0x00, 0xB7, 0x00, 0x6F, 0x00, 0x6F,
+ 0x11, 0x02, 0x23, 0x04, 0x23, 0x04, 0x23, 0x04,
+ 0x47, 0x08, 0x47, 0x08, 0x8F, 0x10, 0x8F, 0x10,
+ 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xFB, 0x00, 0xF7,
+ 0x00, 0xEE, 0x00, 0xDD, 0x00, 0xBB, 0x00, 0x77,
+ 0x01, 0x00, 0x02, 0x00, 0x04, 0x00, 0x08, 0x00,
+ 0x10, 0x01, 0x21, 0x02, 0x43, 0x04, 0x87, 0x08,
+ 0x00, 0xDF, 0x00, 0xBF, 0x00, 0xBF, 0x00, 0x7F,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x1F, 0x20, 0x3F, 0x40, 0x3F, 0x40, 0x7F, 0x80,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xEF, 0x00, 0xB7,
+ 0x00, 0xB7, 0x00, 0xDB, 0x00, 0xDD, 0x00, 0xEE,
+ 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x88, 0x40,
+ 0x88, 0x40, 0xC4, 0x20, 0xC2, 0x20, 0xE1, 0x10,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFC,
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x03, 0x00, 0x1C, 0x00, 0xE0, 0x00,
+ 0x00, 0xFE, 0x00, 0xFD, 0x00, 0xF3, 0x00, 0xEF,
+ 0x00, 0x1C, 0x00, 0xF3, 0x00, 0xEF, 0x00, 0x1F,
+ 0x01, 0x00, 0x02, 0x00, 0x0C, 0x00, 0x10, 0x00,
+ 0xE0, 0x03, 0x03, 0x0C, 0x0F, 0x10, 0x1F, 0xE0,
+ 0x00, 0xEF, 0x00, 0xDF, 0x00, 0xBF, 0x00, 0x7F,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x0F, 0x10, 0x1F, 0x20, 0x3F, 0x40, 0x7F, 0x80,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0xF7, 0x00, 0xF9, 0x00, 0xFE, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xF0, 0x08, 0xF8, 0x06, 0xFE, 0x01, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0x80, 0x00, 0xFF, 0x00, 0x7F, 0x00, 0x80,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x7F, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x7F,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0x03, 0x00, 0xFF, 0x00, 0xFC, 0x00, 0x03,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFC, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0xFC,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0xFC, 0x00, 0xE3, 0x00, 0x1F, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0x03, 0x03, 0x1C, 0x1F, 0xE0, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x01, 0xFF, 0x01, 0xFD, 0x03, 0xFF, 0x03, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x01, 0xFE, 0x02, 0xFE, 0x02, 0xFC, 0x00,
+ 0x0E, 0xEE, 0x3F, 0xFF, 0x75, 0x71, 0xFB, 0xE7,
+ 0xE3, 0xCB, 0xC7, 0x9F, 0x07, 0x3E, 0x84, 0x7C,
+ 0xFB, 0x1B, 0xE6, 0x26, 0x8E, 0x82, 0x3E, 0x22,
+ 0x7C, 0x54, 0x7D, 0x25, 0xF9, 0x40, 0xFB, 0x01,
+ 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0xFF,
+ 0x00, 0x7F, 0x80, 0x4F, 0x31, 0x7F, 0x71, 0xFD,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x80,
+ 0xFF, 0x00, 0xFF, 0x30, 0xFF, 0xB1, 0xDE, 0x52,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0x7B, 0x87, 0xFF, 0x8E, 0xFE,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x84, 0xFA, 0x82, 0xF9, 0x88,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0xC1, 0xFD, 0xE3, 0x7B,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xC3, 0xBC, 0x24,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x01, 0xFF, 0x03, 0xFF, 0xE3, 0xFB, 0xF7, 0xBF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x01, 0xFE, 0x02, 0x7C, 0x64, 0xFC, 0xB4,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0x7F, 0x80, 0xFF, 0xA0, 0x2F, 0xF0, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x80, 0xFF, 0x80, 0xFF, 0x70, 0x8F, 0x80,
+ 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xFD, 0x00, 0xF7,
+ 0x00, 0xFF, 0x11, 0xEE, 0x11, 0xEE, 0x10, 0xEF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x03, 0xF8, 0x0F,
+ 0xF0, 0x0F, 0xE0, 0x1F, 0xE1, 0x1E, 0xE0, 0x1F,
+ 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xE7, 0x00, 0xFB,
+ 0xC4, 0x3B, 0x98, 0x03, 0x00, 0xEF, 0x80, 0x7F,
+ 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF8, 0x07, 0xFC,
+ 0x07, 0xF8, 0xEF, 0x74, 0xFF, 0x10, 0x7F, 0x80,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xEF, 0x00, 0xFF, 0x22, 0xDD, 0x06, 0xB9,
+ 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x07, 0xF0, 0x0F,
+ 0xF0, 0x1F, 0xE0, 0x1F, 0xC0, 0x3F, 0xC4, 0x7B,
+ 0x00, 0xFF, 0x00, 0xFF, 0x80, 0x7D, 0x02, 0xFD,
+ 0x02, 0xBD, 0x40, 0xBF, 0x40, 0xBF, 0x40, 0xBF,
+ 0xFF, 0x00, 0xFF, 0x00, 0x7E, 0x83, 0x7C, 0x83,
+ 0x7C, 0xC3, 0x7C, 0x83, 0x3C, 0xC3, 0x3C, 0xC3,
+ 0x00, 0xFF, 0x00, 0xFF, 0x10, 0xEF, 0x00, 0xFF,
+ 0x00, 0xF7, 0x00, 0xF7, 0x48, 0xB6, 0x48, 0xB7,
+ 0xFF, 0x00, 0xFF, 0x00, 0x0F, 0xF0, 0x0F, 0xF0,
+ 0x0F, 0xF8, 0x0F, 0xF8, 0x07, 0xF9, 0x06, 0xF9,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x02, 0xFC,
+ 0x02, 0x7D, 0x02, 0xFD, 0x02, 0xFD, 0x20, 0xDD,
+ 0xFF, 0x00, 0xFF, 0x00, 0xC1, 0x7E, 0x81, 0x7F,
+ 0x81, 0xFE, 0x01, 0xFE, 0x03, 0xFC, 0x03, 0xFE,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xBF, 0x40, 0xBF,
+ 0x47, 0xB8, 0x08, 0xF0, 0x08, 0xF7, 0x0F, 0xF0,
+ 0xFF, 0x00, 0xFF, 0x00, 0xC0, 0x7F, 0x80, 0x7F,
+ 0x80, 0x7F, 0x87, 0x7F, 0x87, 0x78, 0x80, 0x7F,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFB, 0x24, 0xCB,
+ 0xE4, 0x1B, 0x00, 0x1F, 0x00, 0xFF, 0x80, 0x3F,
+ 0xFF, 0x00, 0xFF, 0x00, 0x1C, 0xE7, 0x18, 0xF7,
+ 0x18, 0xE7, 0xF8, 0xE7, 0xF8, 0x07, 0x78, 0xC7,
+ 0x00, 0xFF, 0x00, 0xFF, 0x04, 0xF9, 0x00, 0xFF,
+ 0x71, 0x8E, 0x89, 0x06, 0x81, 0x7E, 0xE1, 0x1E,
+ 0xFF, 0x00, 0xFF, 0x00, 0x03, 0xFE, 0x01, 0xFE,
+ 0x00, 0xFF, 0x70, 0xFF, 0x70, 0x8F, 0x01, 0xFE,
+ 0x00, 0xFF, 0x00, 0xFF, 0x02, 0xF9, 0x00, 0xFF,
+ 0x00, 0xFF, 0x03, 0xFC, 0x06, 0xB9, 0x44, 0xBB,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFC, 0x07, 0xF0, 0x0F,
+ 0xE0, 0x1F, 0xC1, 0x3E, 0xC3, 0x7C, 0x87, 0x78,
+ 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF7, 0x00, 0xFD,
+ 0xC0, 0x3F, 0x11, 0x0E, 0x00, 0xFF, 0x08, 0xF7,
+ 0xFF, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x03, 0xFE,
+ 0x01, 0xFE, 0xE0, 0xFF, 0xF0, 0x0F, 0xF0, 0x0F,
+ 0x00, 0xFF, 0x00, 0xFF, 0x08, 0x77, 0x40, 0xBF,
+ 0x04, 0xBB, 0x00, 0xFE, 0x00, 0xDD, 0x00, 0x7F,
+ 0xFF, 0x00, 0xFF, 0x00, 0x87, 0xF8, 0x87, 0x78,
+ 0xC3, 0x7C, 0xC3, 0x3D, 0xE2, 0x3F, 0xE0, 0x9F,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFD, 0x06, 0xF9,
+ 0x0C, 0x73, 0x08, 0xF7, 0x10, 0xE7, 0x20, 0xCF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xC3, 0x3E, 0x83, 0x7C,
+ 0x87, 0xF8, 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xF7, 0x00, 0xFF,
+ 0x01, 0xDE, 0x06, 0xF8, 0x1C, 0xC3, 0x00, 0xF3,
+ 0xFF, 0x00, 0xFF, 0x00, 0xF8, 0x0F, 0xE0, 0x1F,
+ 0xE0, 0x3F, 0xC3, 0x3D, 0xE7, 0x38, 0xFF, 0x0C,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xDF, 0x00, 0xFF,
+ 0x00, 0xF7, 0x08, 0x77, 0x08, 0xF7, 0x08, 0xF7,
+ 0xFF, 0x00, 0xFF, 0x00, 0x3F, 0xE0, 0x0F, 0xF0,
+ 0x0F, 0xF8, 0x87, 0xF8, 0x87, 0x78, 0x07, 0xF8,
+ 0x03, 0xFF, 0x03, 0xFF, 0x01, 0xFF, 0x00, 0xFE,
+ 0x18, 0xDF, 0x1C, 0xFD, 0x0F, 0xEF, 0x07, 0xF7,
+ 0xFC, 0x00, 0xFE, 0x02, 0xFF, 0x01, 0xFF, 0x01,
+ 0xFF, 0x38, 0xF3, 0x12, 0xF9, 0x19, 0xFC, 0x0C,
+ 0x02, 0x79, 0x80, 0xFD, 0xC0, 0xDF, 0xF0, 0xFE,
+ 0x79, 0x3F, 0x19, 0xDB, 0x19, 0xFB, 0xF9, 0xF7,
+ 0xFF, 0x84, 0xFF, 0x82, 0x7F, 0x60, 0x9F, 0x91,
+ 0xEF, 0xA9, 0xF6, 0x34, 0xFE, 0x1C, 0x1F, 0x11,
+ 0x63, 0xEF, 0xF3, 0xEB, 0xC6, 0xCE, 0xEF, 0xDE,
+ 0x8C, 0x9C, 0xDE, 0xBD, 0x9C, 0x9D, 0xFF, 0xEF,
+ 0x9E, 0x02, 0xBC, 0xA4, 0x3D, 0x14, 0x7B, 0x4A,
+ 0x73, 0x21, 0xF7, 0x94, 0xF7, 0xF6, 0xFE, 0xEE,
+ 0x8D, 0xEC, 0x9E, 0x7D, 0x1C, 0x5B, 0x38, 0xFA,
+ 0x79, 0xF7, 0x71, 0x75, 0xF3, 0xF3, 0xEF, 0xCF,
+ 0xF3, 0x90, 0xF7, 0x14, 0xEF, 0xA8, 0xEF, 0x2D,
+ 0xCF, 0x41, 0x8E, 0x8A, 0x3C, 0x3C, 0x39, 0x19,
+ 0x67, 0xFF, 0xEF, 0xFE, 0xEC, 0xDC, 0xCF, 0xCF,
+ 0xDD, 0xDC, 0xDC, 0x9F, 0x2C, 0x2F, 0xD7, 0xC7,
+ 0xB9, 0x21, 0xBB, 0xAA, 0xB3, 0x81, 0x76, 0x76,
+ 0x77, 0x76, 0xE7, 0xA4, 0xD7, 0x44, 0xFB, 0xCB,
+ 0xB3, 0x37, 0x73, 0x72, 0xF4, 0xEC, 0xEF, 0xCD,
+ 0xCD, 0x09, 0x11, 0xF3, 0x29, 0xA7, 0xF1, 0xCF,
+ 0xCD, 0x49, 0xDF, 0xDE, 0xBF, 0xA5, 0x7F, 0x5D,
+ 0xF6, 0x32, 0xFE, 0x14, 0xFE, 0x70, 0xFF, 0xC1,
+ 0xF0, 0x77, 0xF0, 0x67, 0xE0, 0xCF, 0x80, 0x97,
+ 0xC8, 0xBB, 0x98, 0xBB, 0x90, 0xD3, 0xE8, 0xE7,
+ 0xDF, 0x58, 0xBF, 0x28, 0x7F, 0x50, 0x7F, 0x28,
+ 0xF7, 0x84, 0xFF, 0xDC, 0xEF, 0xA4, 0xDF, 0xC0,
+ 0x00, 0xFF, 0x04, 0xF3, 0x03, 0xF8, 0x00, 0xFF,
+ 0x08, 0xF7, 0x03, 0xFC, 0x00, 0xBF, 0x18, 0xC7,
+ 0xF0, 0x0F, 0xF8, 0x0F, 0xFE, 0x05, 0xFF, 0x00,
+ 0xE7, 0x18, 0xC0, 0x3F, 0xC0, 0x7F, 0xE0, 0x3F,
+ 0x00, 0xFF, 0x00, 0xFF, 0x08, 0xF6, 0x08, 0x77,
+ 0x08, 0xF5, 0x08, 0xF7, 0x10, 0xE7, 0x70, 0x87,
+ 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF9, 0x86, 0xF9,
+ 0x86, 0x7B, 0x0C, 0xF3, 0x08, 0xFF, 0x38, 0xCF,
+ 0x0A, 0xF1, 0x88, 0x77, 0x0E, 0xF1, 0x00, 0xFF,
+ 0x00, 0xFF, 0x7F, 0x80, 0x41, 0xBE, 0x81, 0x3E,
+ 0x84, 0x7F, 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x3E, 0xC1, 0x7E, 0x81, 0x7E, 0xC1,
+ 0x04, 0xFB, 0x04, 0xDB, 0x24, 0xDB, 0x20, 0xDF,
+ 0x20, 0xDF, 0x00, 0xFF, 0x08, 0xE7, 0x19, 0xE6,
+ 0x38, 0xC7, 0x38, 0xE7, 0x38, 0xC7, 0x18, 0xE7,
+ 0x18, 0xE7, 0x18, 0xE7, 0x10, 0xFF, 0x10, 0xEF,
+ 0x48, 0xB5, 0x80, 0x3F, 0x84, 0x7B, 0x80, 0x7F,
+ 0xA1, 0x5E, 0x21, 0x5E, 0x02, 0x7C, 0x02, 0x7D,
+ 0x46, 0xBB, 0x44, 0xFB, 0x40, 0xBF, 0x40, 0xBF,
+ 0xC0, 0x3F, 0xC1, 0xBE, 0xE1, 0x9F, 0xE3, 0x9C,
+ 0x60, 0x9D, 0x64, 0x99, 0x84, 0x3B, 0x84, 0x7B,
+ 0x04, 0x7B, 0x40, 0xBB, 0x41, 0xBA, 0x09, 0xF2,
+ 0x03, 0xFE, 0x43, 0xBE, 0x43, 0xFC, 0xC3, 0x3C,
+ 0xC7, 0xB8, 0x87, 0x7C, 0x86, 0x7D, 0x86, 0x7D,
+ 0x80, 0x7F, 0x80, 0x7F, 0x8F, 0x70, 0x10, 0xEF,
+ 0x10, 0xEF, 0x1F, 0xE0, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x0F, 0xF0, 0x0F, 0xF0,
+ 0x0F, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x48, 0xB7, 0x48, 0xB7, 0xC0, 0x3F, 0x01, 0xFE,
+ 0x01, 0xFE, 0x81, 0x2E, 0x50, 0xAF, 0x50, 0xAF,
+ 0x30, 0xCF, 0x30, 0xCF, 0xF0, 0x0F, 0xF0, 0x0F,
+ 0xF0, 0x0F, 0x70, 0xDF, 0x20, 0xDF, 0x60, 0x9F,
+ 0x06, 0xF8, 0x00, 0xFD, 0xF0, 0x0F, 0x00, 0x7E,
+ 0x00, 0xEE, 0xE2, 0x1C, 0x02, 0xFD, 0x0C, 0xF1,
+ 0x03, 0xFD, 0x03, 0xFE, 0xE1, 0x1E, 0xF1, 0x8F,
+ 0xF1, 0x1F, 0x01, 0xFF, 0x03, 0xFC, 0x07, 0xFA,
+ 0x08, 0xF3, 0x08, 0xF7, 0x08, 0xF7, 0x00, 0xFF,
+ 0x40, 0xBB, 0x01, 0xFE, 0x20, 0xDF, 0x18, 0xE7,
+ 0x87, 0x7C, 0x87, 0x78, 0x87, 0x78, 0x87, 0x78,
+ 0x87, 0x7C, 0xC0, 0x3F, 0xE0, 0x1F, 0xF0, 0x0F,
+ 0x08, 0xF7, 0x08, 0xF7, 0x01, 0xFE, 0x11, 0xEE,
+ 0x01, 0xDE, 0x82, 0x7C, 0x04, 0xF9, 0x38, 0xC3,
+ 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xE0, 0x1F,
+ 0xE1, 0x3E, 0x03, 0xFD, 0x07, 0xFA, 0x0F, 0xF4,
+ 0x10, 0x6F, 0x00, 0x7F, 0x01, 0x7E, 0x01, 0xFE,
+ 0x01, 0xFE, 0x11, 0xEE, 0x10, 0xEE, 0x12, 0xEC,
+ 0xE0, 0x9F, 0xF0, 0x8F, 0xF0, 0x8F, 0xF0, 0x0F,
+ 0xF0, 0x0F, 0xE1, 0x1E, 0xE1, 0x1F, 0xE1, 0x1F,
+ 0x40, 0x9F, 0x80, 0x3F, 0x80, 0x7F, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x7F, 0xA0, 0x7F, 0xC0, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x00, 0xFF, 0x00, 0xFB, 0x08, 0xF7, 0x00, 0xFF,
+ 0x01, 0xBE, 0x03, 0xFC, 0x00, 0x7F, 0x80, 0x7F,
+ 0xFE, 0x01, 0xFC, 0x07, 0xF0, 0x0F, 0xE0, 0x1F,
+ 0xC1, 0x7E, 0x80, 0x7F, 0x80, 0xFF, 0x00, 0xFF,
+ 0x08, 0xF7, 0x10, 0xE7, 0x60, 0x8F, 0xC0, 0x3F,
+ 0x80, 0x7F, 0xE0, 0x0F, 0x00, 0xEF, 0x00, 0xEF,
+ 0x0F, 0xF0, 0x1F, 0xE8, 0x3F, 0xD0, 0x7F, 0x80,
+ 0xFF, 0x00, 0x1F, 0xF0, 0x1F, 0xF0, 0x1F, 0xF0,
+ 0x02, 0xF8, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x04, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xD0, 0xC6, 0x00, 0x1F, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0xC9, 0xFF, 0xE0, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xE7, 0x86, 0x01, 0x39, 0x01, 0xFF, 0x03, 0xFF,
+ 0x03, 0xFF, 0x00, 0xFC, 0x00, 0xFE, 0x00, 0xFF,
+ 0xFF, 0x9E, 0xFF, 0xC7, 0xFE, 0x00, 0xFE, 0x02,
+ 0xFF, 0x03, 0xFF, 0x02, 0xFF, 0x01, 0xFF, 0x00,
+ 0xC3, 0xD3, 0xC0, 0xBC, 0x80, 0xBF, 0x00, 0x7F,
+ 0x80, 0x7F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF,
+ 0x7F, 0x6B, 0x7F, 0x03, 0xFF, 0xC0, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00,
+ 0xC7, 0x1B, 0x00, 0x7C, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x23, 0xFF, 0x83, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xC0, 0x1F, 0x00, 0x7F, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x20, 0xFF, 0x80, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x50, 0x4F, 0x00, 0x9F, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x40, 0xFF, 0x60, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x07, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xC7, 0x18, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x20, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x80, 0x7F, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xF7, 0x08, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x0C, 0xE1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x12, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x38, 0x87, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x8F, 0x30, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x40, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xF0, 0x07, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x03, 0xF0, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x0C, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x0E, 0xF1, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0x7F, 0x80, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00,
+ 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00
+};
diff --git a/gb/Core/sm83_cpu.c b/gb/Core/sm83_cpu.c
new file mode 100644
index 0000000..77248b5
--- /dev/null
+++ b/gb/Core/sm83_cpu.c
@@ -0,0 +1,1521 @@
+#include
+#include
+#include
+#include "gb.h"
+
+
+typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode);
+
+typedef enum {
+ /* Default behavior. If the CPU writes while another component reads, it reads the old value */
+ GB_CONFLICT_READ_OLD,
+ /* If the CPU writes while another component reads, it reads the new value */
+ GB_CONFLICT_READ_NEW,
+ /* If the CPU and another component write at the same time, the CPU's value "wins" */
+ GB_CONFLICT_WRITE_CPU,
+ /* Register specific values */
+ GB_CONFLICT_STAT_CGB,
+ GB_CONFLICT_STAT_DMG,
+ GB_CONFLICT_PALETTE_DMG,
+ GB_CONFLICT_PALETTE_CGB,
+} GB_conflict_t;
+
+/* Todo: How does double speed mode affect these? */
+static const GB_conflict_t cgb_conflict_map[0x80] = {
+ [GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
+ [GB_IO_LYC] = GB_CONFLICT_WRITE_CPU,
+ [GB_IO_STAT] = GB_CONFLICT_STAT_CGB,
+ [GB_IO_BGP] = GB_CONFLICT_PALETTE_CGB,
+ [GB_IO_OBP0] = GB_CONFLICT_PALETTE_CGB,
+ [GB_IO_OBP1] = GB_CONFLICT_PALETTE_CGB,
+
+
+ /* Todo: most values not verified, and probably differ between revisions */
+};
+
+/* Todo: verify on an MGB */
+static const GB_conflict_t dmg_conflict_map[0x80] = {
+ [GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
+ [GB_IO_LYC] = GB_CONFLICT_READ_OLD,
+ [GB_IO_LCDC] = GB_CONFLICT_READ_NEW,
+ [GB_IO_SCY] = GB_CONFLICT_READ_NEW,
+ [GB_IO_STAT] = GB_CONFLICT_STAT_DMG,
+
+ [GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG,
+ [GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG,
+ [GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG,
+
+ /* Todo: these were not verified at all */
+ [GB_IO_WY] = GB_CONFLICT_READ_NEW,
+ [GB_IO_WX] = GB_CONFLICT_READ_NEW,
+ [GB_IO_SCX] = GB_CONFLICT_READ_NEW,
+};
+
+/* Todo: Verify on an SGB1 */
+static const GB_conflict_t sgb_conflict_map[0x80] = {
+ [GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
+ [GB_IO_LYC] = GB_CONFLICT_READ_OLD,
+ [GB_IO_LCDC] = GB_CONFLICT_READ_NEW,
+ [GB_IO_SCY] = GB_CONFLICT_READ_NEW,
+ [GB_IO_STAT] = GB_CONFLICT_STAT_DMG,
+
+ [GB_IO_BGP] = GB_CONFLICT_READ_NEW,
+ [GB_IO_OBP0] = GB_CONFLICT_READ_NEW,
+ [GB_IO_OBP1] = GB_CONFLICT_READ_NEW,
+
+ /* Todo: these were not verified at all */
+ [GB_IO_WY] = GB_CONFLICT_READ_NEW,
+ [GB_IO_WX] = GB_CONFLICT_READ_NEW,
+ [GB_IO_SCX] = GB_CONFLICT_READ_NEW,
+};
+
+static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (gb->pending_cycles) {
+ GB_advance_cycles(gb, gb->pending_cycles);
+ }
+ uint8_t ret = GB_read_memory(gb, addr);
+ gb->pending_cycles = 4;
+ return ret;
+}
+
+static uint8_t cycle_read_inc_oam_bug(GB_gameboy_t *gb, uint16_t addr)
+{
+ if (gb->pending_cycles) {
+ GB_advance_cycles(gb, gb->pending_cycles);
+ }
+ GB_trigger_oam_bug_read_increase(gb, addr); /* Todo: test T-cycle timing */
+ uint8_t ret = GB_read_memory(gb, addr);
+ gb->pending_cycles = 4;
+ return ret;
+}
+
+/* A special case for IF during ISR, returns the old value of IF. */
+/* TODO: Verify the timing, it might be wrong in cases where, in the same M cycle, IF
+ is both read be the CPU, modified by the ISR, and modified by an actual interrupt.
+ If this timing proves incorrect, the ISR emulation must be updated so IF reads are
+ timed correctly. */
+static uint8_t cycle_write_if(GB_gameboy_t *gb, uint8_t value)
+{
+ assert(gb->pending_cycles);
+ GB_advance_cycles(gb, gb->pending_cycles);
+ uint8_t old = (gb->io_registers[GB_IO_IF]) & 0x1F;
+ GB_write_memory(gb, 0xFF00 + GB_IO_IF, value);
+ gb->pending_cycles = 4;
+ return old;
+}
+
+static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
+{
+ assert(gb->pending_cycles);
+ GB_conflict_t conflict = GB_CONFLICT_READ_OLD;
+ if ((addr & 0xFF80) == 0xFF00) {
+ const GB_conflict_t *map = NULL;
+ if (GB_is_cgb(gb)) {
+ map = cgb_conflict_map;
+ }
+ else if (GB_is_sgb(gb)) {
+ map = sgb_conflict_map;
+ }
+ else {
+ map = dmg_conflict_map;
+ }
+ conflict = map[addr & 0x7F];
+ }
+ switch (conflict) {
+ case GB_CONFLICT_READ_OLD:
+ GB_advance_cycles(gb, gb->pending_cycles);
+ GB_write_memory(gb, addr, value);
+ gb->pending_cycles = 4;
+ return;
+
+ case GB_CONFLICT_READ_NEW:
+ GB_advance_cycles(gb, gb->pending_cycles - 1);
+ GB_write_memory(gb, addr, value);
+ gb->pending_cycles = 5;
+ return;
+
+ case GB_CONFLICT_WRITE_CPU:
+ GB_advance_cycles(gb, gb->pending_cycles + 1);
+ GB_write_memory(gb, addr, value);
+ gb->pending_cycles = 3;
+ return;
+
+ /* The DMG STAT-write bug is basically the STAT register being read as FF for a single T-cycle */
+ case GB_CONFLICT_STAT_DMG:
+ GB_advance_cycles(gb, gb->pending_cycles);
+ /* State 7 is the edge between HBlank and OAM mode, and it behaves a bit weird.
+ The OAM interrupt seems to be blocked by HBlank interrupts in that case, despite
+ the timing not making much sense for that.
+ This is a hack to simulate this effect */
+ if (gb->display_state == 7 && (gb->io_registers[GB_IO_STAT] & 0x28) == 0x08) {
+ GB_write_memory(gb, addr, ~0x20);
+ }
+ else {
+ GB_write_memory(gb, addr, 0xFF);
+ }
+ GB_advance_cycles(gb, 1);
+ GB_write_memory(gb, addr, value);
+ gb->pending_cycles = 3;
+ return;
+
+ case GB_CONFLICT_STAT_CGB: {
+ /* Todo: Verify this with SCX adjustments */
+ /* The LYC bit behaves differently */
+ uint8_t old_value = GB_read_memory(gb, addr);
+ GB_advance_cycles(gb, gb->pending_cycles);
+ GB_write_memory(gb, addr, (old_value & 0x40) | (value & ~0x40));
+ GB_advance_cycles(gb, 1);
+ GB_write_memory(gb, addr, value);
+ gb->pending_cycles = 3;
+ return;
+ }
+
+ /* There is some "time travel" going on with these two values, as it appears
+ that there's some off-by-1-T-cycle timing issue in the PPU implementation.
+
+ This is should be accurate for every measureable scenario, though. */
+
+ case GB_CONFLICT_PALETTE_DMG: {
+ GB_advance_cycles(gb, gb->pending_cycles - 2);
+ uint8_t old_value = GB_read_memory(gb, addr);
+ GB_write_memory(gb, addr, value | old_value);
+ GB_advance_cycles(gb, 1);
+ GB_write_memory(gb, addr, value);
+ gb->pending_cycles = 5;
+ return;
+ }
+
+ case GB_CONFLICT_PALETTE_CGB: {
+ GB_advance_cycles(gb, gb->pending_cycles - 2);
+ GB_write_memory(gb, addr, value);
+ gb->pending_cycles = 6;
+ return;
+ }
+ }
+}
+
+static void cycle_no_access(GB_gameboy_t *gb)
+{
+ gb->pending_cycles += 4;
+}
+
+static void cycle_oam_bug(GB_gameboy_t *gb, uint8_t register_id)
+{
+ if (GB_is_cgb(gb)) {
+ /* Slight optimization */
+ gb->pending_cycles += 4;
+ return;
+ }
+ if (gb->pending_cycles) {
+ GB_advance_cycles(gb, gb->pending_cycles);
+ }
+ GB_trigger_oam_bug(gb, gb->registers[register_id]); /* Todo: test T-cycle timing */
+ gb->pending_cycles = 4;
+
+}
+
+static void flush_pending_cycles(GB_gameboy_t *gb)
+{
+ if (gb->pending_cycles) {
+ GB_advance_cycles(gb, gb->pending_cycles);
+ }
+ gb->pending_cycles = 0;
+}
+
+/* Todo: test if multi-byte opcodes trigger the OAM bug correctly */
+
+static void ill(GB_gameboy_t *gb, uint8_t opcode)
+{
+ GB_log(gb, "Illegal Opcode. Halting.\n");
+ gb->interrupt_enable = 0;
+ gb->halted = true;
+}
+
+static void nop(GB_gameboy_t *gb, uint8_t opcode)
+{
+}
+
+static void stop(GB_gameboy_t *gb, uint8_t opcode)
+{
+ if (gb->io_registers[GB_IO_KEY1] & 0x1) {
+ flush_pending_cycles(gb);
+ bool needs_alignment = false;
+
+ GB_advance_cycles(gb, 0x4);
+ /* Make sure we keep the CPU ticks aligned correctly when returning from double speed mode */
+ if (gb->double_speed_alignment & 7) {
+ GB_advance_cycles(gb, 0x4);
+ needs_alignment = true;
+ }
+
+ gb->cgb_double_speed ^= true;
+ gb->io_registers[GB_IO_KEY1] = 0;
+
+ for (unsigned i = 0x800; i--;) {
+ GB_advance_cycles(gb, 0x40);
+ }
+
+ if (!needs_alignment) {
+ GB_advance_cycles(gb, 0x4);
+ }
+
+ }
+ else {
+ GB_timing_sync(gb);
+ if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
+ /* HW Bug? When STOP is executed while a button is down, the CPU halts forever
+ yet the other hardware keeps running. */
+ gb->interrupt_enable = 0;
+ gb->halted = true;
+ }
+ else {
+ gb->stopped = true;
+ }
+ }
+
+ /* Todo: is PC being actually read? */
+ gb->pc++;
+}
+
+/* Operand naming conventions for functions:
+ r = 8-bit register
+ lr = low 8-bit register
+ hr = high 8-bit register
+ rr = 16-bit register
+ d8 = 8-bit imm
+ d16 = 16-bit imm
+ d.. = [..]
+ cc = condition code (z, nz, c, nc)
+ */
+
+static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ uint16_t value;
+ register_id = (opcode >> 4) + 1;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ value |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8;
+ gb->registers[register_id] = value;
+}
+
+static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ register_id = (opcode >> 4) + 1;
+ cycle_write(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8);
+}
+
+static void inc_rr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id = (opcode >> 4) + 1;
+ cycle_oam_bug(gb, register_id);
+ gb->registers[register_id]++;
+}
+
+static void inc_hr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ register_id = ((opcode >> 4) + 1) & 0x03;
+ gb->registers[register_id] += 0x100;
+ gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
+
+ if ((gb->registers[register_id] & 0x0F00) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((gb->registers[register_id] & 0xFF00) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+static void dec_hr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ register_id = ((opcode >> 4) + 1) & 0x03;
+ gb->registers[register_id] -= 0x100;
+ gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
+ gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
+
+ if ((gb->registers[register_id] & 0x0F00) == 0xF00) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((gb->registers[register_id] & 0xFF00) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ register_id = ((opcode >> 4) + 1) & 0x03;
+ gb->registers[register_id] &= 0xFF;
+ gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8;
+}
+
+static void rlca(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0;
+
+ gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1;
+ if (carry) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x0100;
+ }
+}
+
+static void rla(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool bit7 = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0;
+ bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+
+ gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1;
+ if (carry) {
+ gb->registers[GB_REGISTER_AF] |= 0x0100;
+ }
+ if (bit7) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode)
+{
+ /* Todo: Verify order is correct */
+ uint16_t addr;
+ addr = cycle_read_inc_oam_bug(gb, gb->pc++);
+ addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8;
+ cycle_write(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF);
+ cycle_write(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8);
+}
+
+static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t hl = gb->registers[GB_REGISTER_HL];
+ uint16_t rr;
+ uint8_t register_id;
+ cycle_no_access(gb);
+ register_id = (opcode >> 4) + 1;
+ rr = gb->registers[register_id];
+ gb->registers[GB_REGISTER_HL] = hl + rr;
+ gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG);
+
+ /* The meaning of the Half Carry flag is really hard to track -_- */
+ if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ( ((unsigned long) hl + (unsigned long) rr) & 0x10000) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ register_id = (opcode >> 4) + 1;
+ gb->registers[GB_REGISTER_AF] &= 0xFF;
+ gb->registers[GB_REGISTER_AF] |= cycle_read(gb, gb->registers[register_id]) << 8;
+}
+
+static void dec_rr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id = (opcode >> 4) + 1;
+ cycle_oam_bug(gb, register_id);
+ gb->registers[register_id]--;
+}
+
+static void inc_lr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ uint8_t value;
+ register_id = (opcode >> 4) + 1;
+
+ value = (gb->registers[register_id] & 0xFF) + 1;
+ gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value;
+
+ gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
+
+ if ((gb->registers[register_id] & 0x0F) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((gb->registers[register_id] & 0xFF) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+static void dec_lr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ uint8_t value;
+ register_id = (opcode >> 4) + 1;
+
+ value = (gb->registers[register_id] & 0xFF) - 1;
+ gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value;
+
+ gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
+ gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
+
+ if ((gb->registers[register_id] & 0x0F) == 0xF) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((gb->registers[register_id] & 0xFF) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ register_id = (opcode >> 4) + 1;
+ gb->registers[register_id] &= 0xFF00;
+ gb->registers[register_id] |= cycle_read_inc_oam_bug(gb, gb->pc++);
+}
+
+static void rrca(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0;
+
+ gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00;
+ if (carry) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x8000;
+ }
+}
+
+static void rra(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool bit1 = (gb->registers[GB_REGISTER_AF] & 0x0100) != 0;
+ bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+
+ gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00;
+ if (carry) {
+ gb->registers[GB_REGISTER_AF] |= 0x8000;
+ }
+ if (bit1) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void jr_r8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ /* Todo: Verify timing */
+ gb->pc += (int8_t)cycle_read_inc_oam_bug(gb, gb->pc) + 1;
+ cycle_no_access(gb);
+}
+
+static bool condition_code(GB_gameboy_t *gb, uint8_t opcode)
+{
+ switch ((opcode >> 3) & 0x3) {
+ case 0:
+ return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG);
+ case 1:
+ return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG);
+ case 2:
+ return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
+ case 3:
+ return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG);
+ }
+
+ return false;
+}
+
+static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ int8_t offset = cycle_read_inc_oam_bug(gb, gb->pc++);
+ if (condition_code(gb, opcode)) {
+ gb->pc += offset;
+ cycle_no_access(gb);
+ }
+}
+
+static void daa(GB_gameboy_t *gb, uint8_t opcode)
+{
+ int16_t result = gb->registers[GB_REGISTER_AF] >> 8;
+
+ gb->registers[GB_REGISTER_AF] &= ~(0xFF00 | GB_ZERO_FLAG);
+
+ if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) {
+ if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) {
+ result = (result - 0x06) & 0xFF;
+ }
+
+ if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) {
+ result -= 0x60;
+ }
+ }
+ else {
+ if ((gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) || (result & 0x0F) > 0x09) {
+ result += 0x06;
+ }
+
+ if ((gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) || result > 0x9F) {
+ result += 0x60;
+ }
+ }
+
+ if ((result & 0xFF) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+
+ if ((result & 0x100) == 0x100) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+
+ gb->registers[GB_REGISTER_AF] &= ~GB_HALF_CARRY_FLAG;
+ gb->registers[GB_REGISTER_AF] |= result << 8;
+}
+
+static void cpl(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_AF] ^= 0xFF00;
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG;
+}
+
+static void scf(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG);
+}
+
+static void ccf(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG;
+ gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG);
+}
+
+static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode)
+{
+ cycle_write(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8);
+}
+
+static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode)
+{
+ cycle_write(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8);
+}
+
+static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_AF] &= 0xFF;
+ gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]++) << 8;
+}
+
+static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_AF] &= 0xFF;
+ gb->registers[GB_REGISTER_AF] |= cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_HL]--) << 8;
+}
+
+static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value;
+ value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) + 1;
+ cycle_write(gb, gb->registers[GB_REGISTER_HL], value);
+
+ gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
+ if ((value & 0x0F) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((value & 0xFF) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value;
+ value = cycle_read(gb, gb->registers[GB_REGISTER_HL]) - 1;
+ cycle_write(gb, gb->registers[GB_REGISTER_HL], value);
+
+ gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG);
+ gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
+ if ((value & 0x0F) == 0x0F) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((value & 0xFF) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t data = cycle_read_inc_oam_bug(gb, gb->pc++);
+ cycle_write(gb, gb->registers[GB_REGISTER_HL], data);
+}
+
+static uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t src_register_id;
+ uint8_t src_low;
+ src_register_id = ((opcode >> 1) + 1) & 3;
+ src_low = opcode & 1;
+ if (src_register_id == GB_REGISTER_AF) {
+ if (src_low) {
+ return gb->registers[GB_REGISTER_AF] >> 8;
+ }
+ return cycle_read(gb, gb->registers[GB_REGISTER_HL]);
+ }
+ if (src_low) {
+ return gb->registers[src_register_id] & 0xFF;
+ }
+ return gb->registers[src_register_id] >> 8;
+}
+
+static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value)
+{
+ uint8_t src_register_id;
+ uint8_t src_low;
+ src_register_id = ((opcode >> 1) + 1) & 3;
+ src_low = opcode & 1;
+
+ if (src_register_id == GB_REGISTER_AF) {
+ if (src_low) {
+ gb->registers[GB_REGISTER_AF] &= 0xFF;
+ gb->registers[GB_REGISTER_AF] |= value << 8;
+ }
+ else {
+ cycle_write(gb, gb->registers[GB_REGISTER_HL], value);
+ }
+ }
+ else {
+ if (src_low) {
+ gb->registers[src_register_id] &= 0xFF00;
+ gb->registers[src_register_id] |= value;
+ }
+ else {
+ gb->registers[src_register_id] &= 0xFF;
+ gb->registers[src_register_id] |= value << 8;
+ }
+ }
+}
+
+/* The LD r,r instruction is extremely common and extremely simple. Decoding this opcode at runtime is a significent
+ performance hit, so we generate functions for every ld x,y couple (including [hl]) at compile time using macros. */
+
+/* Todo: It's probably wise to do the same to all opcodes. */
+
+#define LD_X_Y(x, y) \
+static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \
+{ \
+ gb->x = gb->y;\
+}
+
+#define LD_X_DHL(x) \
+static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \
+{ \
+gb->x = cycle_read(gb, gb->registers[GB_REGISTER_HL]); \
+}
+
+#define LD_DHL_Y(y) \
+static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \
+{ \
+cycle_write(gb, gb->registers[GB_REGISTER_HL], gb->y); \
+}
+
+LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a)
+LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a)
+LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a)
+LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a)
+LD_X_Y(h,b) LD_X_Y(h,c) LD_X_Y(h,d) LD_X_Y(h,e) LD_X_Y(h,l) LD_X_DHL(h) LD_X_Y(h,a)
+LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL(l) LD_X_Y(l,a)
+LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a)
+LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a)
+
+
+static void add_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = (a + value) << 8;
+ if ((uint8_t)(a + value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) + (value & 0xF) > 0x0F) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (((unsigned long) a) + ((unsigned long) value) > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a, carry;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+ gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8;
+
+ if ((uint8_t)(a + value + carry) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) + (value & 0xF) + carry > 0x0F) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG;
+ if (a == value) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) < (value & 0xF)) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (a < value) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a, carry;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+ gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG;
+
+ if ((uint8_t) (a - value - carry) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) < (value & 0xF) + carry) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void and_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG;
+ if ((a & value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = (a ^ value) << 8;
+ if ((a ^ value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void or_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = (a | value) << 8;
+ if ((a | value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = get_src_value(gb, opcode);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
+ if (a == value) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) < (value & 0xF)) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (a < value) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void halt(GB_gameboy_t *gb, uint8_t opcode)
+{
+ assert(gb->pending_cycles == 4);
+ gb->pending_cycles = 0;
+ GB_advance_cycles(gb, 1);
+ GB_advance_cycles(gb, 1);
+ GB_advance_cycles(gb, 1);
+ GB_advance_cycles(gb, 1);
+
+ gb->halted = true;
+ /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */
+ if (((gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0)) {
+ if (gb->ime) {
+ gb->halted = false;
+ gb->pc--;
+ }
+ else {
+ gb->halted = false;
+ gb->halt_bug = true;
+ }
+ }
+ gb->just_halted = true;
+}
+
+static void pop_rr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ register_id = ((opcode >> 4) + 1) & 3;
+ gb->registers[register_id] = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++);
+ gb->registers[register_id] |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8;
+ gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test.
+}
+
+static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++);
+ addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8);
+ if (condition_code(gb, opcode)) {
+ cycle_no_access(gb);
+ gb->pc = addr;
+ }
+}
+
+static void jp_a16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc);
+ addr |= (cycle_read_inc_oam_bug(gb, gb->pc + 1) << 8);
+ cycle_no_access(gb);
+ gb->pc = addr;
+
+}
+
+static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t call_addr = gb->pc - 1;
+ uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++);
+ addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8);
+ if (condition_code(gb, opcode)) {
+ cycle_oam_bug(gb, GB_REGISTER_SP);
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8);
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF);
+ gb->pc = addr;
+
+ GB_debugger_call_hook(gb, call_addr);
+ }
+}
+
+static void push_rr(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t register_id;
+ cycle_oam_bug(gb, GB_REGISTER_SP);
+ register_id = ((opcode >> 4) + 1) & 3;
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) >> 8);
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF);
+}
+
+static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = (a + value) << 8;
+ if ((uint8_t) (a + value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) + (value & 0xF) > 0x0F) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (((unsigned long) a) + ((unsigned long) value) > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a, carry;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+ gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8;
+
+ if (gb->registers[GB_REGISTER_AF] == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) + (value & 0xF) + carry > 0x0F) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG;
+ if (a == value) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) < (value & 0xF)) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (a < value) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a, carry;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+ gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG;
+
+ if ((uint8_t) (a - value - carry) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) < (value & 0xF) + carry) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG;
+ if ((a & value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = (a ^ value) << 8;
+ if ((a ^ value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] = (a | value) << 8;
+ if ((a | value) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value, a;
+ value = cycle_read_inc_oam_bug(gb, gb->pc++);
+ a = gb->registers[GB_REGISTER_AF] >> 8;
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG;
+ if (a == value) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ if ((a & 0xF) < (value & 0xF)) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+ if (a < value) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void rst(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t call_addr = gb->pc - 1;
+ cycle_oam_bug(gb, GB_REGISTER_SP);
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8);
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF);
+ gb->pc = opcode ^ 0xC7;
+ GB_debugger_call_hook(gb, call_addr);
+}
+
+static void ret(GB_gameboy_t *gb, uint8_t opcode)
+{
+ GB_debugger_ret_hook(gb);
+ gb->pc = cycle_read_inc_oam_bug(gb, gb->registers[GB_REGISTER_SP]++);
+ gb->pc |= cycle_read(gb, gb->registers[GB_REGISTER_SP]++) << 8;
+ cycle_no_access(gb);
+}
+
+static void reti(GB_gameboy_t *gb, uint8_t opcode)
+{
+ ret(gb, opcode);
+ gb->ime = true;
+}
+
+static void ret_cc(GB_gameboy_t *gb, uint8_t opcode)
+{
+ if (condition_code(gb, opcode)) {
+ cycle_no_access(gb);
+ ret(gb, opcode);
+ }
+ else {
+ cycle_no_access(gb);
+ }
+}
+
+static void call_a16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t call_addr = gb->pc - 1;
+ uint16_t addr = cycle_read_inc_oam_bug(gb, gb->pc++);
+ addr |= (cycle_read_inc_oam_bug(gb, gb->pc++) << 8);
+ cycle_oam_bug(gb, GB_REGISTER_SP);
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8);
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF);
+ gb->pc = addr;
+ GB_debugger_call_hook(gb, call_addr);
+}
+
+static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++);
+ cycle_write(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8);
+}
+
+static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_AF] &= 0xFF;
+ uint8_t temp = cycle_read_inc_oam_bug(gb, gb->pc++);
+ gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + temp) << 8;
+}
+
+static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode)
+{
+ cycle_write(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8);
+}
+
+static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_AF] &= 0xFF;
+ gb->registers[GB_REGISTER_AF] |= cycle_read(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8;
+}
+
+static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ int16_t offset;
+ uint16_t sp = gb->registers[GB_REGISTER_SP];
+ offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++);
+ cycle_no_access(gb);
+ cycle_no_access(gb);
+ gb->registers[GB_REGISTER_SP] += offset;
+
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+
+ /* A new instruction, a new meaning for Half Carry! */
+ if ((sp & 0xF) + (offset & 0xF) > 0xF) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((sp & 0xFF) + (offset & 0xFF) > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void jp_hl(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->pc = gb->registers[GB_REGISTER_HL];
+}
+
+static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t addr;
+ addr = cycle_read_inc_oam_bug(gb, gb->pc++);
+ addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8;
+ cycle_write(gb, addr, gb->registers[GB_REGISTER_AF] >> 8);
+}
+
+static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint16_t addr;
+ gb->registers[GB_REGISTER_AF] &= 0xFF;
+ addr = cycle_read_inc_oam_bug(gb, gb->pc++);
+ addr |= cycle_read_inc_oam_bug(gb, gb->pc++) << 8 ;
+ gb->registers[GB_REGISTER_AF] |= cycle_read(gb, addr) << 8;
+}
+
+static void di(GB_gameboy_t *gb, uint8_t opcode)
+{
+ /* DI is NOT delayed, not even on a CGB. Mooneye's di_timing-GS test fails on a CGB
+ for different reasons. */
+ gb->ime = false;
+}
+
+static void ei(GB_gameboy_t *gb, uint8_t opcode)
+{
+ /* ei is actually "disable interrupts for one instruction, then enable them". */
+ if (!gb->ime && !gb->ime_toggle) {
+ gb->ime_toggle = true;
+ }
+}
+
+static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode)
+{
+ int16_t offset;
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ offset = (int8_t) cycle_read_inc_oam_bug(gb, gb->pc++);
+ cycle_no_access(gb);
+ gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset;
+
+ if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) {
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ }
+
+ if ((gb->registers[GB_REGISTER_SP] & 0xFF) + (offset & 0xFF) > 0xFF) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+}
+
+static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode)
+{
+ gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL];
+ cycle_no_access(gb);
+}
+
+static void rlc_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool carry;
+ uint8_t value;
+ value = get_src_value(gb, opcode);
+ carry = (value & 0x80) != 0;
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ set_src_value(gb, opcode, (value << 1) | carry);
+ if (carry) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+ if (!(value << 1)) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void rrc_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool carry;
+ uint8_t value;
+ value = get_src_value(gb, opcode);
+ carry = (value & 0x01) != 0;
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ value = (value >> 1) | (carry << 7);
+ set_src_value(gb, opcode, value);
+ if (carry) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+ if (value == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void rl_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool carry;
+ uint8_t value;
+ bool bit7;
+ value = get_src_value(gb, opcode);
+ carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+ bit7 = (value & 0x80) != 0;
+
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ value = (value << 1) | carry;
+ set_src_value(gb, opcode, value);
+ if (bit7) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+ if (value == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void rr_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ bool carry;
+ uint8_t value;
+ bool bit1;
+
+ value = get_src_value(gb, opcode);
+ carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0;
+ bit1 = (value & 0x1) != 0;
+
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ value = (value >> 1) | (carry << 7);
+ set_src_value(gb, opcode, value);
+ if (bit1) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+ if (value == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void sla_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value;
+ bool carry;
+ value = get_src_value(gb, opcode);
+ carry = (value & 0x80) != 0;
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ set_src_value(gb, opcode, (value << 1));
+ if (carry) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+ if ((value & 0x7F) == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void sra_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t bit7;
+ uint8_t value;
+ value = get_src_value(gb, opcode);
+ bit7 = value & 0x80;
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ if (value & 1) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+ value = (value >> 1) | bit7;
+ set_src_value(gb, opcode, value);
+ if (value == 0) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void srl_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value;
+ value = get_src_value(gb, opcode);
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ set_src_value(gb, opcode, (value >> 1));
+ if (value & 1) {
+ gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG;
+ }
+ if (!(value >> 1)) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void swap_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value;
+ value = get_src_value(gb, opcode);
+ gb->registers[GB_REGISTER_AF] &= 0xFF00;
+ set_src_value(gb, opcode, (value >> 4) | (value << 4));
+ if (!value) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+}
+
+static void bit_r(GB_gameboy_t *gb, uint8_t opcode)
+{
+ uint8_t value;
+ uint8_t bit;
+ value = get_src_value(gb, opcode);
+ bit = 1 << ((opcode >> 3) & 7);
+ if ((opcode & 0xC0) == 0x40) { /* Bit */
+ gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG;
+ gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG;
+ if (!(bit & value)) {
+ gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG;
+ }
+ }
+ else if ((opcode & 0xC0) == 0x80) { /* res */
+ set_src_value(gb, opcode, value & ~bit) ;
+ }
+ else if ((opcode & 0xC0) == 0xC0) { /* set */
+ set_src_value(gb, opcode, value | bit) ;
+ }
+}
+
+static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode)
+{
+ opcode = cycle_read_inc_oam_bug(gb, gb->pc++);
+ switch (opcode >> 3) {
+ case 0:
+ rlc_r(gb, opcode);
+ break;
+ case 1:
+ rrc_r(gb, opcode);
+ break;
+ case 2:
+ rl_r(gb, opcode);
+ break;
+ case 3:
+ rr_r(gb, opcode);
+ break;
+ case 4:
+ sla_r(gb, opcode);
+ break;
+ case 5:
+ sra_r(gb, opcode);
+ break;
+ case 6:
+ swap_r(gb, opcode);
+ break;
+ case 7:
+ srl_r(gb, opcode);
+ break;
+ default:
+ bit_r(gb, opcode);
+ break;
+ }
+}
+
+static GB_opcode_t *opcodes[256] = {
+ /* X0 X1 X2 X3 X4 X5 X6 X7 */
+ /* X8 X9 Xa Xb Xc Xd Xe Xf */
+ nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */
+ ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca,
+ stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */
+ jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra,
+ jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */
+ jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl,
+ jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */
+ jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf,
+ nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */
+ ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a,
+ ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */
+ ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a,
+ ld_h_b, ld_h_c, ld_h_d, ld_h_e, nop, ld_h_l, ld_h_dhl, ld_h_a, /* 6X */
+ ld_l_b, ld_l_c, ld_l_d, ld_l_e, ld_l_h, nop, ld_l_dhl, ld_l_a,
+ ld_dhl_b, ld_dhl_c, ld_dhl_d, ld_dhl_e, ld_dhl_h, ld_dhl_l, halt, ld_dhl_a, /* 7X */
+ ld_a_b, ld_a_c, ld_a_d, ld_a_e, ld_a_h, ld_a_l, ld_a_dhl, nop,
+ add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */
+ adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r,
+ sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */
+ sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r,
+ and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */
+ xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r,
+ or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */
+ cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r,
+ ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */
+ ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst,
+ ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */
+ ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst,
+ ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */
+ add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst,
+ ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */
+ ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst,
+};
+void GB_cpu_run(GB_gameboy_t *gb)
+{
+ if (gb->hdma_on) {
+ GB_advance_cycles(gb, 4);
+ return;
+ }
+ if (gb->stopped) {
+ GB_timing_sync(gb);
+ GB_advance_cycles(gb, 4);
+ if ((gb->io_registers[GB_IO_JOYP] & 0xF) != 0xF) {
+ gb->stopped = false;
+ /* The CPU takes more time to wake up then the other components */
+ for (unsigned i = 0x800; i--;) {
+ GB_advance_cycles(gb, 0x40);
+ }
+ GB_advance_cycles(gb, 8);
+ }
+ return;
+ }
+
+ if ((gb->interrupt_enable & 0x10) && (gb->ime || gb->halted)) {
+ GB_timing_sync(gb);
+ }
+
+ if (gb->halted && !GB_is_cgb(gb) && !gb->just_halted) {
+ GB_advance_cycles(gb, 2);
+ }
+
+ uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F;
+
+ if (gb->halted) {
+ GB_advance_cycles(gb, (GB_is_cgb(gb) || gb->just_halted) ? 4 : 2);
+ }
+ gb->just_halted = false;
+
+ bool effecitve_ime = gb->ime;
+ if (gb->ime_toggle) {
+ gb->ime = !gb->ime;
+ gb->ime_toggle = false;
+ }
+
+ /* Wake up from HALT mode without calling interrupt code. */
+ if (gb->halted && !effecitve_ime && interrupt_queue) {
+ gb->halted = false;
+ }
+
+ /* Call interrupt */
+ else if (effecitve_ime && interrupt_queue) {
+ gb->halted = false;
+ uint16_t call_addr = gb->pc;
+
+ cycle_no_access(gb);
+ cycle_no_access(gb);
+ GB_trigger_oam_bug(gb, gb->registers[GB_REGISTER_SP]); /* Todo: test T-cycle timing */
+ cycle_no_access(gb);
+
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) >> 8);
+ interrupt_queue = gb->interrupt_enable;
+
+ if (gb->registers[GB_REGISTER_SP] == GB_IO_IF + 0xFF00 + 1) {
+ gb->registers[GB_REGISTER_SP]--;
+ interrupt_queue &= cycle_write_if(gb, (gb->pc) & 0xFF);
+ }
+ else {
+ cycle_write(gb, --gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF);
+ interrupt_queue &= (gb->io_registers[GB_IO_IF]) & 0x1F;
+ }
+
+ if (interrupt_queue) {
+ uint8_t interrupt_bit = 0;
+ while (!(interrupt_queue & 1)) {
+ interrupt_queue >>= 1;
+ interrupt_bit++;
+ }
+ gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit);
+ gb->pc = interrupt_bit * 8 + 0x40;
+ }
+ else {
+ gb->pc = 0;
+ }
+ gb->ime = false;
+ GB_debugger_call_hook(gb, call_addr);
+ }
+ /* Run mode */
+ else if(!gb->halted) {
+ gb->last_opcode_read = cycle_read_inc_oam_bug(gb, gb->pc++);
+ if (gb->halt_bug) {
+ gb->pc--;
+ gb->halt_bug = false;
+ }
+ opcodes[gb->last_opcode_read](gb, gb->last_opcode_read);
+ }
+
+ if (gb->hdma_starting) {
+ gb->hdma_starting = false;
+ gb->hdma_on = true;
+ gb->hdma_cycles = -8;
+ }
+ flush_pending_cycles(gb);
+}
diff --git a/gb/Core/sm83_cpu.h b/gb/Core/sm83_cpu.h
new file mode 100644
index 0000000..49fa80b
--- /dev/null
+++ b/gb/Core/sm83_cpu.h
@@ -0,0 +1,11 @@
+#ifndef sm83_cpu_h
+#define sm83_cpu_h
+#include "gb_struct_def.h"
+#include
+
+void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count);
+#ifdef GB_INTERNAL
+void GB_cpu_run(GB_gameboy_t *gb);
+#endif
+
+#endif /* sm83_cpu_h */
diff --git a/gb/Core/sm83_disassembler.c b/gb/Core/sm83_disassembler.c
new file mode 100644
index 0000000..96aec00
--- /dev/null
+++ b/gb/Core/sm83_disassembler.c
@@ -0,0 +1,788 @@
+#include
+#include
+#include "gb.h"
+
+
+typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc);
+
+static void ill(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, ".BYTE $%02x\n", opcode);
+ (*pc)++;
+}
+
+static void nop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "NOP\n");
+ (*pc)++;
+}
+
+static void stop(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint8_t next = GB_read_memory(gb, (*pc)++);
+ if (next) {
+ GB_log(gb, "CORRUPTED STOP (%02x)\n", next);
+ }
+ else {
+ GB_log(gb, "STOP\n");
+ }
+}
+
+static char *register_names[] = {"af", "bc", "de", "hl", "sp"};
+
+static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ uint16_t value;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+ value = GB_read_memory(gb, (*pc)++);
+ value |= GB_read_memory(gb, (*pc)++) << 8;
+ const char *symbol = GB_debugger_name_for_address(gb, value);
+ if (symbol) {
+ GB_log(gb, "LD %s, %s ; =$%04x\n", register_names[register_id], symbol, value);
+ }
+ else {
+ GB_log(gb, "LD %s, $%04x\n", register_names[register_id], value);
+ }
+}
+
+static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+ GB_log(gb, "LD [%s], a\n", register_names[register_id]);
+}
+
+static void inc_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+ GB_log(gb, "INC %s\n", register_names[register_id]);
+}
+
+static void inc_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ (*pc)++;
+ register_id = ((opcode >> 4) + 1) & 0x03;
+ GB_log(gb, "INC %c\n", register_names[register_id][0]);
+
+}
+static void dec_hr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ (*pc)++;
+ register_id = ((opcode >> 4) + 1) & 0x03;
+ GB_log(gb, "DEC %c\n", register_names[register_id][0]);
+}
+
+static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ (*pc)++;
+ register_id = ((opcode >> 4) + 1) & 0x03;
+ GB_log(gb, "LD %c, $%02x\n", register_names[register_id][0], GB_read_memory(gb, (*pc)++));
+}
+
+static void rlca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "RLCA\n");
+}
+
+static void rla(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "RLA\n");
+}
+
+static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc){
+ uint16_t addr;
+ (*pc)++;
+ addr = GB_read_memory(gb, (*pc)++);
+ addr |= GB_read_memory(gb, (*pc)++) << 8;
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_log(gb, "LD [%s], sp ; =$%04x\n", symbol, addr);
+ }
+ else {
+ GB_log(gb, "LD [$%04x], sp\n", addr);
+ }
+}
+
+static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ (*pc)++;
+ register_id = (opcode >> 4) + 1;
+ GB_log(gb, "ADD hl, %s\n", register_names[register_id]);
+}
+
+static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+ GB_log(gb, "LD a, [%s]\n", register_names[register_id]);
+}
+
+static void dec_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+ GB_log(gb, "DEC %s\n", register_names[register_id]);
+}
+
+static void inc_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+
+ GB_log(gb, "INC %c\n", register_names[register_id][1]);
+}
+static void dec_lr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+
+ GB_log(gb, "DEC %c\n", register_names[register_id][1]);
+}
+
+static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = (GB_read_memory(gb, (*pc)++) >> 4) + 1;
+
+ GB_log(gb, "LD %c, $%02x\n", register_names[register_id][1], GB_read_memory(gb, (*pc)++));
+}
+
+static void rrca(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "RRCA\n");
+ (*pc)++;
+}
+
+static void rra(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "RRA\n");
+ (*pc)++;
+}
+
+static void jr_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1;
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR %s ; =$%04x\n", symbol, addr);
+ }
+ else {
+ GB_attributed_log(gb, GB_LOG_UNDERLINE, "JR $%04x\n", addr);
+ }
+ (*pc)++;
+}
+
+static const char *condition_code(uint8_t opcode)
+{
+ switch ((opcode >> 3) & 0x3) {
+ case 0:
+ return "nz";
+ case 1:
+ return "z";
+ case 2:
+ return "nc";
+ case 3:
+ return "c";
+ }
+
+ return NULL;
+}
+
+static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = *pc + (int8_t) GB_read_memory(gb, (*pc)) + 1;
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr);
+ }
+ else {
+ GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JR %s, $%04x\n", condition_code(opcode), addr);
+ }
+ (*pc)++;
+}
+
+static void daa(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "DAA\n");
+ (*pc)++;
+}
+
+static void cpl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "CPL\n");
+ (*pc)++;
+}
+
+static void scf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "SCF\n");
+ (*pc)++;
+}
+
+static void ccf(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "CCF\n");
+ (*pc)++;
+}
+
+static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "LD [hli], a\n");
+ (*pc)++;
+}
+
+static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "LD [hld], a\n");
+ (*pc)++;
+}
+
+static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "LD a, [hli]\n");
+ (*pc)++;
+}
+
+static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "LD a, [hld]\n");
+ (*pc)++;
+}
+
+static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "INC [hl]\n");
+ (*pc)++;
+}
+
+static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ GB_log(gb, "DEC [hl]\n");
+ (*pc)++;
+}
+
+static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "LD [hl], $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static const char *get_src_name(uint8_t opcode)
+{
+ uint8_t src_register_id;
+ uint8_t src_low;
+ src_register_id = ((opcode >> 1) + 1) & 3;
+ src_low = (opcode & 1);
+ if (src_register_id == GB_REGISTER_AF) {
+ return src_low? "a": "[hl]";
+ }
+ if (src_low) {
+ return register_names[src_register_id] + 1;
+ }
+ static const char *high_register_names[] = {"a", "b", "d", "h"};
+ return high_register_names[src_register_id];
+}
+
+static const char *get_dst_name(uint8_t opcode)
+{
+ uint8_t dst_register_id;
+ uint8_t dst_low;
+ dst_register_id = ((opcode >> 4) + 1) & 3;
+ dst_low = opcode & 8;
+ if (dst_register_id == GB_REGISTER_AF) {
+ return dst_low? "a": "[hl]";
+ }
+ if (dst_low) {
+ return register_names[dst_register_id] + 1;
+ }
+ static const char *high_register_names[] = {"a", "b", "d", "h"};
+ return high_register_names[dst_register_id];
+}
+
+static void ld_r_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "LD %s, %s\n", get_dst_name(opcode), get_src_name(opcode));
+}
+
+static void add_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "ADD %s\n", get_src_name(opcode));
+}
+
+static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "ADC %s\n", get_src_name(opcode));
+}
+
+static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SUB %s\n", get_src_name(opcode));
+}
+
+static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SBC %s\n", get_src_name(opcode));
+}
+
+static void and_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "AND %s\n", get_src_name(opcode));
+}
+
+static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "XOR %s\n", get_src_name(opcode));
+}
+
+static void or_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "OR %s\n", get_src_name(opcode));
+}
+
+static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "CP %s\n", get_src_name(opcode));
+}
+
+static void halt(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "HALT\n");
+}
+
+static void ret_cc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "RET %s\n", condition_code(opcode));
+}
+
+static void pop_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3;
+ GB_log(gb, "POP %s\n", register_names[register_id]);
+}
+
+static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr);
+ }
+ else {
+ GB_attributed_log(gb, GB_LOG_DASHED_UNDERLINE, "JP %s, $%04x\n", condition_code(opcode), addr);
+ }
+ (*pc) += 2;
+}
+
+static void jp_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_log(gb, "JP %s ; =$%04x\n", symbol, addr);
+ }
+ else {
+ GB_log(gb, "JP $%04x\n", addr);
+ }
+ (*pc) += 2;
+}
+
+static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_log(gb, "CALL %s, %s ; =$%04x\n", condition_code(opcode), symbol, addr);
+ }
+ else {
+ GB_log(gb, "CALL %s, $%04x\n", condition_code(opcode), addr);
+ }
+ (*pc) += 2;
+}
+
+static void push_rr(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t register_id;
+ register_id = ((GB_read_memory(gb, (*pc)++) >> 4) + 1) & 3;
+ GB_log(gb, "PUSH %s\n", register_names[register_id]);
+}
+
+static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "ADD $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "ADC $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SUB $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SBC $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "AND $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "XOR $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "OR $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "CP $%02x\n", GB_read_memory(gb, (*pc)++));
+}
+
+static void rst(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "RST $%02x\n", opcode ^ 0xC7);
+
+}
+
+static void ret(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_attributed_log(gb, GB_LOG_UNDERLINE, "RET\n");
+}
+
+static void reti(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_attributed_log(gb, GB_LOG_UNDERLINE, "RETI\n");
+}
+
+static void call_a16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_log(gb, "CALL %s ; =$%04x\n", symbol, addr);
+ }
+ else {
+ GB_log(gb, "CALL $%04x\n", addr);
+ }
+ (*pc) += 2;
+}
+
+static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint8_t addr = GB_read_memory(gb, (*pc)++);
+ const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
+ if (symbol) {
+ GB_log(gb, "LDH [%s & $FF], a ; =$%02x\n", symbol, addr);
+ }
+ else {
+ GB_log(gb, "LDH [$%02x], a\n", addr);
+ }
+}
+
+static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint8_t addr = GB_read_memory(gb, (*pc)++);
+ const char *symbol = GB_debugger_name_for_address(gb, 0xff00 + addr);
+ if (symbol) {
+ GB_log(gb, "LDH a, [%s & $FF] ; =$%02x\n", symbol, addr);
+ }
+ else {
+ GB_log(gb, "LDH a, [$%02x]\n", addr);
+ }
+}
+
+static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "LDH [c], a\n");
+}
+
+static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "LDH a, [c]\n");
+}
+
+static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ int8_t temp = GB_read_memory(gb, (*pc)++);
+ GB_log(gb, "ADD SP, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp);
+}
+
+static void jp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "JP hl\n");
+}
+
+static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_log(gb, "LD [%s], a ; =$%04x\n", symbol, addr);
+ }
+ else {
+ GB_log(gb, "LD [$%04x], a\n", addr);
+ }
+ (*pc) += 2;
+}
+
+static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ uint16_t addr = GB_read_memory(gb, *pc) | (GB_read_memory(gb, *pc + 1) << 8);
+ const char *symbol = GB_debugger_name_for_address(gb, addr);
+ if (symbol) {
+ GB_log(gb, "LD a, [%s] ; =$%04x\n", symbol, addr);
+ }
+ else {
+ GB_log(gb, "LD a, [$%04x]\n", addr);
+ }
+ (*pc) += 2;
+}
+
+static void di(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "DI\n");
+}
+
+static void ei(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "EI\n");
+}
+
+static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ int8_t temp = GB_read_memory(gb, (*pc)++);
+ GB_log(gb, "LD hl, sp, %s$%02x\n", temp < 0? "-" : "", temp < 0? -temp : temp);
+}
+
+static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "LD sp, hl\n");
+}
+
+static void rlc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "RLC %s\n", get_src_name(opcode));
+}
+
+static void rrc_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "RRC %s\n", get_src_name(opcode));
+}
+
+static void rl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "RL %s\n", get_src_name(opcode));
+}
+
+static void rr_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "RR %s\n", get_src_name(opcode));
+}
+
+static void sla_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SLA %s\n", get_src_name(opcode));
+}
+
+static void sra_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SRA %s\n", get_src_name(opcode));
+}
+
+static void srl_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SRL %s\n", get_src_name(opcode));
+}
+
+static void swap_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ (*pc)++;
+ GB_log(gb, "SWAP %s\n", get_src_name(opcode));
+}
+
+static void bit_r(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ uint8_t bit;
+ (*pc)++;
+ bit = ((opcode >> 3) & 7);
+ if ((opcode & 0xC0) == 0x40) { /* Bit */
+ GB_log(gb, "BIT %s, %d\n", get_src_name(opcode), bit);
+ }
+ else if ((opcode & 0xC0) == 0x80) { /* res */
+ GB_log(gb, "RES %s, %d\n", get_src_name(opcode), bit);
+ }
+ else if ((opcode & 0xC0) == 0xC0) { /* set */
+ GB_log(gb, "SET %s, %d\n", get_src_name(opcode), bit);
+ }
+}
+
+static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode, uint16_t *pc)
+{
+ opcode = GB_read_memory(gb, ++*pc);
+ switch (opcode >> 3) {
+ case 0:
+ rlc_r(gb, opcode, pc);
+ break;
+ case 1:
+ rrc_r(gb, opcode, pc);
+ break;
+ case 2:
+ rl_r(gb, opcode, pc);
+ break;
+ case 3:
+ rr_r(gb, opcode, pc);
+ break;
+ case 4:
+ sla_r(gb, opcode, pc);
+ break;
+ case 5:
+ sra_r(gb, opcode, pc);
+ break;
+ case 6:
+ swap_r(gb, opcode, pc);
+ break;
+ case 7:
+ srl_r(gb, opcode, pc);
+ break;
+ default:
+ bit_r(gb, opcode, pc);
+ break;
+ }
+}
+
+static GB_opcode_t *opcodes[256] = {
+ /* X0 X1 X2 X3 X4 X5 X6 X7 */
+ /* X8 X9 Xa Xb Xc Xd Xe Xf */
+ nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */
+ ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca,
+ stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */
+ jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra,
+ jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */
+ jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl,
+ jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */
+ jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf,
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 4X */
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 5X */
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, /* 6X */
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, halt, ld_r_r, /* 7X */
+ ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r, ld_r_r,
+ add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */
+ adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r,
+ sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */
+ sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r,
+ and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */
+ xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r,
+ or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */
+ cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r,
+ ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */
+ ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst,
+ ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */
+ ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst,
+ ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */
+ add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst,
+ ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */
+ ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst,
+};
+
+
+
+void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count)
+{
+ const GB_bank_symbol_t *function_symbol = GB_debugger_find_symbol(gb, pc);
+
+ if (function_symbol && pc - function_symbol->addr > 0x1000) {
+ function_symbol = NULL;
+ }
+
+ if (function_symbol && pc != function_symbol->addr) {
+ GB_log(gb, "%s:\n", function_symbol->name);
+ }
+
+ uint16_t current_function = function_symbol? function_symbol->addr : 0;
+
+ while (count--) {
+ function_symbol = GB_debugger_find_symbol(gb, pc);
+ if (function_symbol && function_symbol->addr == pc) {
+ if (current_function != function_symbol->addr) {
+ GB_log(gb, "\n");
+ }
+ GB_log(gb, "%s:\n", function_symbol->name);
+ }
+ if (function_symbol) {
+ GB_log(gb, "%s%04x <+%03x>: ", pc == gb->pc? " ->": " ", pc, pc - function_symbol->addr);
+ }
+ else {
+ GB_log(gb, "%s%04x: ", pc == gb->pc? " ->": " ", pc);
+ }
+ uint8_t opcode = GB_read_memory(gb, pc);
+ opcodes[opcode](gb, opcode, &pc);
+ }
+}
diff --git a/gb/Core/symbol_hash.c b/gb/Core/symbol_hash.c
new file mode 100644
index 0000000..208e72d
--- /dev/null
+++ b/gb/Core/symbol_hash.c
@@ -0,0 +1,110 @@
+#include "gb.h"
+#include
+#include
+#include
+#include
+
+static size_t GB_map_find_symbol_index(GB_symbol_map_t *map, uint16_t addr)
+{
+ if (!map->symbols) {
+ return 0;
+ }
+ ssize_t min = 0;
+ ssize_t max = map->n_symbols;
+ while (min < max) {
+ size_t pivot = (min + max) / 2;
+ if (map->symbols[pivot].addr == addr) return pivot;
+ if (map->symbols[pivot].addr > addr) {
+ max = pivot;
+ }
+ else {
+ min = pivot + 1;
+ }
+ }
+ return (size_t) min;
+}
+
+GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name)
+{
+ size_t index = GB_map_find_symbol_index(map, addr);
+
+ if (index < map->n_symbols && map->symbols[index].addr == addr) return NULL;
+
+ map->symbols = realloc(map->symbols, (map->n_symbols + 1) * sizeof(map->symbols[0]));
+ memmove(&map->symbols[index + 1], &map->symbols[index], (map->n_symbols - index) * sizeof(map->symbols[0]));
+ map->symbols[index].addr = addr;
+ map->symbols[index].name = strdup(name);
+ map->n_symbols++;
+ return &map->symbols[index];
+}
+
+const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr)
+{
+ if (!map) return NULL;
+ size_t index = GB_map_find_symbol_index(map, addr);
+ if (index < map->n_symbols && map->symbols[index].addr != addr) {
+ index--;
+ }
+ if (index < map->n_symbols) {
+ return &map->symbols[index];
+ }
+ return NULL;
+}
+
+GB_symbol_map_t *GB_map_alloc(void)
+{
+ GB_symbol_map_t *map = malloc(sizeof(*map));
+ memset(map, 0, sizeof(*map));
+ return map;
+}
+
+void GB_map_free(GB_symbol_map_t *map)
+{
+ for (unsigned i = 0; i < map->n_symbols; i++) {
+ free(map->symbols[i].name);
+ }
+
+ if (map->symbols) {
+ free(map->symbols);
+ }
+
+ free(map);
+}
+
+static int hash_name(const char *name)
+{
+ int r = 0;
+ while (*name) {
+ r <<= 1;
+ if (r & 0x400) {
+ r ^= 0x401;
+ }
+ r += (unsigned char)*(name++);
+ }
+
+ return r & 0x3FF;
+}
+
+void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *bank_symbol)
+{
+ int hash = hash_name(bank_symbol->name);
+ GB_symbol_t *symbol = malloc(sizeof(*symbol));
+ symbol->name = bank_symbol->name;
+ symbol->addr = bank_symbol->addr;
+ symbol->bank = bank;
+ symbol->next = map->buckets[hash];
+ map->buckets[hash] = symbol;
+}
+
+const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name)
+{
+ int hash = hash_name(name);
+ GB_symbol_t *symbol = map->buckets[hash];
+
+ while (symbol) {
+ if (strcmp(symbol->name, name) == 0) return symbol;
+ symbol = symbol->next;
+ }
+
+ return NULL;
+}
diff --git a/gb/Core/symbol_hash.h b/gb/Core/symbol_hash.h
new file mode 100644
index 0000000..2a03c96
--- /dev/null
+++ b/gb/Core/symbol_hash.h
@@ -0,0 +1,38 @@
+#ifndef symbol_hash_h
+#define symbol_hash_h
+
+#include
+#include
+#include
+
+typedef struct {
+ char *name;
+ uint16_t addr;
+} GB_bank_symbol_t;
+
+typedef struct GB_symbol_s {
+ struct GB_symbol_s *next;
+ const char *name;
+ uint16_t bank;
+ uint16_t addr;
+} GB_symbol_t;
+
+typedef struct {
+ GB_bank_symbol_t *symbols;
+ size_t n_symbols;
+} GB_symbol_map_t;
+
+typedef struct {
+ GB_symbol_t *buckets[0x400];
+} GB_reversed_symbol_map_t;
+
+#ifdef GB_INTERNAL
+void GB_reversed_map_add_symbol(GB_reversed_symbol_map_t *map, uint16_t bank, GB_bank_symbol_t *symbol);
+const GB_symbol_t *GB_reversed_map_find_symbol(GB_reversed_symbol_map_t *map, const char *name);
+GB_bank_symbol_t *GB_map_add_symbol(GB_symbol_map_t *map, uint16_t addr, const char *name);
+const GB_bank_symbol_t *GB_map_find_symbol(GB_symbol_map_t *map, uint16_t addr);
+GB_symbol_map_t *GB_map_alloc(void);
+void GB_map_free(GB_symbol_map_t *map);
+#endif
+
+#endif /* symbol_hash_h */
diff --git a/gb/Core/timing.c b/gb/Core/timing.c
new file mode 100644
index 0000000..283558c
--- /dev/null
+++ b/gb/Core/timing.c
@@ -0,0 +1,294 @@
+#include "gb.h"
+#ifdef _WIN32
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0500
+#endif
+#include
+#else
+#include
+#endif
+
+static const unsigned GB_TAC_TRIGGER_BITS[] = {512, 8, 32, 128};
+
+#ifndef DISABLE_TIMEKEEPING
+static int64_t get_nanoseconds(void)
+{
+#ifndef _WIN32
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ return (now.tv_usec) * 1000 + now.tv_sec * 1000000000L;
+#else
+ FILETIME time;
+ GetSystemTimeAsFileTime(&time);
+ return (((int64_t)time.dwHighDateTime << 32) | time.dwLowDateTime) * 100L;
+#endif
+}
+
+static void nsleep(uint64_t nanoseconds)
+{
+#ifndef _WIN32
+ struct timespec sleep = {0, nanoseconds};
+ nanosleep(&sleep, NULL);
+#else
+ HANDLE timer;
+ LARGE_INTEGER time;
+ timer = CreateWaitableTimer(NULL, true, NULL);
+ time.QuadPart = -(nanoseconds / 100L);
+ SetWaitableTimer(timer, &time, 0, NULL, NULL, false);
+ WaitForSingleObject(timer, INFINITE);
+ CloseHandle(timer);
+#endif
+}
+
+bool GB_timing_sync_turbo(GB_gameboy_t *gb)
+{
+ if (!gb->turbo_dont_skip) {
+ int64_t nanoseconds = get_nanoseconds();
+ if (nanoseconds <= gb->last_sync + (1000000000LL * LCDC_PERIOD / GB_get_clock_rate(gb))) {
+ return true;
+ }
+ gb->last_sync = nanoseconds;
+ }
+ return false;
+}
+
+void GB_timing_sync(GB_gameboy_t *gb)
+{
+ if (gb->turbo) {
+ gb->cycles_since_last_sync = 0;
+ return;
+ }
+ /* Prevent syncing if not enough time has passed.*/
+ if (gb->cycles_since_last_sync < LCDC_PERIOD / 3) return;
+
+ uint64_t target_nanoseconds = gb->cycles_since_last_sync * 1000000000LL / 2 / GB_get_clock_rate(gb); /* / 2 because we use 8MHz units */
+ int64_t nanoseconds = get_nanoseconds();
+ int64_t time_to_sleep = target_nanoseconds + gb->last_sync - nanoseconds;
+ if (time_to_sleep > 0 && time_to_sleep < LCDC_PERIOD * 1000000000LL / GB_get_clock_rate(gb)) {
+ nsleep(time_to_sleep);
+ gb->last_sync += target_nanoseconds;
+ }
+ else {
+ gb->last_sync = nanoseconds;
+ }
+
+ gb->cycles_since_last_sync = 0;
+ if (gb->update_input_hint_callback) {
+ gb->update_input_hint_callback(gb);
+ }
+}
+#else
+
+bool GB_timing_sync_turbo(GB_gameboy_t *gb)
+{
+ return false;
+}
+
+void GB_timing_sync(GB_gameboy_t *gb)
+{
+}
+
+#endif
+static void GB_ir_run(GB_gameboy_t *gb)
+{
+ if (gb->ir_queue_length == 0) return;
+ if (gb->cycles_since_input_ir_change >= gb->ir_queue[0].delay) {
+ gb->cycles_since_input_ir_change -= gb->ir_queue[0].delay;
+ gb->infrared_input = gb->ir_queue[0].state;
+ gb->ir_queue_length--;
+ memmove(&gb->ir_queue[0], &gb->ir_queue[1], sizeof(gb->ir_queue[0]) * (gb->ir_queue_length));
+ }
+}
+
+static void advance_tima_state_machine(GB_gameboy_t *gb)
+{
+ if (gb->tima_reload_state == GB_TIMA_RELOADED) {
+ gb->tima_reload_state = GB_TIMA_RUNNING;
+ }
+ else if (gb->tima_reload_state == GB_TIMA_RELOADING) {
+ gb->io_registers[GB_IO_IF] |= 4;
+ gb->tima_reload_state = GB_TIMA_RELOADED;
+ }
+}
+
+static void increase_tima(GB_gameboy_t *gb)
+{
+ gb->io_registers[GB_IO_TIMA]++;
+ if (gb->io_registers[GB_IO_TIMA] == 0) {
+ gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA];
+ gb->tima_reload_state = GB_TIMA_RELOADING;
+ }
+}
+
+static void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value)
+{
+ /* TIMA increases when a specific high-bit becomes a low-bit. */
+ value &= INTERNAL_DIV_CYCLES - 1;
+ uint32_t triggers = gb->div_counter & ~value;
+ if ((gb->io_registers[GB_IO_TAC] & 4) && (triggers & GB_TAC_TRIGGER_BITS[gb->io_registers[GB_IO_TAC] & 3])) {
+ increase_tima(gb);
+ }
+
+ /* TODO: Can switching to double speed mode trigger an event? */
+ if (triggers & (gb->cgb_double_speed? 0x2000 : 0x1000)) {
+ GB_apu_run(gb);
+ GB_apu_div_event(gb);
+ }
+ gb->div_counter = value;
+}
+
+static void GB_timers_run(GB_gameboy_t *gb, uint8_t cycles)
+{
+ GB_STATE_MACHINE(gb, div, cycles, 1) {
+ GB_STATE(gb, div, 1);
+ GB_STATE(gb, div, 2);
+ GB_STATE(gb, div, 3);
+ }
+
+ GB_set_internal_div_counter(gb, 0);
+main:
+ GB_SLEEP(gb, div, 1, 3);
+ while (true) {
+ advance_tima_state_machine(gb);
+ GB_set_internal_div_counter(gb, gb->div_counter + 4);
+ gb->apu.apu_cycles += 4 << !gb->cgb_double_speed;
+ GB_SLEEP(gb, div, 2, 4);
+ }
+
+ /* Todo: This is ugly to allow compatibility with 0.11 save states. Fix me when breaking save compatibility */
+ {
+ div3:
+ /* Compensate for lack of prefetch emulation, as well as DIV's internal initial value */
+ GB_set_internal_div_counter(gb, 8);
+ goto main;
+ }
+}
+
+static void advance_serial(GB_gameboy_t *gb, uint8_t cycles)
+{
+ if (gb->serial_length == 0) {
+ gb->serial_cycles += cycles;
+ return;
+ }
+
+ while (cycles > gb->serial_length) {
+ advance_serial(gb, gb->serial_length);
+ cycles -= gb->serial_length;
+ }
+
+ uint16_t previous_serial_cycles = gb->serial_cycles;
+ gb->serial_cycles += cycles;
+ if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) {
+ gb->serial_count++;
+ if (gb->serial_count == 8) {
+ gb->serial_length = 0;
+ gb->serial_count = 0;
+ gb->io_registers[GB_IO_SC] &= ~0x80;
+ gb->io_registers[GB_IO_IF] |= 8;
+ }
+
+ gb->io_registers[GB_IO_SB] <<= 1;
+
+ if (gb->serial_transfer_bit_end_callback) {
+ gb->io_registers[GB_IO_SB] |= gb->serial_transfer_bit_end_callback(gb);
+ }
+ else {
+ gb->io_registers[GB_IO_SB] |= 1;
+ }
+
+ if (gb->serial_length) {
+ /* Still more bits to send */
+ if (gb->serial_transfer_bit_start_callback) {
+ gb->serial_transfer_bit_start_callback(gb, gb->io_registers[GB_IO_SB] & 0x80);
+ }
+ }
+
+ }
+ return;
+
+}
+
+void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
+{
+ // Affected by speed boost
+ gb->dma_cycles += cycles;
+
+ if (!gb->stopped) {
+ GB_timers_run(gb, cycles);
+ advance_serial(gb, cycles); // TODO: Verify what happens in STOP mode
+ }
+
+ gb->debugger_ticks += cycles;
+
+ if (!gb->cgb_double_speed) {
+ cycles <<= 1;
+ }
+
+ // Not affected by speed boost
+ gb->double_speed_alignment += cycles;
+ gb->hdma_cycles += cycles;
+ gb->apu_output.sample_cycles += cycles;
+ gb->cycles_since_ir_change += cycles;
+ gb->cycles_since_input_ir_change += cycles;
+ gb->cycles_since_last_sync += cycles;
+ gb->cycles_since_run += cycles;
+ if (!gb->stopped) { // TODO: Verify what happens in STOP mode
+ GB_dma_run(gb);
+ GB_hdma_run(gb);
+ }
+ GB_apu_run(gb);
+ GB_display_run(gb, cycles);
+ GB_ir_run(gb);
+}
+
+/*
+ This glitch is based on the expected results of mooneye-gb rapid_toggle test.
+ This glitch happens because how TIMA is increased, see GB_set_internal_div_counter.
+ According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented.
+*/
+void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac)
+{
+ /* Glitch only happens when old_tac is enabled. */
+ if (!(old_tac & 4)) return;
+
+ unsigned old_clocks = GB_TAC_TRIGGER_BITS[old_tac & 3];
+ unsigned new_clocks = GB_TAC_TRIGGER_BITS[new_tac & 3];
+
+ /* The bit used for overflow testing must have been 1 */
+ if (gb->div_counter & old_clocks) {
+ /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */
+ if (!(new_tac & 4) || gb->div_counter & new_clocks) {
+ increase_tima(gb);
+ }
+ }
+}
+
+void GB_rtc_run(GB_gameboy_t *gb)
+{
+ if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */
+ time_t current_time = time(NULL);
+ while (gb->last_rtc_second < current_time) {
+ gb->last_rtc_second++;
+ if (++gb->rtc_real.seconds == 60)
+ {
+ gb->rtc_real.seconds = 0;
+ if (++gb->rtc_real.minutes == 60)
+ {
+ gb->rtc_real.minutes = 0;
+ if (++gb->rtc_real.hours == 24)
+ {
+ gb->rtc_real.hours = 0;
+ if (++gb->rtc_real.days == 0)
+ {
+ if (gb->rtc_real.high & 1) /* Bit 8 of days*/
+ {
+ gb->rtc_real.high |= 0x80; /* Overflow bit */
+ }
+ gb->rtc_real.high ^= 1;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/gb/Core/timing.h b/gb/Core/timing.h
new file mode 100644
index 0000000..02ca54c
--- /dev/null
+++ b/gb/Core/timing.h
@@ -0,0 +1,44 @@
+#ifndef timing_h
+#define timing_h
+#include "gb_struct_def.h"
+
+#ifdef GB_INTERNAL
+void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles);
+void GB_rtc_run(GB_gameboy_t *gb);
+void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac);
+bool GB_timing_sync_turbo(GB_gameboy_t *gb); /* Returns true if should skip frame */
+void GB_timing_sync(GB_gameboy_t *gb);
+
+enum {
+ GB_TIMA_RUNNING = 0,
+ GB_TIMA_RELOADING = 1,
+ GB_TIMA_RELOADED = 2
+};
+
+#define GB_HALT_VALUE (0xFFFF)
+
+#define GB_SLEEP(gb, unit, state, cycles) do {\
+ (gb)->unit##_cycles -= (cycles) * __state_machine_divisor; \
+ if ((gb)->unit##_cycles <= 0) {\
+ (gb)->unit##_state = state;\
+ return;\
+ unit##state:; \
+ }\
+} while (0)
+
+#define GB_HALT(gb, unit) (gb)->unit##_cycles = GB_HALT_VALUE
+
+#define GB_STATE_MACHINE(gb, unit, cycles, divisor) \
+static const int __state_machine_divisor = divisor;\
+(gb)->unit##_cycles += cycles; \
+if ((gb)->unit##_cycles <= 0 || (gb)->unit##_cycles == GB_HALT_VALUE) {\
+ return;\
+}\
+switch ((gb)->unit##_state)
+#endif
+
+#define GB_STATE(gb, unit, state) case state: goto unit##state
+
+#define GB_UNIT(unit) int32_t unit##_cycles, unit##_state
+
+#endif /* timing_h */
diff --git a/gb/LICENSE b/gb/LICENSE
new file mode 100644
index 0000000..94966be
--- /dev/null
+++ b/gb/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2015-2019 Lior Halphon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/gb/README b/gb/README
new file mode 100644
index 0000000..fa08dc0
--- /dev/null
+++ b/gb/README
@@ -0,0 +1,5 @@
+Core: 2019-10-22 master snapshot
+* has issues with SGB2 audio desynchronization
+
+Core-new: 2019-11-05 master snapshot
+* Game Boy inputs are not working
diff --git a/heuristics/bs-memory.cpp b/heuristics/bs-memory.cpp
new file mode 100644
index 0000000..ffb255c
--- /dev/null
+++ b/heuristics/bs-memory.cpp
@@ -0,0 +1,33 @@
+namespace Heuristics {
+
+struct BSMemory {
+ BSMemory(vector& data, string location);
+ explicit operator bool() const;
+ auto manifest() const -> string;
+
+private:
+ vector& data;
+ string location;
+};
+
+BSMemory::BSMemory(vector& data, string location) : data(data), location(location) {
+}
+
+BSMemory::operator bool() const {
+ return data.size() >= 0x8000;
+}
+
+auto BSMemory::manifest() const -> string {
+ if(!operator bool()) return {};
+
+ string output;
+ output.append("game\n");
+ output.append(" sha256: ", Hash::SHA256(data).digest(), "\n");
+ output.append(" label: ", Location::prefix(location), "\n");
+ output.append(" name: ", Location::prefix(location), "\n");
+ output.append(" board\n");
+ output.append(Memory{}.type("Flash").size(data.size()).content("Program").text());
+ return output;
+}
+
+}
diff --git a/heuristics/game-boy.cpp b/heuristics/game-boy.cpp
new file mode 100644
index 0000000..411a1cc
--- /dev/null
+++ b/heuristics/game-boy.cpp
@@ -0,0 +1,298 @@
+namespace Heuristics {
+
+struct GameBoy {
+ GameBoy(vector& data, string location);
+ explicit operator bool() const;
+ auto manifest() const -> string;
+
+private:
+ auto read(uint offset) const -> uint8_t { return data[headerAddress + offset]; }
+
+ vector& data;
+ string location;
+ uint headerAddress = 0;
+};
+
+GameBoy::GameBoy(vector& data, string location) : data(data), location(location) {
+ headerAddress = data.size() < 0x8000 ? data.size() : data.size() - 0x8000;
+ if(read(0x0104) == 0xce && read(0x0105) == 0xed && read(0x0106) == 0x66 && read(0x0107) == 0x66
+ && read(0x0108) == 0xcc && read(0x0109) == 0x0d && read(0x0147) >= 0x0b && read(0x0147) <= 0x0d
+ ) { //MMM01 stores header at bottom of data[]
+ } else { //all other mappers store header at top of data[]
+ headerAddress = 0;
+ }
+}
+
+GameBoy::operator bool() const {
+ return data.size() >= 0x4000;
+}
+
+auto GameBoy::manifest() const -> string {
+ if(!operator bool()) return {};
+
+ bool black = (read(0x0143) & 0xc0) == 0x80; //cartridge works in DMG+CGB mode
+ bool clear = (read(0x0143) & 0xc0) == 0xc0; //cartridge works in CGB mode only
+
+ bool ram = false;
+ bool battery = false;
+ bool eeprom = false;
+ bool flash = false;
+ bool rtc = false;
+ bool accelerometer = false;
+ bool rumble = false;
+
+ uint romSize = 0;
+ uint ramSize = 0;
+ uint eepromSize = 0;
+ uint flashSize = 0;
+ uint rtcSize = 0;
+
+ string mapper = "MBC0";
+
+ switch(read(0x0147)) {
+
+ case 0x00:
+ mapper = "MBC0";
+ break;
+
+ case 0x01:
+ mapper = "MBC1";
+ break;
+
+ case 0x02:
+ mapper = "MBC1";
+ ram = true;
+ break;
+
+ case 0x03:
+ mapper = "MBC1";
+ battery = true;
+ ram = true;
+ break;
+
+ case 0x05:
+ mapper = "MBC2";
+ ram = true;
+ break;
+
+ case 0x06:
+ mapper = "MBC2";
+ battery = true;
+ ram = true;
+ break;
+
+ case 0x08:
+ mapper = "MBC0";
+ ram = true;
+ break;
+
+ case 0x09:
+ mapper = "MBC0";
+ battery = true;
+ ram = true;
+ break;
+
+ case 0x0b:
+ mapper = "MMM01";
+ break;
+
+ case 0x0c:
+ mapper = "MMM01";
+ ram = true;
+ break;
+
+ case 0x0d:
+ mapper = "MMM01";
+ battery = true;
+ ram = true;
+ break;
+
+ case 0x0f:
+ mapper = "MBC3";
+ battery = true;
+ rtc = true;
+ break;
+
+ case 0x10:
+ mapper = "MBC3";
+ battery = true;
+ ram = true;
+ rtc = true;
+ break;
+
+ case 0x11:
+ mapper = "MBC3";
+ break;
+
+ case 0x12:
+ mapper = "MBC3";
+ ram = true;
+ break;
+
+ case 0x13:
+ mapper = "MBC3";
+ battery = true;
+ ram = true;
+ break;
+
+ case 0x19:
+ mapper = "MBC5";
+ break;
+
+ case 0x1a:
+ mapper = "MBC5";
+ ram = true;
+ break;
+
+ case 0x1b:
+ mapper = "MBC5";
+ battery = true;
+ ram = true;
+ break;
+
+ case 0x1c:
+ mapper = "MBC5";
+ rumble = true;
+ break;
+
+ case 0x1d:
+ mapper = "MBC5";
+ ram = true;
+ rumble = true;
+ break;
+
+ case 0x1e:
+ mapper = "MBC5";
+ battery = true;
+ ram = true;
+ rumble = true;
+ break;
+
+ case 0x20:
+ mapper = "MBC6";
+ flash = true;
+ battery = true;
+ ram = true;
+ break;
+
+ case 0x22:
+ mapper = "MBC7";
+ battery = true;
+ eeprom = true;
+ accelerometer = true;
+ rumble = true;
+ break;
+
+ case 0xfc:
+ mapper = "CAMERA";
+ break;
+
+ case 0xfd:
+ mapper = "TAMA";
+ battery = true;
+ ram = true;
+ rtc = true;
+ break;
+
+ case 0xfe:
+ mapper = "HuC3";
+ break;
+
+ case 0xff:
+ mapper = "HuC1";
+ battery = true;
+ ram = true;
+ break;
+
+ }
+
+ //Game Boy: title = $0134-0143
+ //Game Boy Color (early games): title = $0134-0142; model = $0143
+ //Game Boy Color (later games): title = $0134-013e; serial = $013f-0142; model = $0143
+ string title;
+ for(uint n : range(black || clear ? 15 : 16)) {
+ char byte = read(0x0134 + n);
+ if(byte < 0x20 || byte > 0x7e) byte = ' ';
+ title.append(byte);
+ }
+
+ string serial = title.slice(-4);
+ if(!black && !clear) serial = "";
+ for(auto& byte : serial) {
+ if(byte >= 'A' && byte <= 'Z') continue;
+ //invalid serial
+ serial = "";
+ break;
+ }
+ title.trimRight(serial, 1L); //remove the serial from the title, if it exists
+ title.strip(); //remove any excess whitespace from the title
+
+ switch(read(0x0148)) { default:
+ case 0x00: romSize = 2 * 16 * 1024; break;
+ case 0x01: romSize = 4 * 16 * 1024; break;
+ case 0x02: romSize = 8 * 16 * 1024; break;
+ case 0x03: romSize = 16 * 16 * 1024; break;
+ case 0x04: romSize = 32 * 16 * 1024; break;
+ case 0x05: romSize = 64 * 16 * 1024; break;
+ case 0x06: romSize = 128 * 16 * 1024; break;
+ case 0x07: romSize = 256 * 16 * 1024; break;
+ case 0x52: romSize = 72 * 16 * 1024; break;
+ case 0x53: romSize = 80 * 16 * 1024; break;
+ case 0x54: romSize = 96 * 16 * 1024; break;
+ }
+
+ switch(read(0x0149)) { default:
+ case 0x00: ramSize = 0 * 1024; break;
+ case 0x01: ramSize = 2 * 1024; break;
+ case 0x02: ramSize = 8 * 1024; break;
+ case 0x03: ramSize = 32 * 1024; break;
+ }
+
+ if(mapper == "MBC2" && ram) ramSize = 256;
+ if(mapper == "MBC6" && ram) ramSize = 32 * 1024;
+ if(mapper == "TAMA" && ram) ramSize = 32;
+
+ if(mapper == "MBC6" && flash) flashSize = 1024 * 1024;
+
+ //Game Boy header does not specify EEPROM size: detect via game title instead
+ //Command Master: EEPROM = 512 bytes
+ //Kirby Tilt 'n' Tumble: EEPROM = 256 bytes
+ //Korokoro Kirby: EEPROM = 256 bytes
+ if(mapper == "MBC7" && eeprom) {
+ eepromSize = 256; //fallback guess; supported values are 128, 256, 512
+ if(title == "CMASTER" && serial == "KCEJ") eepromSize = 512;
+ if(title == "KIRBY TNT" && serial == "KTNE") eepromSize = 256;
+ if(title == "KORO2 KIRBY" && serial == "KKKJ") eepromSize = 256;
+ }
+
+ if(mapper == "MBC3" && rtc) rtcSize = 13;
+ if(mapper == "TAMA" && rtc) rtcSize = 21;
+
+ string output;
+ output.append("game\n");
+ output.append(" sha256: ", Hash::SHA256(data).digest(), "\n");
+ output.append(" label: ", Location::prefix(location), "\n");
+ output.append(" name: ", Location::prefix(location), "\n");
+ output.append(" title: ", title, "\n");
+if(serial)
+ output.append(" serial: ", serial, "\n");
+ output.append(" board: ", mapper, "\n");
+ output.append(Memory{}.type("ROM").size(data.size()).content("Program").text());
+if(ram && ramSize && battery)
+ output.append(Memory{}.type("RAM").size(ramSize).content("Save").text());
+if(ram && ramSize && !battery)
+ output.append(Memory{}.type("RAM").size(ramSize).content("Save").isVolatile().text());
+if(eeprom && eepromSize)
+ output.append(Memory{}.type("EEPROM").size(eepromSize).content("Save").text());
+if(flash && flashSize)
+ output.append(Memory{}.type("Flash").size(flashSize).content("Download").text());
+if(rtc && rtcSize)
+ output.append(Memory{}.type("RTC").size(rtcSize).content("Time").text());
+if(accelerometer)
+ output.append(" accelerometer\n");
+if(rumble)
+ output.append(" rumble\n");
+ return output;
+}
+
+}
diff --git a/heuristics/heuristics.cpp b/heuristics/heuristics.cpp
new file mode 100644
index 0000000..c564ac9
--- /dev/null
+++ b/heuristics/heuristics.cpp
@@ -0,0 +1,34 @@
+namespace Heuristics {
+
+auto Memory::text() const -> string {
+ string output;
+ output.append(" memory\n");
+ output.append(" type: ", _type, "\n");
+ output.append(" size: 0x", hex(_size), "\n");
+ output.append(" content: ", _content, "\n");
+if(_manufacturer)
+ output.append(" manufacturer: ", _manufacturer, "\n");
+if(_architecture)
+ output.append(" architecture: ", _architecture, "\n");
+if(_identifier)
+ output.append(" identifier: ", _identifier, "\n");
+if(_volatile)
+ output.append(" volatile\n");
+ return output;
+}
+
+auto Oscillator::text() const -> string {
+ string output;
+ output.append(" oscillator\n");
+ output.append(" frequency: ", _frequency, "\n");
+ return output;
+}
+
+auto Slot::text() const -> string {
+ string output;
+ output.append(" slot\n");
+ output.append(" type: ", _type, "\n");
+ return output;
+}
+
+}
diff --git a/heuristics/heuristics.hpp b/heuristics/heuristics.hpp
new file mode 100644
index 0000000..a65dafa
--- /dev/null
+++ b/heuristics/heuristics.hpp
@@ -0,0 +1,37 @@
+namespace Heuristics {
+
+struct Memory {
+ auto& type(string type) { _type = type; return *this; }
+ auto& size(natural size) { _size = size; return *this; }
+ auto& content(string content) { _content = content; return *this; }
+ auto& manufacturer(string manufacturer) { _manufacturer = manufacturer; return *this; }
+ auto& architecture(string architecture) { _architecture = architecture; return *this; }
+ auto& identifier(string identifier) { _identifier = identifier; return *this; }
+ auto& isVolatile() { _volatile = true; return *this; }
+ auto text() const -> string;
+
+ string _type;
+ boolean _battery;
+ natural _size;
+ string _content;
+ string _manufacturer;
+ string _architecture;
+ string _identifier;
+ boolean _volatile;
+};
+
+struct Oscillator {
+ auto& frequency(natural frequency) { _frequency = frequency; return *this; }
+ auto text() const -> string;
+
+ natural _frequency;
+};
+
+struct Slot {
+ auto& type(string type) { _type = type; return *this; }
+ auto text() const -> string;
+
+ string _type;
+};
+
+}
diff --git a/heuristics/sufami-turbo.cpp b/heuristics/sufami-turbo.cpp
new file mode 100644
index 0000000..5e4ea2d
--- /dev/null
+++ b/heuristics/sufami-turbo.cpp
@@ -0,0 +1,39 @@
+namespace Heuristics {
+
+struct SufamiTurbo {
+ SufamiTurbo(vector& data, string location);
+ explicit operator bool() const;
+
+ auto manifest() const -> string;
+
+private:
+ vector& data;
+ string location;
+};
+
+SufamiTurbo::SufamiTurbo(vector& data, string location) : data(data), location(location) {
+}
+
+SufamiTurbo::operator bool() const {
+ return data.size() >= 0x20000;
+}
+
+auto SufamiTurbo::manifest() const -> string {
+ if(!operator bool()) return {};
+
+ uint romSize = data[0x36] * 0x20000; //128KB
+ uint ramSize = data[0x37] * 0x800; // 2KB
+
+ string output;
+ output.append("game\n");
+ output.append(" sha256: ", Hash::SHA256(data).digest(), "\n");
+ output.append(" label: ", Location::prefix(location), "\n");
+ output.append(" name: ", Location::prefix(location), "\n");
+ output.append(" board\n");
+ output.append(Memory{}.type("ROM").size(data.size()).content("Program").text());
+if(ramSize)
+ output.append(Memory{}.type("RAM").size(ramSize).content("Save").text());
+ return output;
+}
+
+}
diff --git a/heuristics/super-famicom.cpp b/heuristics/super-famicom.cpp
new file mode 100644
index 0000000..674196e
--- /dev/null
+++ b/heuristics/super-famicom.cpp
@@ -0,0 +1,605 @@
+namespace Heuristics {
+
+struct SuperFamicom {
+ SuperFamicom(vector& data, string location);
+ explicit operator bool() const;
+
+ auto manifest() const -> string;
+ auto region() const -> string;
+ auto videoRegion() const -> string;
+ auto revision() const -> string;
+ auto board() const -> string;
+ auto title() const -> string;
+ auto serial() const -> string;
+ auto romSize() const -> uint;
+ auto programRomSize() const -> uint;
+ auto dataRomSize() const -> uint;
+ auto expansionRomSize() const -> uint;
+ auto firmwareRomSize() const -> uint;
+ auto ramSize() const -> uint;
+ auto expansionRamSize() const -> uint;
+ auto nonVolatile() const -> bool;
+
+private:
+ auto size() const -> uint { return data.size(); }
+ auto scoreHeader(uint address) -> uint;
+ auto firmwareARM() const -> string;
+ auto firmwareEXNEC() const -> string;
+ auto firmwareGB() const -> string;
+ auto firmwareHITACHI() const -> string;
+ auto firmwareNEC() const -> string;
+
+ vector& data;
+ string location;
+ uint headerAddress = 0;
+};
+
+SuperFamicom::SuperFamicom(vector& data, string location) : data(data), location(location) {
+ if((size() & 0x7fff) == 512) {
+ //remove header if present
+ memory::move(&data[0], &data[512], size() - 512);
+ data.resize(size() - 512);
+ }
+
+ if(size() < 0x8000) return; //ignore images too small to be valid
+
+ uint LoROM = scoreHeader( 0x7fb0);
+ uint HiROM = scoreHeader( 0xffb0);
+ uint ExLoROM = scoreHeader(0x407fb0);
+ uint ExHiROM = scoreHeader(0x40ffb0);
+ if(ExLoROM) ExLoROM += 4;
+ if(ExHiROM) ExHiROM += 4;
+
+ if(LoROM >= HiROM && LoROM >= ExLoROM && LoROM >= ExHiROM) headerAddress = 0x7fb0;
+ else if(HiROM >= ExLoROM && HiROM >= ExHiROM) headerAddress = 0xffb0;
+ else if(ExLoROM >= ExHiROM) headerAddress = 0x407fb0;
+ else headerAddress = 0x40ffb0;
+}
+
+SuperFamicom::operator bool() const {
+ return headerAddress;
+}
+
+auto SuperFamicom::manifest() const -> string {
+ if(!operator bool()) return {};
+
+ string output;
+ output.append("game\n");
+ output.append(" sha256: ", Hash::SHA256(data).digest(), "\n");
+ output.append(" label: ", Location::prefix(location), "\n");
+ output.append(" name: ", Location::prefix(location), "\n");
+ output.append(" title: ", title(), "\n");
+ output.append(" region: ", region(), "\n");
+ output.append(" revision: ", revision(), "\n");
+ output.append(" board: ", board(), "\n");
+
+ auto board = this->board().trimRight("#A", 1L).split("-");
+
+ if(auto size = romSize()) {
+ if(board(0) == "SPC7110" && size > 0x100000) {
+ output.append(Memory{}.type("ROM").size(0x100000).content("Program").text());
+ output.append(Memory{}.type("ROM").size(size - 0x100000).content("Data").text());
+ } else if(board(0) == "EXSPC7110" && size == 0x700000) {
+ //Tengai Maykou Zero (fan translation)
+ output.append(Memory{}.type("ROM").size(0x100000).content("Program").text());
+ output.append(Memory{}.type("ROM").size(0x500000).content("Data").text());
+ output.append(Memory{}.type("ROM").size(0x100000).content("Expansion").text());
+ } else {
+ output.append(Memory{}.type("ROM").size(size).content("Program").text());
+ }
+ }
+
+ if(auto size = ramSize()) {
+ output.append(Memory{}.type("RAM").size(size).content("Save").text());
+ }
+
+ if(auto size = expansionRamSize()) {
+ output.append(Memory{}.type("RAM").size(size).content("Save").text());
+ }
+
+ if(0) {
+ } else if(board(0) == "ARM") {
+ output.append(Memory{}.type("ROM").size(0x20000).content("Program").manufacturer("SETA").architecture("ARM6").identifier(firmwareARM()).text());
+ output.append(Memory{}.type("ROM").size( 0x8000).content("Data" ).manufacturer("SETA").architecture("ARM6").identifier(firmwareARM()).text());
+ output.append(Memory{}.type("RAM").size( 0x4000).content("Data" ).manufacturer("SETA").architecture("ARM6").identifier(firmwareARM()).isVolatile().text());
+ output.append(Oscillator{}.frequency(21'440'000).text());
+ } else if(board(0) == "BS" && board(1) == "MCC") {
+ output.append(Memory{}.type("RAM").size(0x80000).content("Download").text());
+ } else if(board(0) == "EXNEC") {
+ output.append(Memory{}.type("ROM").size(0xc000).content("Program").manufacturer("NEC").architecture("uPD96050").identifier(firmwareEXNEC()).text());
+ output.append(Memory{}.type("ROM").size(0x1000).content("Data" ).manufacturer("NEC").architecture("uPD96050").identifier(firmwareEXNEC()).text());
+ output.append(Memory{}.type("RAM").size(0x1000).content("Data" ).manufacturer("NEC").architecture("uPD96050").identifier(firmwareEXNEC()).text());
+ output.append(Oscillator{}.frequency(firmwareEXNEC() == "ST010" ? 11'000'000 : 15'000'000).text());
+ } else if(board(0) == "GB") {
+ output.append(Memory{}.type("ROM").size(0x100).content("Boot").manufacturer("Nintendo").architecture("LR35902").identifier(firmwareGB()).text());
+ if(firmwareGB() == "SGB2")
+ output.append(Oscillator{}.frequency(20'971'520).text());
+ } else if(board(0) == "GSU") {
+ //todo: MARIO CHIP 1 uses CPU oscillator
+ output.append(Oscillator{}.frequency(21'440'000).text());
+ } else if(board(0) == "HITACHI") {
+ output.append(Memory{}.type("ROM").size(0xc00).content("Data").manufacturer("Hitachi").architecture("HG51BS169").identifier(firmwareHITACHI()).text());
+ output.append(Memory{}.type("RAM").size(0xc00).content("Data").manufacturer("Hitachi").architecture("HG51BS169").identifier(firmwareHITACHI()).isVolatile().text());
+ output.append(Oscillator{}.frequency(20'000'000).text());
+ } else if(board(0) == "NEC") {
+ output.append(Memory{}.type("ROM").size(0x1800).content("Program").manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).text());
+ output.append(Memory{}.type("ROM").size( 0x800).content("Data" ).manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).text());
+ output.append(Memory{}.type("RAM").size( 0x200).content("Data" ).manufacturer("NEC").architecture("uPD7725").identifier(firmwareNEC()).isVolatile().text());
+ output.append(Oscillator{}.frequency(7'600'000).text());
+ } else if(board(0) == "SA1" || board(1) == "SA1") { //SA1-* or BS-SA1-*
+ output.append(Memory{}.type("RAM").size(0x800).content("Internal").isVolatile().text());
+ }
+
+ if(board.right() == "EPSONRTC") {
+ output.append(Memory{}.type("RTC").size(0x10).content("Time").manufacturer("Epson").text());
+ } else if(board.right() == "SHARPRTC") {
+ output.append(Memory{}.type("RTC").size(0x10).content("Time").manufacturer("Sharp").text());
+ }
+
+ return output;
+}
+
+auto SuperFamicom::region() const -> string {
+ //Unlicensed software (homebrew, ROM hacks, etc) often change the standard region code,
+ //and then neglect to change the extended header region code. Thanks to that, we can't
+ //decode and display the full game serial + region code.
+ return videoRegion();
+
+ string region;
+
+ char A = data[headerAddress + 0x02]; //game type
+ char B = data[headerAddress + 0x03]; //game code
+ char C = data[headerAddress + 0x04]; //game code
+ char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous)
+ auto E = data[headerAddress + 0x29]; //region code (old)
+
+ auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); };
+ if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) {
+ string code{A, B, C, D};
+ if(D == 'B') region = {"SNS-", code, "-BRA"};
+ if(D == 'C') region = {"SNSN-", code, "-ROC"};
+ if(D == 'D') region = {"SNSP-", code, "-NOE"};
+ if(D == 'E') region = {"SNS-", code, "-USA"};
+ if(D == 'F') region = {"SNSP-", code, "-FRA"};
+ if(D == 'H') region = {"SNSP-", code, "-HOL"};
+ if(D == 'I') region = {"SNSP-", code, "-ITA"};
+ if(D == 'J') region = {"SHVC-", code, "-JPN"};
+ if(D == 'K') region = {"SNSN-", code, "-KOR"};
+ if(D == 'N') region = {"SNS-", code, "-CAN"};
+ if(D == 'P') region = {"SNSP-", code, "-EUR"};
+ if(D == 'S') region = {"SNSP-", code, "-ESP"};
+ if(D == 'U') region = {"SNSP-", code, "-AUS"};
+ if(D == 'X') region = {"SNSP-", code, "-SCN"};
+ }
+
+ if(!region) {
+ if(E == 0x00) region = {"JPN"};
+ if(E == 0x01) region = {"USA"};
+ if(E == 0x02) region = {"EUR"};
+ if(E == 0x03) region = {"SCN"};
+ if(E == 0x06) region = {"FRA"};
+ if(E == 0x07) region = {"HOL"};
+ if(E == 0x08) region = {"ESP"};
+ if(E == 0x09) region = {"NOE"};
+ if(E == 0x0a) region = {"ITA"};
+ if(E == 0x0b) region = {"ROC"};
+ if(E == 0x0d) region = {"KOR"};
+ if(E == 0x0f) region = {"CAN"};
+ if(E == 0x10) region = {"BRA"};
+ if(E == 0x11) region = {"AUS"};
+ if(E == 0x12) region = {"SCN"};
+ }
+
+ return region ? region : "NTSC";
+}
+
+auto SuperFamicom::videoRegion() const -> string {
+ auto region = data[headerAddress + 0x29];
+ if(region == 0x00) return "NTSC"; //JPN
+ if(region == 0x01) return "NTSC"; //USA
+ if(region == 0x0b) return "NTSC"; //ROC
+ if(region == 0x0d) return "NTSC"; //KOR
+ if(region == 0x0f) return "NTSC"; //CAN
+ if(region == 0x10) return "NTSC"; //BRA
+ return "PAL";
+}
+
+auto SuperFamicom::revision() const -> string {
+ string revision;
+
+ char A = data[headerAddress + 0x02]; //game type
+ char B = data[headerAddress + 0x03]; //game code
+ char C = data[headerAddress + 0x04]; //game code
+ char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous)
+ auto E = data[headerAddress + 0x29]; //region code (old)
+ uint F = data[headerAddress + 0x2b]; //revision code
+
+ auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); };
+ if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) {
+ string code{A, B, C, D};
+ if(D == 'B') revision = {"SNS-", code, "-", F};
+ if(D == 'C') revision = {"SNSN-", code, "-", F};
+ if(D == 'D') revision = {"SNSP-", code, "-", F};
+ if(D == 'E') revision = {"SNS-", code, "-", F};
+ if(D == 'F') revision = {"SNSP-", code, "-", F};
+ if(D == 'H') revision = {"SNSP-", code, "-", F};
+ if(D == 'I') revision = {"SNSP-", code, "-", F};
+ if(D == 'J') revision = {"SHVC-", code, "-", F};
+ if(D == 'K') revision = {"SNSN-", code, "-", F};
+ if(D == 'N') revision = {"SNS-", code, "-", F};
+ if(D == 'P') revision = {"SNSP-", code, "-", F};
+ if(D == 'S') revision = {"SNSP-", code, "-", F};
+ if(D == 'U') revision = {"SNSP-", code, "-", F};
+ if(D == 'X') revision = {"SNSP-", code, "-", F};
+ }
+
+ if(!revision) {
+ revision = {"1.", F};
+ }
+
+ return revision ? revision : string{"1.", F};
+}
+
+//format: [slot]-[coprocessor]-[mapper]-[ram]-[rtc]
+auto SuperFamicom::board() const -> string {
+ string board;
+
+ auto mapMode = data[headerAddress + 0x25];
+ auto cartridgeTypeLo = data[headerAddress + 0x26] & 15;
+ auto cartridgeTypeHi = data[headerAddress + 0x26] >> 4;
+ auto cartridgeSubType = data[headerAddress + 0x0f];
+
+ string mode;
+ if(mapMode == 0x20 || mapMode == 0x30) mode = "LOROM-";
+ if(mapMode == 0x21 || mapMode == 0x31) mode = "HIROM-";
+ if(mapMode == 0x22 || mapMode == 0x32) mode = "SDD1-";
+ if(mapMode == 0x23 || mapMode == 0x33) mode = "SA1-";
+ if(mapMode == 0x25 || mapMode == 0x35) mode = "EXHIROM-";
+ if(mapMode == 0x2a || mapMode == 0x3a) mode = "SPC7110-";
+
+ //many games will store an extra title character, overwriting the map mode
+ //further, ExLoROM mode is unofficial, and lacks a mapping mode value
+ if(!mode) {
+ if(headerAddress == 0x7fb0) mode = "LOROM-";
+ if(headerAddress == 0xffb0) mode = "HIROM-";
+ if(headerAddress == 0x407fb0) mode = "EXLOROM-";
+ if(headerAddress == 0x40ffb0) mode = "EXHIROM-";
+ }
+
+ //this game's title ovewrites the map mode with '!' (0x21), but is a LOROM game
+ if(title() == "YUYU NO QUIZ DE GO!GO") mode = "LOROM-";
+
+ if(mode == "LOROM-" && headerAddress == 0x407fb0) mode = "EXLOROM-";
+
+ bool epsonRTC = false;
+ bool sharpRTC = false;
+
+ if(serial() == "A9PJ") {
+ //Sufami Turbo (JPN)
+ board.append("ST-", mode);
+ } else if(serial() == "ZBSJ") {
+ //BS-X: Sore wa Namae o Nusumareta Machi no Monogatari (JPN)
+ board.append("BS-MCC-");
+ } else if(serial() == "042J") {
+ //Super Game Boy 2
+ board.append("GB-", mode);
+ } else if(serial().match("Z??J")) {
+ board.append("BS-", mode);
+ } else if(cartridgeTypeLo >= 0x3) {
+ if(cartridgeTypeHi == 0x0) board.append("NEC-", mode);
+ if(cartridgeTypeHi == 0x1) board.append("GSU-");
+ if(cartridgeTypeHi == 0x2) board.append("OBC1-", mode);
+ if(cartridgeTypeHi == 0x3) board.append("SA1-");
+ if(cartridgeTypeHi == 0x4) board.append("SDD1-");
+ if(cartridgeTypeHi == 0x5) board.append(mode), sharpRTC = true;
+ if(cartridgeTypeHi == 0xe && cartridgeTypeLo == 0x3) board.append("GB-", mode);
+ if(cartridgeTypeHi == 0xf && cartridgeTypeLo == 0x5 && cartridgeSubType == 0x00) board.append("SPC7110-");
+ if(cartridgeTypeHi == 0xf && cartridgeTypeLo == 0x9 && cartridgeSubType == 0x00) board.append("SPC7110-"), epsonRTC = true;
+ if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) board.append("EXNEC-", mode);
+ if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) board.append("ARM-", mode);
+ if(cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) board.append("HITACHI-", mode);
+ }
+ if(!board) board.append(mode);
+
+ if(ramSize() || expansionRamSize()) board.append("RAM-");
+ if(epsonRTC) board.append("EPSONRTC-");
+ if(sharpRTC) board.append("SHARPRTC-");
+
+ board.trimRight("-", 1L);
+
+ if(board.beginsWith( "LOROM-RAM") && romSize() <= 0x200000) board.append("#A");
+ if(board.beginsWith("NEC-LOROM-RAM") && romSize() <= 0x100000) board.append("#A");
+
+ //Tengai Makyou Zero (fan translation)
+ if(board.beginsWith("SPC7110-") && data.size() == 0x700000) board.prepend("EX");
+
+ return board;
+}
+
+auto SuperFamicom::title() const -> string {
+ string label;
+
+ for(uint n = 0; n < 0x15; n++) {
+ auto x = data[headerAddress + 0x10 + n];
+ auto y = n == 0x14 ? 0 : data[headerAddress + 0x11 + n];
+
+ //null terminator (padding)
+ if(x == 0x00 || x == 0xff);
+
+ //ASCII
+ else if(x >= 0x20 && x <= 0x7e) label.append((char)x);
+
+ //Shift-JIS (half-width katakana)
+ else if(x == 0xa1) label.append("。");
+ else if(x == 0xa2) label.append("「");
+ else if(x == 0xa3) label.append("」");
+ else if(x == 0xa4) label.append("、");
+ else if(x == 0xa5) label.append("・");
+ else if(x == 0xa6) label.append("ヲ");
+ else if(x == 0xa7) label.append("ァ");
+ else if(x == 0xa8) label.append("ィ");
+ else if(x == 0xa9) label.append("ゥ");
+ else if(x == 0xaa) label.append("ェ");
+ else if(x == 0xab) label.append("ォ");
+ else if(x == 0xac) label.append("ャ");
+ else if(x == 0xad) label.append("ュ");
+ else if(x == 0xae) label.append("ョ");
+ else if(x == 0xaf) label.append("ッ");
+ else if(x == 0xb0) label.append("ー");
+
+ else if(x == 0xb1) label.append( "ア");
+ else if(x == 0xb2) label.append( "イ");
+ else if(x == 0xb3) label.append(y == 0xde ? "ヴ" : "ウ");
+ else if(x == 0xb4) label.append( "エ");
+ else if(x == 0xb5) label.append( "オ");
+
+ else if(x == 0xb6) label.append(y == 0xde ? "ガ" : "カ");
+ else if(x == 0xb7) label.append(y == 0xde ? "ギ" : "キ");
+ else if(x == 0xb8) label.append(y == 0xde ? "グ" : "ク");
+ else if(x == 0xb9) label.append(y == 0xde ? "ゲ" : "ケ");
+ else if(x == 0xba) label.append(y == 0xde ? "ゴ" : "コ");
+
+ else if(x == 0xbb) label.append(y == 0xde ? "ザ" : "サ");
+ else if(x == 0xbc) label.append(y == 0xde ? "ジ" : "シ");
+ else if(x == 0xbd) label.append(y == 0xde ? "ズ" : "ス");
+ else if(x == 0xbe) label.append(y == 0xde ? "ゼ" : "セ");
+ else if(x == 0xbf) label.append(y == 0xde ? "ゾ" : "ソ");
+
+ else if(x == 0xc0) label.append(y == 0xde ? "ダ" : "タ");
+ else if(x == 0xc1) label.append(y == 0xde ? "ヂ" : "チ");
+ else if(x == 0xc2) label.append(y == 0xde ? "ヅ" : "ツ");
+ else if(x == 0xc3) label.append(y == 0xde ? "デ" : "テ");
+ else if(x == 0xc4) label.append(y == 0xde ? "ド" : "ト");
+
+ else if(x == 0xc5) label.append("ナ");
+ else if(x == 0xc6) label.append("ニ");
+ else if(x == 0xc7) label.append("ヌ");
+ else if(x == 0xc8) label.append("ネ");
+ else if(x == 0xc9) label.append("ノ");
+
+ else if(x == 0xca) label.append(y == 0xdf ? "パ" : y == 0xde ? "バ" : "ハ");
+ else if(x == 0xcb) label.append(y == 0xdf ? "ピ" : y == 0xde ? "ビ" : "ヒ");
+ else if(x == 0xcc) label.append(y == 0xdf ? "プ" : y == 0xde ? "ブ" : "フ");
+ else if(x == 0xcd) label.append(y == 0xdf ? "ペ" : y == 0xde ? "ベ" : "ヘ");
+ else if(x == 0xce) label.append(y == 0xdf ? "ポ" : y == 0xde ? "ボ" : "ホ");
+
+ else if(x == 0xcf) label.append("マ");
+ else if(x == 0xd0) label.append("ミ");
+ else if(x == 0xd1) label.append("ム");
+ else if(x == 0xd2) label.append("メ");
+ else if(x == 0xd3) label.append("モ");
+
+ else if(x == 0xd4) label.append("ヤ");
+ else if(x == 0xd5) label.append("ユ");
+ else if(x == 0xd6) label.append("ヨ");
+
+ else if(x == 0xd7) label.append("ラ");
+ else if(x == 0xd8) label.append("リ");
+ else if(x == 0xd9) label.append("ル");
+ else if(x == 0xda) label.append("レ");
+ else if(x == 0xdb) label.append("ロ");
+
+ else if(x == 0xdc) label.append("ワ");
+ else if(x == 0xdd) label.append("ン");
+
+ else if(x == 0xde) label.append("\xef\xbe\x9e"); //dakuten
+ else if(x == 0xdf) label.append("\xef\xbe\x9f"); //handakuten
+
+ //unknown
+ else label.append("?");
+
+ //(han)dakuten skip
+ if(y == 0xde && x == 0xb3) n++;
+ if(y == 0xde && x >= 0xb6 && x <= 0xc4) n++;
+ if(y == 0xde && x >= 0xca && x <= 0xce) n++;
+ if(y == 0xdf && x >= 0xca && y <= 0xce) n++;
+ }
+
+ return label.strip();
+}
+
+auto SuperFamicom::serial() const -> string {
+ char A = data[headerAddress + 0x02]; //game type
+ char B = data[headerAddress + 0x03]; //game code
+ char C = data[headerAddress + 0x04]; //game code
+ char D = data[headerAddress + 0x05]; //region code (new; sometimes ambiguous)
+
+ auto valid = [](char n) { return (n >= '0' && n <= '9') || (n >= 'A' && n <= 'Z'); };
+ if(data[headerAddress + 0x2a] == 0x33 && valid(A) && valid(B) & valid(C) & valid(D)) {
+ return {A, B, C, D};
+ }
+
+ return "";
+}
+
+auto SuperFamicom::romSize() const -> uint {
+ return size() - firmwareRomSize();
+}
+
+auto SuperFamicom::programRomSize() const -> uint {
+ if(board().beginsWith("SPC7110-")) return 0x100000;
+ if(board().beginsWith("EXSPC7110-")) return 0x100000;
+ return romSize();
+}
+
+auto SuperFamicom::dataRomSize() const -> uint {
+ if(board().beginsWith("SPC7110-")) return romSize() - 0x100000;
+ if(board().beginsWith("EXSPC7110-")) return 0x500000;
+ return 0;
+}
+
+auto SuperFamicom::expansionRomSize() const -> uint {
+ if(board().beginsWith("EXSPC7110-")) return 0x100000;
+ return 0;
+}
+
+//detect if any firmware is appended to the ROM image, and return its size if so
+auto SuperFamicom::firmwareRomSize() const -> uint {
+ auto cartridgeTypeLo = data[headerAddress + 0x26] & 15;
+ auto cartridgeTypeHi = data[headerAddress + 0x26] >> 4;
+ auto cartridgeSubType = data[headerAddress + 0x0f];
+
+ if(serial() == "042J" || (cartridgeTypeLo == 0x3 && cartridgeTypeHi == 0xe)) {
+ //Game Boy
+ if((size() & 0x7fff) == 0x100) return 0x100;
+ }
+
+ if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x10) {
+ //Hitachi HG51BS169
+ if((size() & 0x7fff) == 0xc00) return 0xc00;
+ }
+
+ if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0x0) {
+ //NEC uPD7725
+ if((size() & 0x7fff) == 0x2000) return 0x2000;
+ }
+
+ if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x01) {
+ //NEC uPD96050
+ if((size() & 0xffff) == 0xd000) return 0xd000;
+ }
+
+ if(cartridgeTypeLo >= 0x3 && cartridgeTypeHi == 0xf && cartridgeSubType == 0x02) {
+ //ARM6
+ if((size() & 0x3ffff) == 0x28000) return 0x28000;
+ }
+
+ return 0;
+}
+
+auto SuperFamicom::ramSize() const -> uint {
+ auto ramSize = data[headerAddress + 0x28] & 15;
+ if(ramSize > 8) ramSize = 8;
+ if(ramSize > 0) return 1024 << ramSize;
+ return 0;
+}
+
+auto SuperFamicom::expansionRamSize() const -> uint {
+ if(data[headerAddress + 0x2a] == 0x33) {
+ auto ramSize = data[headerAddress + 0x0d] & 15;
+ if(ramSize > 8) ramSize = 8;
+ if(ramSize > 0) return 1024 << ramSize;
+ }
+ if((data[headerAddress + 0x26] >> 4) == 1) {
+ //GSU: Starfox / Starwing lacks an extended header; but still has expansion RAM
+ return 0x8000;
+ }
+ return 0;
+}
+
+auto SuperFamicom::nonVolatile() const -> bool {
+ auto cartridgeTypeLo = data[headerAddress + 0x26] & 15;
+ return cartridgeTypeLo == 0x2 || cartridgeTypeLo == 0x5 || cartridgeTypeLo == 0x6;
+}
+
+auto SuperFamicom::scoreHeader(uint address) -> uint {
+ int score = 0;
+ if(size() < address + 0x50) return score;
+
+ uint8_t mapMode = data[address + 0x25] & ~0x10; //ignore FastROM bit
+ uint16_t complement = data[address + 0x2c] << 0 | data[address + 0x2d] << 8;
+ uint16_t checksum = data[address + 0x2e] << 0 | data[address + 0x2f] << 8;
+ uint16_t resetVector = data[address + 0x4c] << 0 | data[address + 0x4d] << 8;
+ if(resetVector < 0x8000) return score; //$00:0000-7fff is never ROM data
+
+ uint8_t opcode = data[(address & ~0x7fff) | (resetVector & 0x7fff)]; //first instruction executed
+
+ //most likely opcodes
+ if(opcode == 0x78 //sei
+ || opcode == 0x18 //clc (clc; xce)
+ || opcode == 0x38 //sec (sec; xce)
+ || opcode == 0x9c //stz $nnnn (stz $4200)
+ || opcode == 0x4c //jmp $nnnn
+ || opcode == 0x5c //jml $nnnnnn
+ ) score += 8;
+
+ //plausible opcodes
+ if(opcode == 0xc2 //rep #$nn
+ || opcode == 0xe2 //sep #$nn
+ || opcode == 0xad //lda $nnnn
+ || opcode == 0xae //ldx $nnnn
+ || opcode == 0xac //ldy $nnnn
+ || opcode == 0xaf //lda $nnnnnn
+ || opcode == 0xa9 //lda #$nn
+ || opcode == 0xa2 //ldx #$nn
+ || opcode == 0xa0 //ldy #$nn
+ || opcode == 0x20 //jsr $nnnn
+ || opcode == 0x22 //jsl $nnnnnn
+ ) score += 4;
+
+ //implausible opcodes
+ if(opcode == 0x40 //rti
+ || opcode == 0x60 //rts
+ || opcode == 0x6b //rtl
+ || opcode == 0xcd //cmp $nnnn
+ || opcode == 0xec //cpx $nnnn
+ || opcode == 0xcc //cpy $nnnn
+ ) score -= 4;
+
+ //least likely opcodes
+ if(opcode == 0x00 //brk #$nn
+ || opcode == 0x02 //cop #$nn
+ || opcode == 0xdb //stp
+ || opcode == 0x42 //wdm
+ || opcode == 0xff //sbc $nnnnnn,x
+ ) score -= 8;
+
+ if(checksum + complement == 0xffff) score += 4;
+
+ if(address == 0x7fb0 && mapMode == 0x20) score += 2;
+ if(address == 0xffb0 && mapMode == 0x21) score += 2;
+
+ return max(0, score);
+}
+
+auto SuperFamicom::firmwareARM() const -> string {
+ return "ST018";
+}
+
+auto SuperFamicom::firmwareEXNEC() const -> string {
+ if(title() == "EXHAUST HEAT2") return "ST010";
+ if(title() == "F1 ROC II") return "ST010";
+ if(title() == "2DAN MORITA SHOUGI") return "ST011";
+ return "ST010";
+}
+
+auto SuperFamicom::firmwareGB() const -> string {
+ if(title() == "Super GAMEBOY") return "SGB1";
+ if(title() == "Super GAMEBOY2") return "SGB2";
+ return "SGB1";
+}
+
+auto SuperFamicom::firmwareHITACHI() const -> string {
+ return "Cx4";
+}
+
+auto SuperFamicom::firmwareNEC() const -> string {
+ if(title() == "PILOTWINGS") return "DSP1";
+ if(title() == "DUNGEON MASTER") return "DSP2";
+ if(title() == "SDガンダムGX") return "DSP3";
+ if(title() == "PLANETS CHAMP TG3000") return "DSP4";
+ if(title() == "TOP GEAR 3000") return "DSP4";
+ return "DSP1B";
+}
+
+}
diff --git a/jg.cpp b/jg.cpp
new file mode 100644
index 0000000..75c03d3
--- /dev/null
+++ b/jg.cpp
@@ -0,0 +1,914 @@
+/*
+ * bsnes-jg - Super Nintendo emulator
+ *
+ * Copyright (C) 2004-2020 byuu
+ * Copyright (C) 2020 Rupert Carmichael
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, specifically version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+using namespace nall;
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#define SAMPLERATE 48000
+#define FRAMERATE 60
+#define FRAMERATE_PAL 50
+#define CHANNELS 2
+#define NUMINPUTS 2
+
+#define ASPECT_NTSC 1.306122
+#define ASPECT_PAL 1.4257812
+
+#define TIMING_NTSC 60.098812
+#define TIMING_PAL 50.006979
+
+static jg_cb_audio_t jg_cb_audio;
+static jg_cb_frametime_t jg_cb_frametime;
+static jg_cb_log_t jg_cb_log;
+static jg_cb_rumble_t jg_cb_rumble;
+static jg_cb_settings_read_t jg_cb_settings_read;
+
+static jg_coreinfo_t coreinfo = {
+ "bsnes", "bsnes-jg", "115", "snes", NUMINPUTS, JG_HINT_VIDEO_INTERNAL
+};
+
+static jg_videoinfo_t vidinfo = {
+ JG_PIXFMT_RGB1555, // pixfmt
+ 2304, // wmax
+ 2160, // hmax
+ 256, // w
+ 224, // h
+ 0, // x
+ 8, // y
+ 1024, // p
+ 8.0/7.0, // aspect
+ NULL
+};
+
+static jg_audioinfo_t audinfo = {
+ JG_SAMPFMT_FLT32,
+ SAMPLERATE,
+ CHANNELS,
+ (SAMPLERATE / FRAMERATE) * CHANNELS,
+ NULL
+};
+
+static jg_pathinfo_t pathinfo;
+static jg_gameinfo_t gameinfo;
+static jg_inputinfo_t inputinfo[NUMINPUTS];
+static jg_inputstate_t *input_device[NUMINPUTS];
+
+// Emulator settings
+static jg_setting_t settings_bsnes[] = { // name, default, min, max
+ { "aspect_ratio", 0, 0, 1 }, // 0 = 8:7, 1 = Auto Region
+};
+
+enum {
+ ASPECT,
+};
+
+static uint16_t audio_buffer_index = 0;
+static uint16_t audio_buffer_max = audinfo.spf;
+
+static int vidmult = 1;
+static int ss_offset_x = 0;
+static int ss_offset_y = 0;
+
+static const unsigned char iplrom[64] = {
+ 0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0,
+ 0xfc, 0x8f, 0xaa, 0xf4, 0x8f, 0xbb, 0xf5, 0x78,
+ 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19, 0xeb, 0xf4,
+ 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5,
+ 0xcb, 0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab,
+ 0x01, 0x10, 0xef, 0x7e, 0xf4, 0x10, 0xeb, 0xba,
+ 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4, 0xdd,
+ 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff
+};
+
+static Emulator::Interface *emulator;
+
+struct Program : Emulator::Platform {
+ Program();
+ ~Program();
+
+ auto open(uint id, string name, vfs::file::mode mode, bool required) ->
+ shared_pointer override;
+ auto load(uint id, string name, string type, vector options = {}) ->
+ Emulator::Platform::Load override;
+ auto videoFrame(const uint16* data, uint pitch, uint width, uint height,
+ uint scale) -> void override;
+ auto audioFrame(const double* samples, uint channels) -> void override;
+ auto inputPoll(uint port, uint device, uint input) -> int16 override;
+ auto inputRumble(uint port, uint device, uint input, bool enable) ->
+ void override;
+
+ auto load() -> void;
+ auto loadFile(string location) -> vector;
+ auto loadSuperFamicom(string location) -> bool;
+ auto loadGameBoy(string location) -> bool;
+ auto loadBSMemory(string location) -> bool;
+
+ auto save() -> void;
+
+ auto openRomSuperFamicom(string name, vfs::file::mode mode) ->
+ shared_pointer;
+ auto openRomGameBoy(string name, vfs::file::mode mode) ->
+ shared_pointer;
+ auto openRomBSMemory(string name, vfs::file::mode mode) ->
+ shared_pointer;
+
+ auto hackPatchMemory(vector& data) -> void;
+
+ string base_name;
+
+ bool overscan = true;
+
+public:
+ struct Game {
+ explicit operator bool() const { return (bool)location; }
+
+ string option;
+ string location;
+ string manifest;
+ Markup::Node document;
+ boolean patched;
+ boolean verified;
+ };
+
+ struct SuperFamicom : Game {
+ string title;
+ string region;
+ vector program;
+ vector data;
+ vector expansion;
+ vector firmware;
+ } superFamicom;
+
+ struct GameBoy : Game {
+ vector program;
+ } gameBoy;
+
+ struct BSMemory : Game {
+ vector program;
+ } bsMemory;
+};
+
+static Program *program = nullptr;
+
+Program::Program() {
+ Emulator::platform = this;
+}
+
+Program::~Program() {
+ delete emulator;
+}
+
+auto Program::save() -> void {
+ if(!emulator->loaded()) return;
+ emulator->save();
+}
+
+auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
+ shared_pointer {
+
+ shared_pointer result;
+
+ if (name == "ipl.rom" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(iplrom, sizeof(iplrom));
+ }
+ if (name == "boards.bml" && mode == vfs::file::mode::read) {
+ string boardspath = {pathinfo.core, "/boards.bml"};
+ return vfs::fs::file::open(boardspath, mode);
+ }
+
+ if (id == 1) { //Super Famicom
+ if (name == "manifest.bml" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(
+ superFamicom.manifest.data(),
+ superFamicom.manifest.size());
+ }
+ else if (name == "program.rom" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(superFamicom.program.data(),
+ superFamicom.program.size());
+ }
+ else if (name == "data.rom" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(superFamicom.data.data(),
+ superFamicom.data.size());
+ }
+ else if (name == "expansion.rom" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(superFamicom.expansion.data(),
+ superFamicom.expansion.size());
+ }
+ else {
+ result = openRomSuperFamicom(name, mode);
+ }
+ }
+ else if (id == 2) { //Game Boy
+ if (name == "manifest.bml" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(gameBoy.manifest.data(),
+ gameBoy.manifest.size());
+ }
+ else if (name == "program.rom" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(gameBoy.program.data(),
+ gameBoy.program.size());
+ }
+ else {
+ result = openRomGameBoy(name, mode);
+ }
+ }
+ else if (id == 3) { //BS Memory
+ if (name == "manifest.bml" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(bsMemory.manifest.data(),
+ bsMemory.manifest.size());
+ }
+ else if (name == "program.rom" && mode == vfs::file::mode::read) {
+ result = vfs::memory::file::open(bsMemory.program.data(),
+ bsMemory.program.size());
+ }
+ else if(name == "program.flash") {
+ //writes are not flushed to disk in bsnes
+ result = vfs::memory::file::open(bsMemory.program.data(),
+ bsMemory.program.size());
+ }
+ else {
+ result = openRomBSMemory(name, mode);
+ }
+ }
+
+ return result;
+}
+
+auto Program::load() -> void {
+ emulator->unload();
+ emulator->load();
+
+ // per-game hack overrides
+ auto title = superFamicom.title;
+ auto region = superFamicom.region;
+
+ //sometimes menu options are skipped over in the main menu with cycle-based
+ //joypad polling
+ if (title == "Arcades Greatest Hits")
+ emulator->configure("Hacks/CPU/FastJoypadPolling", true);
+
+ //the start button doesn't work in this game with cycle-based joypad polling
+ else if (title == "TAIKYOKU-IGO Goliath")
+ emulator->configure("Hacks/CPU/FastJoypadPolling", true);
+
+ //holding up or down on the menu quickly cycles through options instead of
+ //stopping after each button press
+ else if (title == "WORLD MASTERS GOLF")
+ emulator->configure("Hacks/CPU/FastJoypadPolling", true);
+
+ //relies on mid-scanline rendering techniques
+ else if (title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER")
+ emulator->configure("Hacks/PPU/Fast", false);
+
+ //the dialogue text is blurry due to an issue in the scanline-based
+ //renderer's color math support
+ else if (title == "マーヴェラス")
+ emulator->configure("Hacks/PPU/Fast", false);
+
+ //stage 2 uses pseudo-hires in a way that's not compatible with the
+ //scanline-based renderer
+ else if (title == "SFC クレヨンシンチャン")
+ emulator->configure("Hacks/PPU/Fast", false);
+
+ //title screen game select (after choosing a game) changes OAM tiledata
+ //address mid-frame. This is only supported by the cycle-based PPU renderer
+ else if (title == "Winter olympics")
+ emulator->configure("Hacks/PPU/Fast", false);
+
+ //title screen shows remnants of the flag after choosing a language with the
+ //scanline-based renderer
+ else if (title == "WORLD CUP STRIKER")
+ emulator->configure("Hacks/PPU/Fast", false);
+
+ //relies on cycle-accurate writes to the echo buffer
+ else if (title == "KOUSHIEN_2")
+ emulator->configure("Hacks/DSP/Fast", false);
+
+ //will hang immediately
+ else if (title == "RENDERING RANGER R2")
+ emulator->configure("Hacks/DSP/Fast", false);
+
+ //will hang sometimes in the "Bach in Time" stage
+ else if (title == "BUBSY II" && region == "PAL")
+ emulator->configure("Hacks/DSP/Fast", false);
+
+ //fixes for an errant scanline on the title screen due to writing to PPU
+ //registers too late
+ else if (title == "ADVENTURES OF FRANKEN" && region == "PAL")
+ emulator->configure("Hacks/PPU/RenderCycle", 32);
+
+ else if (title == "FIREPOWER 2000" || title == "SUPER SWIV")
+ emulator->configure("Hacks/PPU/RenderCycle", 32);
+
+ else if (title == "NHL '94" || title == "NHL PROHOCKEY'94")
+ emulator->configure("Hacks/PPU/RenderCycle", 32);
+
+ else if (title == "Sugoro Quest++")
+ emulator->configure("Hacks/PPU/RenderCycle", 128);
+
+ else if (emulator->configuration("Hacks/Hotfixes")) {
+ //this game transfers uninitialized memory into video RAM: this can
+ //cause a row of invalid tiles to appear in the background of stage 12.
+ //this one is a bug in the original game, so only enable it if the
+ //hotfixes option has been enabled.
+ if (title == "The Hurricanes")
+ emulator->configure("Hacks/Entropy", "None");
+
+ //Frisky Tom attract sequence sometimes hangs when WRAM is initialized
+ //to pseudo-random patterns
+ else if (title == "ニチブツ・アーケード・クラシックス")
+ emulator->configure("Hacks/Entropy", "None");
+ }
+}
+
+auto Program::load(uint id, string name, string type, vector options) ->
+ Emulator::Platform::Load {
+
+ if (id == 1) {
+ if (loadSuperFamicom(superFamicom.location)) {
+ return {id, superFamicom.region};
+ }
+ }
+ else if (id == 2) {
+ if (loadGameBoy(gameBoy.location)) {
+ return { id, NULL };
+ }
+ }
+ else if (id == 3) {
+ if (loadBSMemory(bsMemory.location)) {
+ return { id, NULL };
+ }
+ }
+ return { id, options(0) };
+}
+
+auto Program::videoFrame(const uint16* data, uint pitch, uint width,
+ uint height, uint scale) -> void {
+
+ //print("p: ", pitch, " w: ", width, " h: ", height, "\n");
+ vidmult = height / 240;
+ vidinfo.y = 8 * vidmult;
+ height -= 2 * vidinfo.y;
+
+ vidinfo.w = width;
+ vidinfo.h = height;
+ vidinfo.p = pitch / 2; // Divide by pixel size - 16-bit pixels == 2
+ vidinfo.buf = (void*)data;
+}
+
+auto Program::audioFrame(const double* samples, uint channels) -> void {
+ float *abuf = (float*)audinfo.buf;
+ abuf[audio_buffer_index++] = (float)samples[0];
+ abuf[audio_buffer_index++] = (float)samples[1];
+}
+
+static uint8_t imap[12] = { 0, 1, 2, 3, 7, 6, 9, 8, 10, 11, 4, 5 };
+
+auto pollInputDevices(uint port, uint device, uint input) -> int16 {
+ //print("port: ", port, " device: ", device, " input: ", input, "\n");
+ if (device == SuperFamicom::ID::Device::SuperScope) {
+ switch (input) {
+ case 0: // X
+ return (input_device[port]->coord[0] / vidmult) + ss_offset_x;
+ case 1: // Y
+ return (input_device[port]->coord[1] / vidmult) + ss_offset_y;
+ case 2: { // Trigger
+ int ret = 0;
+ if (input_device[port]->button[1]) { // Offscreen
+ input_device[port]->coord[0] = 1026;
+ ret = 1;
+ }
+ else
+ ret = input_device[port]->button[0];
+ return ret;
+ }
+ case 3: // Cursor
+ return input_device[port]->button[2];
+ case 4: // Turbo
+ return input_device[port]->button[3];
+ case 5: // Pause
+ return input_device[port]->button[4];
+ default:
+ return 0;
+ }
+ }
+ /*else if (device == SuperFamicom::ID::Device::Justifier) {
+ switch (input) {
+ case 0: // X
+ return (input_device[port]->coord[0] / vidmult) + ss_offset_x;
+ case 1: // Y
+ return (input_device[port]->coord[1] / vidmult) + ss_offset_y;
+ case 2: {// Trigger
+ int ret = 0;
+ if (input_device[port]->button[1]) { // Offscreen
+ input_device[port]->coord[0] = 1026;
+ ret = 1;
+ }
+ else
+ ret = input_device[port]->button[0];
+ return ret;
+ }
+ case 3: // Start
+ return input_device[port]->button[2];
+ default:
+ return 0;
+ }
+ }*/
+
+ return port > 1 ? 0 : input_device[port]->button[imap[input]];
+}
+
+auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
+ return pollInputDevices(port, device, input);
+}
+
+auto Program::inputRumble(uint port, uint device, uint input, bool enable) ->
+ void {
+}
+
+auto Program::openRomSuperFamicom(string name, vfs::file::mode mode) ->
+ shared_pointer {
+
+ if (name == "program.rom" && mode == vfs::file::mode::read) {
+ return vfs::memory::file::open(superFamicom.program.data(),
+ superFamicom.program.size());
+ }
+
+ if (name == "data.rom" && mode == vfs::file::mode::read) {
+ return vfs::memory::file::open(superFamicom.data.data(),
+ superFamicom.data.size());
+ }
+
+ if (name == "expansion.rom" && mode == vfs::file::mode::read) {
+ return vfs::memory::file::open(superFamicom.expansion.data(),
+ superFamicom.expansion.size());
+ }
+
+ if (name == "msu1/data.rom") {
+ return vfs::fs::file::open({Location::notsuffix(superFamicom.location),
+ ".msu"}, mode);
+ }
+
+ if (name.match("msu1/track*.pcm")) {
+ name.trimLeft("msu1/track", 1L);
+ return vfs::fs::file::open({Location::notsuffix(superFamicom.location),
+ name}, mode);
+ }
+
+ if (name == "save.ram") {
+ string save_path = { pathinfo.save, "/", gameinfo.name, ".srm" };
+ const char *save = nullptr;
+ return vfs::fs::file::open(save_path, mode);
+ }
+
+ if (name == "download.ram") {
+ string ram_path = { pathinfo.save, "/", gameinfo.name, ".psr" };
+ const char *save = nullptr;
+ return vfs::fs::file::open(ram_path, mode);
+ }
+
+ return {};
+}
+
+auto Program::openRomGameBoy(string name, vfs::file::mode mode) ->
+ shared_pointer {
+
+ if (name == "program.rom" && mode == vfs::file::mode::read) {
+ return vfs::memory::file::open(gameBoy.program.data(),
+ gameBoy.program.size());
+ }
+
+ if (name == "save.ram") {
+ string save_path = { pathinfo.save, "/", gameinfo.name, ".srm" };
+ const char *save = nullptr;
+ return vfs::fs::file::open(save_path, mode);
+ }
+
+ if (name == "time.rtc") {
+ string save_path = { pathinfo.save, "/", gameinfo.name, ".rtc" };
+ const char *save = nullptr;
+ return vfs::fs::file::open(save_path, mode);
+ }
+
+ return {};
+}
+
+auto Program::openRomBSMemory(string name, vfs::file::mode mode) ->
+ shared_pointer {
+
+ if (name == "program.rom" && mode == vfs::file::mode::read) {
+ return vfs::memory::file::open(bsMemory.program.data(),
+ bsMemory.program.size());
+ }
+
+ if (name == "program.flash") {
+ //writes are not flushed to disk
+ return vfs::memory::file::open(bsMemory.program.data(),
+ bsMemory.program.size());
+ }
+
+ return {};
+}
+
+auto Program::loadFile(string location) -> vector {
+ if(Location::suffix(location).downcase() == ".zip") {
+ Decode::ZIP archive;
+ if (archive.open(location)) {
+ for (auto& file : archive.file) {
+ auto type = Location::suffix(file.name).downcase();
+ if (type == ".sfc" || type == ".smc" || type == ".gb" ||
+ type == ".gbc" || type == ".bs" || type == ".st") {
+ return archive.extract(file);
+ }
+ }
+ }
+ return {};
+ }
+ else if(Location::suffix(location).downcase() == ".7z") {
+ return LZMA::extract(location);
+ }
+ else {
+ return file::read(location);
+ }
+}
+
+auto Program::loadSuperFamicom(string location) -> bool {
+ vector rom;
+ rom = loadFile(location);
+
+ if (rom.size() < 0x8000) return false;
+
+ //assume ROM and IPS agree on whether a copier header is present
+ //superFamicom.patched = applyPatchIPS(rom, location);
+ if ((rom.size() & 0x7fff) == 512) {
+ //remove copier header
+ memory::move(&rom[0], &rom[512], rom.size() - 512);
+ rom.resize(rom.size() - 512);
+ }
+
+ auto heuristics = Heuristics::SuperFamicom(rom, location);
+ auto sha256 = Hash::SHA256(rom).digest();
+
+ superFamicom.title = heuristics.title();
+ superFamicom.region = heuristics.videoRegion();
+ superFamicom.manifest = heuristics.manifest();
+
+ hackPatchMemory(rom);
+ superFamicom.document = BML::unserialize(superFamicom.manifest);
+ superFamicom.location = location;
+
+ uint offset = 0;
+ if (auto size = heuristics.programRomSize()) {
+ superFamicom.program.resize(size);
+ memory::copy(&superFamicom.program[0], &rom[offset], size);
+ offset += size;
+ }
+ if (auto size = heuristics.dataRomSize()) {
+ superFamicom.data.resize(size);
+ memory::copy(&superFamicom.data[0], &rom[offset], size);
+ offset += size;
+ }
+ if (auto size = heuristics.expansionRomSize()) {
+ superFamicom.expansion.resize(size);
+ memory::copy(&superFamicom.expansion[0], &rom[offset], size);
+ offset += size;
+ }
+ if (auto size = heuristics.firmwareRomSize()) {
+ superFamicom.firmware.resize(size);
+ memory::copy(&superFamicom.firmware[0], &rom[offset], size);
+ offset += size;
+ }
+ return true;
+}
+
+auto Program::loadGameBoy(string location) -> bool {
+ vector rom;
+ rom = loadFile(location);
+
+ if (rom.size() < 0x4000) return false;
+
+ auto heuristics = Heuristics::GameBoy(rom, location);
+ auto sha256 = Hash::SHA256(rom).digest();
+
+ gameBoy.manifest = heuristics.manifest();
+ gameBoy.document = BML::unserialize(gameBoy.manifest);
+ gameBoy.location = location;
+ gameBoy.program = rom;
+
+ return true;
+}
+
+auto Program::loadBSMemory(string location) -> bool {
+ string manifest;
+ vector rom;
+ rom = loadFile(location);
+
+ if (rom.size() < 0x8000) return false;
+
+ auto heuristics = Heuristics::BSMemory(rom, location);
+ auto sha256 = Hash::SHA256(rom).digest();
+
+ bsMemory.manifest = manifest ? manifest : heuristics.manifest();
+ bsMemory.document = BML::unserialize(bsMemory.manifest);
+ bsMemory.location = location;
+
+ bsMemory.program = rom;
+ return true;
+}
+
+auto Program::hackPatchMemory(vector& data) -> void {
+ auto title = superFamicom.title;
+
+ if(title == "Satellaview BS-X" && data.size() >= 0x100000) {
+ //BS-X: Sore wa Namae o Nusumareta Machi no Monogatari (JPN) (1.1)
+ //disable limited play check for BS Memory flash cartridges
+ //benefit: allow locked out BS Memory flash games to play without manual
+ //header patching.
+ //detriment: BS Memory ROM cartridges will cause the game to hang in the
+ //load menu
+ if(data[0x4a9b] == 0x10) data[0x4a9b] = 0x80;
+ if(data[0x4d6d] == 0x10) data[0x4d6d] = 0x80;
+ if(data[0x4ded] == 0x10) data[0x4ded] = 0x80;
+ if(data[0x4e9a] == 0x10) data[0x4e9a] = 0x80;
+ }
+}
+
+// Jolly Good API Calls
+void jg_set_cb_audio(jg_cb_audio_t func) {
+ jg_cb_audio = func;
+}
+
+void jg_set_cb_frametime(jg_cb_frametime_t func) {
+ jg_cb_frametime = func;
+}
+
+void jg_set_cb_log(jg_cb_log_t func) {
+ jg_cb_log = func;
+}
+
+void jg_set_cb_rumble(jg_cb_rumble_t func) {
+ jg_cb_rumble = func;
+}
+
+void jg_set_cb_settings_read(jg_cb_settings_read_t func) {
+ jg_cb_settings_read = func;
+}
+
+int jg_init() {
+ jg_cb_settings_read(settings_bsnes,
+ sizeof(settings_bsnes) / sizeof(jg_setting_t));
+ emulator = new SuperFamicom::Interface;
+ program = new Program;
+ return 1;
+}
+
+void jg_deinit() {
+ delete program;
+}
+
+void jg_reset(int hard) {
+ emulator->reset();
+}
+
+void jg_exec_frame() {
+ emulator->run();
+ jg_cb_audio(audio_buffer_index);
+ audio_buffer_index = 0;
+}
+
+int jg_game_load() {
+ emulator->configure("Audio/Frequency", SAMPLERATE);
+
+ emulator->configure("Video/BlurEmulation", false);
+
+ emulator->configure("Hacks/Entropy", "Low");
+ emulator->configure("Hacks/Hotfixes", false);
+
+ emulator->configure("Hacks/CPU/Overclock", 100);
+ emulator->configure("Hacks/CPU/FastMath", false);
+ emulator->configure("Hacks/SA1/Overclock", 100);
+ emulator->configure("Hacks/SuperFX/Overclock", 100);
+
+ emulator->configure("Hacks/PPU/Fast", false); // default true
+ emulator->configure("Hacks/PPU/Deinterlace", false);
+ emulator->configure("Hacks/PPU/NoSpriteLimit", false);
+ emulator->configure("Hacks/PPU/NoVRAMBlocking", false);
+ emulator->configure("Hacks/PPU/Mode7/Scale", 4);
+ emulator->configure("Hacks/PPU/Mode7/Perspective", false);
+ emulator->configure("Hacks/PPU/Mode7/Supersample", false);
+ emulator->configure("Hacks/PPU/Mode7/Mosaic", false);
+
+ emulator->configure("Hacks/DSP/Fast", false); // default true
+ emulator->configure("Hacks/DSP/Cubic", false);
+ emulator->configure("Hacks/DSP/EchoShadow", false);
+ emulator->configure("Hacks/Coprocessor/DelayedSync", false); // default true
+ emulator->configure("Hacks/Coprocessor/PreferHLE", true);
+
+ // Overscan should be drawn because the frontend does the clipping
+ program->overscan = true;
+
+ // Load the game
+ if (string(gameinfo.path).endsWith(".gb") ||
+ string(gameinfo.path).endsWith(".gbc")) {
+ //string sgb_bios = string(pathinfo.bios, "/Super Game Boy (JU).sfc");
+ //string sgb_bios = string(pathinfo.bios, "/Super Game Boy (UE).sfc");
+ string sgb_bios = string(pathinfo.bios, "/Super Game Boy 2 (J).sfc");
+ if (!file::exists(sgb_bios)) {
+ jg_cb_log(JG_LOG_ERR,
+ "Missing BIOS file \'Super Game Boy 2 (J).sfc\', exiting...\n");
+ return 0;
+ }
+
+ program->superFamicom.location = sgb_bios;
+ program->gameBoy.location = string(gameinfo.path);
+ }
+ else if (string(gameinfo.path).endsWith(".bs")) {
+ string bsx_bios = string(pathinfo.bios, "/BsxBios.sfc");
+ if (!file::exists(bsx_bios)) {
+ jg_cb_log(JG_LOG_ERR,
+ "Missing BIOS file \'BsxBios.sfc\', exiting...\n");
+ return 0;
+ }
+ program->superFamicom.location = bsx_bios;
+ program->bsMemory.location = string(gameinfo.path);
+ }
+ else {
+ program->superFamicom.location = string(gameinfo.path);
+ }
+
+ program->base_name = string(gameinfo.path);
+ program->load();
+
+ // Set up inputs
+ string title = program->superFamicom.title;
+ bool superscope = false;
+
+ // There must be a better way of doing this. But nall must be killed first.
+ // The requirement for offsets suggests something broken at deeper levels.
+ if (title == "BAZOOKA BLITZKRIEG" || title == "SFC DESTRUCTIVE") {
+ superscope = true;
+ ss_offset_x = 1;
+ ss_offset_y = 19;
+ }
+ else if(title == "T2 ARCADE") {
+ superscope = true;
+ ss_offset_x = 36;
+ ss_offset_y = -14;
+ }
+ else if(title == "X ZONE") {
+ superscope = true;
+ ss_offset_x = 40;
+ ss_offset_y = -7;
+ }
+ // Games that do not require an offset
+ else if (title == "BATTLE CLASH" || title == "Hunt for Red October" ||
+ title == "LAMBORGHINI AMERICAN" || title == "METAL COMBAT" ||
+ title == "OPERATION THUNDERBOLT" || title == "SUPER SCOPE 6" ||
+ title == "TINSTAR" || title == "YOSHI'S SAFARI") {
+ superscope = true;
+ }
+
+ // Default input devices are SNES Controllers
+ inputinfo[0] = (jg_inputinfo_t){JG_INPUT_CONTROLLER, 0,
+ "pad1", "Controller 1", defs_snespad, 0, NDEFS_SNESPAD};
+ emulator->connect(SuperFamicom::ID::Port::Controller1,
+ SuperFamicom::ID::Device::Gamepad);
+
+ inputinfo[1] = (jg_inputinfo_t){JG_INPUT_CONTROLLER, 1,
+ "pad2", "Controller 2", defs_snespad, 0, NDEFS_SNESPAD};
+ emulator->connect(SuperFamicom::ID::Port::Controller2,
+ SuperFamicom::ID::Device::Gamepad);//*/
+
+ /*inputinfo[0] = (jg_inputinfo_t){JG_INPUT_POINTER, 0,
+ "mouse", "SNES Mouse", defs_snesmouse, 0, NDEFS_SNESMOUSE};
+ emulator->connect(SuperFamicom::ID::Port::Controller1,
+ SuperFamicom::ID::Device::Mouse);//*/
+
+ // Plug in a Super Scope if the game supports it
+ if (superscope) {
+ inputinfo[1] = (jg_inputinfo_t){JG_INPUT_GUN, 1,
+ "superscope", "Super Scope", defs_superscope, 0, NDEFS_SUPERSCOPE};
+ emulator->connect(SuperFamicom::ID::Port::Controller2,
+ SuperFamicom::ID::Device::SuperScope);
+ }
+
+ // Justifier support appears fundamentally broken at the core level
+ /*inputinfo[1] = (jg_inputinfo_t){JG_INPUT_GUN, 1,
+ "justifier", "Justifier", defs_snesjustifier, 0, NDEFS_SNESJUSTIFIER};
+ emulator->connect(SuperFamicom::ID::Port::Controller2,
+ SuperFamicom::ID::Device::Justifier);*/
+
+ // Set the aspect ratio
+ if (settings_bsnes[ASPECT].value)
+ vidinfo.aspect = program->superFamicom.region == "PAL" ?
+ ASPECT_PAL : ASPECT_NTSC;
+
+ // Audio and timing adjustments
+ if (program->superFamicom.region == "PAL") {
+ audinfo.spf = (SAMPLERATE / FRAMERATE_PAL) * CHANNELS;
+ jg_cb_frametime(TIMING_PAL);
+ }
+ else {
+ jg_cb_frametime(TIMING_NTSC);
+ }
+
+ emulator->power(); // Power up!
+
+ return 1;
+}
+
+int jg_game_unload() {
+ program->save();
+ emulator->unload();
+ return 1;
+}
+
+int jg_state_load(const char *filename) {
+ vector memory;
+ memory = file::read(filename);
+ auto serializerRLE = Decode::RLE<1>({memory.data() + 3 * sizeof(uint),
+ memory.size() - 3 * sizeof(uint)});
+ serializer s{serializerRLE.data(), (uint)serializerRLE.size()};
+ return emulator->unserialize(s);
+}
+
+int jg_state_save(const char *filename) {
+ serializer s = emulator->serialize();
+ if (!s.size()) return 0;
+
+ auto serializerRLE = Encode::RLE<1>({s.data(), s.size()});
+
+ vector saveState;
+ saveState.resize(3 * sizeof(uint));
+ memory::writel(saveState.data() + 0 * sizeof(uint),
+ 0x5a22'0000);
+ memory::writel(saveState.data() + 1 * sizeof(uint),
+ serializerRLE.size());
+ memory::writel(saveState.data() + 2 * sizeof(uint), 0);
+ saveState.append(serializerRLE);
+
+ return file::write(filename, saveState);
+}
+
+void jg_media_select() {
+}
+
+void jg_media_insert() {
+}
+
+// JG Functions that return values to the frontend
+jg_coreinfo_t* jg_get_coreinfo(const char *sys) { return &coreinfo; }
+jg_videoinfo_t* jg_get_videoinfo() { return &vidinfo; }
+jg_audioinfo_t* jg_get_audioinfo() { return &audinfo; }
+jg_inputinfo_t* jg_get_inputinfo(int port) { return &inputinfo[port]; }
+
+void jg_setup_video() { }
+void jg_setup_audio() { }
+
+void jg_set_inputstate(jg_inputstate_t *ptr, int port) {
+ input_device[port] = ptr;
+}
+
+void jg_set_gameinfo(jg_gameinfo_t info) { gameinfo = info; }
+void jg_set_paths(jg_pathinfo_t paths) { pathinfo = paths; }
diff --git a/libco/aarch64.c b/libco/aarch64.c
new file mode 100644
index 0000000..2132b4e
--- /dev/null
+++ b/libco/aarch64.c
@@ -0,0 +1,109 @@
+#define LIBCO_C
+#include "libco.h"
+#include "settings.h"
+
+#include
+#include
+#include
+#ifdef LIBCO_MPROTECT
+ #include
+ #include
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static thread_local unsigned long co_active_buffer[64];
+static thread_local cothread_t co_active_handle = 0;
+static void (*co_swap)(cothread_t, cothread_t) = 0;
+
+#ifdef LIBCO_MPROTECT
+ alignas(4096)
+#else
+ section(text)
+#endif
+static const uint32_t co_swap_function[1024] = {
+ 0xa9002428, /* stp x8,x9,[x1] */
+ 0xa9012c2a, /* stp x10,x11,[x1,#16] */
+ 0xa902342c, /* stp x12,x13,[x1,#32] */
+ 0xa9033c2e, /* stp x14,x15,[x1,#48] */
+ 0xf9002433, /* str x19,[x1,#72] */
+ 0xa9055434, /* stp x20,x21,[x1,#80] */
+ 0xa9065c36, /* stp x22,x23,[x1,#96] */
+ 0xa9076438, /* stp x24,x25,[x1,#112] */
+ 0xa9086c3a, /* stp x26,x27,[x1,#128] */
+ 0xa909743c, /* stp x28,x29,[x1,#144] */
+ 0x910003f0, /* mov x16,sp */
+ 0xa90a7830, /* stp x16,x30,[x1,#160] */
+
+ 0xa9402408, /* ldp x8,x9,[x0] */
+ 0xa9412c0a, /* ldp x10,x11,[x0,#16] */
+ 0xa942340c, /* ldp x12,x13,[x0,#32] */
+ 0xa9433c0e, /* ldp x14,x15,[x0,#48] */
+ 0xf9402413, /* ldr x19,[x0,#72] */
+ 0xa9455414, /* ldp x20,x21,[x0,#80] */
+ 0xa9465c16, /* ldp x22,x23,[x0,#96] */
+ 0xa9476418, /* ldp x24,x25,[x0,#112] */
+ 0xa9486c1a, /* ldp x26,x27,[x0,#128] */
+ 0xa949741c, /* ldp x28,x29,[x0,#144] */
+ 0xa94a4410, /* ldp x16,x17,[x0,#160] */
+ 0x9100021f, /* mov sp,x16 */
+ 0xd61f0220, /* br x17 */
+};
+
+static void co_init() {
+ #ifdef LIBCO_MPROTECT
+ unsigned long addr = (unsigned long)co_swap_function;
+ unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE));
+ unsigned long size = (addr - base) + sizeof co_swap_function;
+ mprotect((void*)base, size, PROT_READ | PROT_EXEC);
+ #endif
+}
+
+cothread_t co_active() {
+ if(!co_active_handle) co_active_handle = &co_active_buffer;
+ return co_active_handle;
+}
+
+cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
+ unsigned long* handle;
+ if(!co_swap) {
+ co_init();
+ co_swap = (void (*)(cothread_t, cothread_t))co_swap_function;
+ }
+ if(!co_active_handle) co_active_handle = &co_active_buffer;
+
+ if(handle = (unsigned long*)memory) {
+ unsigned int offset = (size & ~15);
+ unsigned long* p = (unsigned long*)((unsigned char*)handle + offset);
+ handle[19] = (unsigned long)p; /* x29 (frame pointer) */
+ handle[20] = (unsigned long)p; /* x30 (stack pointer) */
+ handle[21] = (unsigned long)entrypoint; /* x31 (link register) */
+ }
+
+ return handle;
+}
+
+cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
+ void* memory = malloc(size);
+ if(!memory) return (cothread_t)0;
+ return co_derive(memory, size, entrypoint);
+}
+
+void co_delete(cothread_t handle) {
+ free(handle);
+}
+
+void co_switch(cothread_t handle) {
+ cothread_t co_previous_handle = co_active_handle;
+ co_swap(co_active_handle = handle, co_previous_handle);
+}
+
+int co_serializable() {
+ return 1;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libco/amd64.c b/libco/amd64.c
new file mode 100755
index 0000000..9d827d3
--- /dev/null
+++ b/libco/amd64.c
@@ -0,0 +1,165 @@
+#define LIBCO_C
+#include "libco.h"
+#include "settings.h"
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static thread_local long long co_active_buffer[64];
+static thread_local cothread_t co_active_handle = 0;
+static void (*co_swap)(cothread_t, cothread_t) = 0;
+
+#ifdef LIBCO_MPROTECT
+ alignas(4096)
+#else
+ section(text)
+#endif
+#ifdef _WIN32
+ /* ABI: Win64 */
+ static const unsigned char co_swap_function[4096] = {
+ 0x48, 0x89, 0x22, /* mov [rdx],rsp */
+ 0x48, 0x8b, 0x21, /* mov rsp,[rcx] */
+ 0x58, /* pop rax */
+ 0x48, 0x89, 0x6a, 0x08, /* mov [rdx+ 8],rbp */
+ 0x48, 0x89, 0x72, 0x10, /* mov [rdx+16],rsi */
+ 0x48, 0x89, 0x7a, 0x18, /* mov [rdx+24],rdi */
+ 0x48, 0x89, 0x5a, 0x20, /* mov [rdx+32],rbx */
+ 0x4c, 0x89, 0x62, 0x28, /* mov [rdx+40],r12 */
+ 0x4c, 0x89, 0x6a, 0x30, /* mov [rdx+48],r13 */
+ 0x4c, 0x89, 0x72, 0x38, /* mov [rdx+56],r14 */
+ 0x4c, 0x89, 0x7a, 0x40, /* mov [rdx+64],r15 */
+ #if !defined(LIBCO_NO_SSE)
+ 0x0f, 0x29, 0x72, 0x50, /* movaps [rdx+ 80],xmm6 */
+ 0x0f, 0x29, 0x7a, 0x60, /* movaps [rdx+ 96],xmm7 */
+ 0x44, 0x0f, 0x29, 0x42, 0x70, /* movaps [rdx+112],xmm8 */
+ 0x48, 0x83, 0xc2, 0x70, /* add rdx,112 */
+ 0x44, 0x0f, 0x29, 0x4a, 0x10, /* movaps [rdx+ 16],xmm9 */
+ 0x44, 0x0f, 0x29, 0x52, 0x20, /* movaps [rdx+ 32],xmm10 */
+ 0x44, 0x0f, 0x29, 0x5a, 0x30, /* movaps [rdx+ 48],xmm11 */
+ 0x44, 0x0f, 0x29, 0x62, 0x40, /* movaps [rdx+ 64],xmm12 */
+ 0x44, 0x0f, 0x29, 0x6a, 0x50, /* movaps [rdx+ 80],xmm13 */
+ 0x44, 0x0f, 0x29, 0x72, 0x60, /* movaps [rdx+ 96],xmm14 */
+ 0x44, 0x0f, 0x29, 0x7a, 0x70, /* movaps [rdx+112],xmm15 */
+ #endif
+ 0x48, 0x8b, 0x69, 0x08, /* mov rbp,[rcx+ 8] */
+ 0x48, 0x8b, 0x71, 0x10, /* mov rsi,[rcx+16] */
+ 0x48, 0x8b, 0x79, 0x18, /* mov rdi,[rcx+24] */
+ 0x48, 0x8b, 0x59, 0x20, /* mov rbx,[rcx+32] */
+ 0x4c, 0x8b, 0x61, 0x28, /* mov r12,[rcx+40] */
+ 0x4c, 0x8b, 0x69, 0x30, /* mov r13,[rcx+48] */
+ 0x4c, 0x8b, 0x71, 0x38, /* mov r14,[rcx+56] */
+ 0x4c, 0x8b, 0x79, 0x40, /* mov r15,[rcx+64] */
+ #if !defined(LIBCO_NO_SSE)
+ 0x0f, 0x28, 0x71, 0x50, /* movaps xmm6, [rcx+ 80] */
+ 0x0f, 0x28, 0x79, 0x60, /* movaps xmm7, [rcx+ 96] */
+ 0x44, 0x0f, 0x28, 0x41, 0x70, /* movaps xmm8, [rcx+112] */
+ 0x48, 0x83, 0xc1, 0x70, /* add rcx,112 */
+ 0x44, 0x0f, 0x28, 0x49, 0x10, /* movaps xmm9, [rcx+ 16] */
+ 0x44, 0x0f, 0x28, 0x51, 0x20, /* movaps xmm10,[rcx+ 32] */
+ 0x44, 0x0f, 0x28, 0x59, 0x30, /* movaps xmm11,[rcx+ 48] */
+ 0x44, 0x0f, 0x28, 0x61, 0x40, /* movaps xmm12,[rcx+ 64] */
+ 0x44, 0x0f, 0x28, 0x69, 0x50, /* movaps xmm13,[rcx+ 80] */
+ 0x44, 0x0f, 0x28, 0x71, 0x60, /* movaps xmm14,[rcx+ 96] */
+ 0x44, 0x0f, 0x28, 0x79, 0x70, /* movaps xmm15,[rcx+112] */
+ #endif
+ 0xff, 0xe0, /* jmp rax */
+ };
+
+ #include
+
+ static void co_init() {
+ #ifdef LIBCO_MPROTECT
+ DWORD old_privileges;
+ VirtualProtect((void*)co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READ, &old_privileges);
+ #endif
+ }
+#else
+ /* ABI: SystemV */
+ static const unsigned char co_swap_function[4096] = {
+ 0x48, 0x89, 0x26, /* mov [rsi],rsp */
+ 0x48, 0x8b, 0x27, /* mov rsp,[rdi] */
+ 0x58, /* pop rax */
+ 0x48, 0x89, 0x6e, 0x08, /* mov [rsi+ 8],rbp */
+ 0x48, 0x89, 0x5e, 0x10, /* mov [rsi+16],rbx */
+ 0x4c, 0x89, 0x66, 0x18, /* mov [rsi+24],r12 */
+ 0x4c, 0x89, 0x6e, 0x20, /* mov [rsi+32],r13 */
+ 0x4c, 0x89, 0x76, 0x28, /* mov [rsi+40],r14 */
+ 0x4c, 0x89, 0x7e, 0x30, /* mov [rsi+48],r15 */
+ 0x48, 0x8b, 0x6f, 0x08, /* mov rbp,[rdi+ 8] */
+ 0x48, 0x8b, 0x5f, 0x10, /* mov rbx,[rdi+16] */
+ 0x4c, 0x8b, 0x67, 0x18, /* mov r12,[rdi+24] */
+ 0x4c, 0x8b, 0x6f, 0x20, /* mov r13,[rdi+32] */
+ 0x4c, 0x8b, 0x77, 0x28, /* mov r14,[rdi+40] */
+ 0x4c, 0x8b, 0x7f, 0x30, /* mov r15,[rdi+48] */
+ 0xff, 0xe0, /* jmp rax */
+ };
+
+ #ifdef LIBCO_MPROTECT
+ #include
+ #include
+ #endif
+
+ static void co_init() {
+ #ifdef LIBCO_MPROTECT
+ unsigned long long addr = (unsigned long long)co_swap_function;
+ unsigned long long base = addr - (addr % sysconf(_SC_PAGESIZE));
+ unsigned long long size = (addr - base) + sizeof co_swap_function;
+ mprotect((void*)base, size, PROT_READ | PROT_EXEC);
+ #endif
+ }
+#endif
+
+static void crash() {
+ assert(0); /* called only if cothread_t entrypoint returns */
+}
+
+cothread_t co_active() {
+ if(!co_active_handle) co_active_handle = &co_active_buffer;
+ return co_active_handle;
+}
+
+cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
+ cothread_t handle;
+ if(!co_swap) {
+ co_init();
+ co_swap = (void (*)(cothread_t, cothread_t))co_swap_function;
+ }
+ if(!co_active_handle) co_active_handle = &co_active_buffer;
+
+ if((handle = (cothread_t)memory)) {
+ unsigned int offset = (size & ~15) - 32;
+ long long *p = (long long*)((char*)handle + offset); /* seek to top of stack */
+ *--p = (long long)crash; /* crash if entrypoint returns */
+ *--p = (long long)entrypoint; /* start of function */
+ *(long long*)handle = (long long)p; /* stack pointer */
+ }
+
+ return handle;
+}
+
+cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
+ void* memory = malloc(size);
+ if(!memory) return (cothread_t)0;
+ return co_derive(memory, size, entrypoint);
+}
+
+void co_delete(cothread_t handle) {
+ free(handle);
+}
+
+void co_switch(cothread_t handle) {
+ register cothread_t co_previous_handle = co_active_handle;
+ co_swap(co_active_handle = handle, co_previous_handle);
+}
+
+int co_serializable() {
+ return 1;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libco/arm.c b/libco/arm.c
new file mode 100644
index 0000000..ce5970e
--- /dev/null
+++ b/libco/arm.c
@@ -0,0 +1,84 @@
+#define LIBCO_C
+#include "libco.h"
+#include "settings.h"
+
+#include
+#include
+#ifdef LIBCO_MPROTECT
+ #include
+ #include
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static thread_local unsigned long co_active_buffer[64];
+static thread_local cothread_t co_active_handle = 0;
+static void (*co_swap)(cothread_t, cothread_t) = 0;
+
+#ifdef LIBCO_MPROTECT
+ alignas(4096)
+#else
+ section(text)
+#endif
+static const unsigned long co_swap_function[1024] = {
+ 0xe8a16ff0, /* stmia r1!, {r4-r11,sp,lr} */
+ 0xe8b0aff0, /* ldmia r0!, {r4-r11,sp,pc} */
+ 0xe12fff1e, /* bx lr */
+};
+
+static void co_init() {
+ #ifdef LIBCO_MPROTECT
+ unsigned long addr = (unsigned long)co_swap_function;
+ unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE));
+ unsigned long size = (addr - base) + sizeof co_swap_function;
+ mprotect((void*)base, size, PROT_READ | PROT_EXEC);
+ #endif
+}
+
+cothread_t co_active() {
+ if(!co_active_handle) co_active_handle = &co_active_buffer;
+ return co_active_handle;
+}
+
+cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
+ unsigned long* handle;
+ if(!co_swap) {
+ co_init();
+ co_swap = (void (*)(cothread_t, cothread_t))co_swap_function;
+ }
+ if(!co_active_handle) co_active_handle = &co_active_buffer;
+
+ if((handle = (unsigned long*)memory)) {
+ unsigned int offset = (size & ~15);
+ unsigned long* p = (unsigned long*)((unsigned char*)handle + offset);
+ handle[8] = (unsigned long)p;
+ handle[9] = (unsigned long)entrypoint;
+ }
+
+ return handle;
+}
+
+cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
+ void* memory = malloc(size);
+ if(!memory) return (cothread_t)0;
+ return co_derive(memory, size, entrypoint);
+}
+
+void co_delete(cothread_t handle) {
+ free(handle);
+}
+
+void co_switch(cothread_t handle) {
+ cothread_t co_previous_handle = co_active_handle;
+ co_swap(co_active_handle = handle, co_previous_handle);
+}
+
+int co_serializable() {
+ return 1;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libco/doc/style.css b/libco/doc/style.css
new file mode 100755
index 0000000..ab07025
--- /dev/null
+++ b/libco/doc/style.css
@@ -0,0 +1,12 @@
+body {
+ background: #333;
+ color: #fff;
+}
+
+code {
+ background: #444;
+}
+
+a {
+ color: #aaf;
+}
diff --git a/libco/doc/targets.html b/libco/doc/targets.html
new file mode 100755
index 0000000..d6211a1
--- /dev/null
+++ b/libco/doc/targets.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+Supported targets:
+
+Note that supported targets are only those that have been tested and confirmed
+working. It is quite possible that libco will work on more processors, compilers
+and operating systems than those listed below.
+
+
+libco.x86
+Overhead: ~5x
+Supported processor(s): 32-bit x86
+Supported compiler(s): any
+Supported operating system(s):
+Windows
+Mac OS X
+Linux
+BSD
+
+
+
+libco.amd64
+Overhead: ~10x (Windows), ~6x (all other platforms)
+Supported processor(s): 64-bit amd64
+Supported compiler(s): any
+Supported operating system(s):
+Windows
+Mac OS X
+Linux
+BSD
+
+
+
+libco.ppc
+Overhead: ~20x
+Supported processor(s): 32-bit PowerPC, 64-bit PowerPC
+Supported compiler(s): GNU GCC
+Supported operating system(s):
+Mac OS X
+Linux
+BSD
+Playstation 3
+
+
+
+Note: this module contains compiler flags to enable/disable FPU and Altivec
+support.
+
+
+
+libco.fiber
+Overhead: ~15x
+Supported processor(s): Processor independent
+Supported compiler(s): any
+Supported operating system(s):
+
+
+libco.sjlj
+Overhead: ~30x
+Supported processor(s): Processor independent
+Supported compiler(s): any
+Supported operating system(s):
+Mac OS X
+Linux
+BSD
+Solaris
+
+
+
+libco.ucontext
+Overhead: ~300x
+Supported processor(s): Processor independent
+Supported compiler(s): any
+Supported operating system(s):
+
+
+
+
diff --git a/libco/doc/usage.html b/libco/doc/usage.html
new file mode 100755
index 0000000..994072f
--- /dev/null
+++ b/libco/doc/usage.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+License:
+libco is released under the ISC license.
+
+
+Foreword:
+libco is a cross-platform, permissively licensed implementation of
+cooperative-multithreading; a feature that is sorely lacking from the ISO C/C++
+standard.
+The library is designed for maximum speed and portability, and not for safety or
+features. If safety or extra functionality is desired, a wrapper API can easily
+be written to encapsulate all library functions.
+Behavior of executing operations that are listed as not permitted below result
+in undefined behavior. They may work anyway, they may cause undesired / unknown
+behavior, or they may crash the program entirely.
+The goal of this library was to simplify the base API as much as possible,
+implementing only that which cannot be implemented using pure C. Additional
+functionality after this would only complicate ports of this library to new
+platforms.
+
+
+Porting:
+This document is included as a reference for porting libco. Please submit any
+ports you create to me, so that libco can become more useful. Please note that
+since libco is permissively licensed, you must submit your code as a work of the
+public domain in order for it to be included in the official distribution.
+Full credit will be given in the source code of the official release. Please
+do not bother submitting code to me under any other license -- including GPL,
+LGPL, BSD or CC -- I am not interested in creating a library with multiple
+different licenses depending on which targets are used.
+
+
+Synopsis:
+
+typedef void* cothread_t;
+
+cothread_t co_active();
+cothread_t co_create(unsigned int heapsize, void (*coentry)(void));
+void co_delete(cothread_t cothread);
+void co_switch(cothread_t cothread);
+
+
+
+Usage:
+
+
+typedef void* cothread_t;
+Handle to cothread.
+Handle must be of type void*.
+A value of null (0) indicates an uninitialized or invalid
+handle, whereas a non-zero value indicates a valid handle.
+
+
+cothread_t co_active();
+Return handle to current cothread. Always returns a valid handle, even when
+called from the main program thread.
+
+
+cothread_t co_derive(void* memory, unsigned int heapsize, void (*coentry)(void));
+Initializes new cothread.
+This function is identical to co_create, only it attempts to use the provided
+memory instead of allocating new memory on the heap. Please note that certain
+implementations (currently only Windows Fibers) cannot be created using existing
+memory, and as such, this function will fail.
+
+
+cothread_t co_create(unsigned int heapsize, void (*coentry)(void));
+Create new cothread.
+Heapsize is the amount of memory allocated for the cothread stack, specified
+in bytes. This is unfortunately impossible to make fully portable. It is
+recommended to specify sizes using `n * sizeof(void*)'. It is better to err
+on the side of caution and allocate more memory than will be needed to ensure
+compatibility with other platforms, within reason. A typical heapsize for a
+32-bit architecture is ~1MB.
+When the new cothread is first called, program execution jumps to coentry.
+This function does not take any arguments, due to portability issues with
+passing function arguments. However, arguments can be simulated by the use
+of global variables, which can be set before the first call to each cothread.
+coentry() must not return, and should end with an appropriate co_switch()
+statement. Behavior is undefined if entry point returns normally.
+Library is responsible for allocating cothread stack memory, to free
+the user from needing to allocate special memory capable of being used
+as program stack memory on platforms where this is required.
+User is always responsible for deleting cothreads with co_delete().
+Return value of null (0) indicates cothread creation failed.
+
+
+void co_delete(cothread_t cothread);
+Delete specified cothread.
+Null (0) or invalid cothread handle is not allowed.
+Passing handle of active cothread to this function is not allowed.
+Passing handle of primary cothread is not allowed.
+
+
+void co_switch(cothread_t cothread);
+Switch to specified cothread.
+Null (0) or invalid cothread handle is not allowed.
+Passing handle of active cothread to this function is not allowed.
+
+
+
+
diff --git a/libco/fiber.c b/libco/fiber.c
new file mode 100755
index 0000000..dd539c3
--- /dev/null
+++ b/libco/fiber.c
@@ -0,0 +1,55 @@
+#define LIBCO_C
+#include "libco.h"
+#include "settings.h"
+
+#define WINVER 0x0400
+#define _WIN32_WINNT 0x0400
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static thread_local cothread_t co_active_ = 0;
+
+static void __stdcall co_thunk(void* coentry) {
+ ((void (*)(void))coentry)();
+}
+
+cothread_t co_active() {
+ if(!co_active_) {
+ ConvertThreadToFiber(0);
+ co_active_ = GetCurrentFiber();
+ }
+ return co_active_;
+}
+
+cothread_t co_derive(void* memory, unsigned int heapsize, void (*coentry)(void)) {
+ //Windows fibers do not allow users to supply their own memory
+ return (cothread_t)0;
+}
+
+cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) {
+ if(!co_active_) {
+ ConvertThreadToFiber(0);
+ co_active_ = GetCurrentFiber();
+ }
+ return (cothread_t)CreateFiber(heapsize, co_thunk, (void*)coentry);
+}
+
+void co_delete(cothread_t cothread) {
+ DeleteFiber(cothread);
+}
+
+void co_switch(cothread_t cothread) {
+ co_active_ = cothread;
+ SwitchToFiber(cothread);
+}
+
+int co_serializable() {
+ return 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/libco/libco.c b/libco/libco.c
new file mode 100755
index 0000000..21fe4ca
--- /dev/null
+++ b/libco/libco.c
@@ -0,0 +1,37 @@
+#if defined(__clang__)
+ #pragma clang diagnostic ignored "-Wparentheses"
+
+ /* placing code in section(text) does not mark it executable with Clang. */
+ #undef LIBCO_MPROTECT
+ #define LIBCO_MPROTECT
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+ #if defined(__i386__)
+ #include "x86.c"
+ #elif defined(__amd64__)
+ #include "amd64.c"
+ #elif defined(__arm__)
+ #include "arm.c"
+ #elif defined(__aarch64__)
+ #include "aarch64.c"
+ #elif defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
+ #include "ppc64v2.c"
+ #elif defined(_ARCH_PPC) && !defined(__LITTLE_ENDIAN__)
+ #include "ppc.c"
+ #elif defined(_WIN32)
+ #include "fiber.c"
+ #else
+ #include "sjlj.c"
+ #endif
+#elif defined(_MSC_VER)
+ #if defined(_M_IX86)
+ #include "x86.c"
+ #elif defined(_M_AMD64)
+ #include "amd64.c"
+ #else
+ #include "fiber.c"
+ #endif
+#else
+ #error "libco: unsupported processor, compiler or operating system"
+#endif
diff --git a/libco/libco.h b/libco/libco.h
new file mode 100755
index 0000000..88d00a7
--- /dev/null
+++ b/libco/libco.h
@@ -0,0 +1,28 @@
+/*
+ libco v20 (2019-10-16)
+ author: byuu
+ license: ISC
+*/
+
+#ifndef LIBCO_H
+#define LIBCO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void* cothread_t;
+
+cothread_t co_active();
+cothread_t co_derive(void*, unsigned int, void (*)(void));
+cothread_t co_create(unsigned int, void (*)(void));
+void co_delete(cothread_t);
+void co_switch(cothread_t);
+int co_serializable();
+
+#ifdef __cplusplus
+}
+#endif
+
+/* ifndef LIBCO_H */
+#endif
diff --git a/libco/ppc.c b/libco/ppc.c
new file mode 100755
index 0000000..ee6a9a8
--- /dev/null
+++ b/libco/ppc.c
@@ -0,0 +1,431 @@
+/* ppc64le (ELFv2) is not currently supported */
+
+#define LIBCO_C
+#include "libco.h"
+#include "settings.h"
+
+#include
+#include