mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-19 12:51:47 +02:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ae5968cfeb | ||
|
b03563426f | ||
|
f500426158 | ||
|
8499c64756 | ||
|
26bd7590ad | ||
|
21ee597aae | ||
|
bf90bdfcc8 | ||
|
0ad70a30f8 | ||
|
79c83ade70 | ||
|
a3aea95e6b | ||
|
569f5abc28 | ||
|
5bdf55f08f | ||
|
e30780bb72 | ||
|
bab2ac812a | ||
|
1d7b674dd4 | ||
|
c2c957a9da | ||
|
8cf20dabbf | ||
|
2707c5316d | ||
|
f3e67da937 | ||
|
c6fc15f8d2 | ||
|
d6e9d94ec3 | ||
|
2fbbccf985 | ||
|
4c3f58150c | ||
|
d91f3999cc | ||
|
7c96826eb0 | ||
|
5df717ff2a | ||
|
f7ddbfc462 | ||
|
0b70a01b47 | ||
|
4d2e17f9c0 | ||
|
043f6a8b33 | ||
|
ffd150735b | ||
|
427bac3011 | ||
|
ac2d0ba1cf | ||
|
1df2549d18 | ||
|
9b8c3ff8c0 | ||
|
0a57cac70c | ||
|
8bdf8f2a55 |
47
.gitlab-ci.yml
Normal file
47
.gitlab-ci.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
# NOTE: This file is not part of the official higan source, it's been added
|
||||
# to help build WIP binaries with minimal fuss.
|
||||
|
||||
image: debian:stable
|
||||
|
||||
linux-x86_64-binaries:
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential libgtk2.0-dev libpulse-dev mesa-common-dev libgtksourceview2.0-dev libcairo2-dev libsdl1.2-dev libxv-dev libao-dev libopenal-dev libudev-dev
|
||||
- make -C icarus compiler=g++
|
||||
- make -C higan compiler=g++
|
||||
- mkdir higan-nightly
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a higan/out/higan higan-nightly/higan
|
||||
- cp -a higan/data/cheats.bml higan-nightly/
|
||||
- cp -a higan/systems/* higan-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- higan-nightly/*
|
||||
cache:
|
||||
paths:
|
||||
- icarus/obj/*.o
|
||||
- higan/obj/*.o
|
||||
|
||||
windows-x86_64-binaries:
|
||||
# This is a normal Windows cross-compile process, except that
|
||||
# nall::chrono tries to use clock_gettime on Windows even
|
||||
# though it's a POSIX function, and for some weird reason mingw has
|
||||
# clock_gettime() in the pthread library.
|
||||
script:
|
||||
- apt-get update && apt-get -y install build-essential mingw-w64
|
||||
- sed -i -e 's/-lole32/& -static -lpthread/' nall/GNUmakefile
|
||||
- make -C icarus platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
|
||||
- make -C higan platform=windows compiler="x86_64-w64-mingw32-g++ -static-libgcc -static-libstdc++" windres="x86_64-w64-mingw32-windres"
|
||||
- mkdir higan-nightly
|
||||
- cp -a icarus/out/icarus higan-nightly/icarus.exe
|
||||
- cp -a icarus/Database higan-nightly/
|
||||
- cp -a higan/out/higan higan-nightly/higan.exe
|
||||
- cp -a higan/data/cheats.bml higan-nightly/
|
||||
- cp -a higan/systems/* higan-nightly/
|
||||
artifacts:
|
||||
paths:
|
||||
- higan-nightly/*
|
||||
cache:
|
||||
paths:
|
||||
- icarus/obj/*.o
|
||||
- higan/obj/*.o
|
@@ -4,7 +4,7 @@ target := tomoko
|
||||
# console := true
|
||||
|
||||
flags += -I. -I.. -O3
|
||||
objects := libco audio video resource
|
||||
objects := libco emulator audio video resource
|
||||
|
||||
# profile-guided optimization mode
|
||||
# pgo := instrument
|
||||
@@ -24,7 +24,7 @@ ifeq ($(platform),windows)
|
||||
else
|
||||
link += -mwindows
|
||||
endif
|
||||
link += -mthreads -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -mthreads -lpthread -luuid -lkernel32 -luser32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32
|
||||
link += -Wl,-enable-auto-import
|
||||
link += -Wl,-enable-runtime-pseudo-reloc
|
||||
else ifeq ($(platform),macosx)
|
||||
@@ -52,10 +52,11 @@ compile = \
|
||||
|
||||
all: build;
|
||||
|
||||
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco/)
|
||||
obj/audio.o: audio/audio.cpp $(call rwildcard,audio/)
|
||||
obj/video.o: video/video.cpp $(call rwildcard,video/)
|
||||
obj/resource.o: resource/resource.cpp $(call rwildcard,resource/)
|
||||
obj/libco.o: ../libco/libco.c $(call rwildcard,../libco)
|
||||
obj/emulator.o: emulator/emulator.cpp $(call rwildcard,emulator)
|
||||
obj/audio.o: audio/audio.cpp $(call rwildcard,audio)
|
||||
obj/video.o: video/video.cpp $(call rwildcard,video)
|
||||
obj/resource.o: resource/resource.cpp $(call rwildcard,resource)
|
||||
|
||||
ui := target-$(target)
|
||||
include $(ui)/GNUmakefile
|
||||
|
@@ -6,6 +6,8 @@ namespace Emulator {
|
||||
Audio audio;
|
||||
|
||||
auto Audio::reset(maybe<uint> channels_, maybe<double> frequency_) -> void {
|
||||
interface = nullptr;
|
||||
|
||||
if(channels_) channels = channels_();
|
||||
if(frequency_) frequency = frequency_();
|
||||
|
||||
@@ -81,7 +83,7 @@ auto Audio::process() -> void {
|
||||
if(balance > 0.0) samples[0] *= 1.0 - balance;
|
||||
}
|
||||
|
||||
interface->audioSample(samples, channels);
|
||||
platform->audioSample(samples, channels);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,7 +11,7 @@ struct Stream;
|
||||
|
||||
struct Audio {
|
||||
auto reset(maybe<uint> channels = nothing, maybe<double> frequency = nothing) -> void;
|
||||
auto setInterface(Interface*) -> void;
|
||||
auto setInterface(Interface* interface) -> void;
|
||||
|
||||
auto setVolume(double volume) -> void;
|
||||
auto setBalance(double balance) -> void;
|
||||
|
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#define debug(id, ...) if(debugger.id) debugger.id(__VA_ARGS__)
|
||||
#define privileged public
|
||||
#else
|
||||
#define debug(id, ...)
|
||||
#define privileged private
|
||||
#endif
|
||||
|
||||
}
|
7
higan/emulator/emulator.cpp
Normal file
7
higan/emulator/emulator.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include <emulator/emulator.hpp>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
Platform* platform = nullptr;
|
||||
|
||||
}
|
@@ -4,6 +4,7 @@
|
||||
#include <nall/vfs.hpp>
|
||||
using namespace nall;
|
||||
|
||||
#include "types.hpp"
|
||||
#include <libco/libco.h>
|
||||
#include <audio/audio.hpp>
|
||||
#include <video/video.hpp>
|
||||
@@ -11,7 +12,7 @@ using namespace nall;
|
||||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "101";
|
||||
static const string Version = "102";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
@@ -28,4 +29,3 @@ namespace Emulator {
|
||||
}
|
||||
|
||||
#include "interface.hpp"
|
||||
#include "debugger.hpp"
|
||||
|
@@ -2,14 +2,23 @@
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct Platform {
|
||||
virtual auto path(uint id) -> string { return ""; }
|
||||
virtual auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return {}; }
|
||||
virtual auto load(uint id, string name, string type) -> maybe<uint> { return nothing; }
|
||||
virtual auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {}
|
||||
virtual auto audioSample(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 { print(text, "\n"); }
|
||||
};
|
||||
|
||||
struct Interface {
|
||||
struct Information {
|
||||
string manufacturer;
|
||||
string name;
|
||||
uint width;
|
||||
uint height;
|
||||
bool overscan;
|
||||
double aspectRatio;
|
||||
bool resettable;
|
||||
struct Capability {
|
||||
bool states;
|
||||
@@ -41,35 +50,14 @@ struct Interface {
|
||||
};
|
||||
vector<Port> ports;
|
||||
|
||||
struct Bind {
|
||||
virtual auto path(uint) -> string { return ""; }
|
||||
virtual auto open(uint, string, vfs::file::mode, bool) -> vfs::shared::file { return {}; }
|
||||
virtual auto load(uint, string, string) -> maybe<uint> { return nothing; }
|
||||
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
|
||||
virtual auto audioSample(const double*, uint) -> void {}
|
||||
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
|
||||
virtual auto inputRumble(uint, uint, uint, bool) -> void {}
|
||||
virtual auto dipSettings(Markup::Node) -> uint { return 0; }
|
||||
virtual auto notify(string text) -> void { print(text, "\n"); }
|
||||
};
|
||||
Bind* bind = nullptr;
|
||||
|
||||
//callback bindings (provided by user interface)
|
||||
auto path(uint id) -> string { return bind->path(id); }
|
||||
auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return bind->open(id, name, mode, required); }
|
||||
auto load(uint id, string name, string type) -> maybe<uint> { return bind->load(id, name, type); }
|
||||
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
|
||||
auto audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
|
||||
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
|
||||
auto inputRumble(uint port, uint device, uint input, bool enable) -> void { return bind->inputRumble(port, device, input, enable); }
|
||||
auto dipSettings(Markup::Node node) -> uint { return bind->dipSettings(node); }
|
||||
template<typename... P> auto notify(P&&... p) -> void { return bind->notify({forward<P>(p)...}); }
|
||||
|
||||
//information
|
||||
virtual auto manifest() -> string = 0;
|
||||
virtual auto title() -> string = 0;
|
||||
|
||||
//video information
|
||||
struct VideoSize { uint width, height; };
|
||||
virtual auto videoSize() -> VideoSize = 0;
|
||||
virtual auto videoSize(uint width, uint height, bool arc) -> VideoSize = 0;
|
||||
virtual auto videoFrequency() -> double = 0;
|
||||
virtual auto videoColors() -> uint32 = 0;
|
||||
virtual auto videoColor(uint32 color) -> uint64 = 0;
|
||||
@@ -118,4 +106,6 @@ struct File {
|
||||
static const auto Required = true;
|
||||
};
|
||||
|
||||
extern Platform* platform;
|
||||
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@
|
||||
namespace Emulator {
|
||||
|
||||
struct Thread {
|
||||
enum : uintmax { Second = (uintmax)-1 >> 1 };
|
||||
|
||||
virtual ~Thread() {
|
||||
if(_handle) co_delete(_handle);
|
||||
}
|
||||
@@ -15,7 +17,7 @@ struct Thread {
|
||||
|
||||
auto setFrequency(double frequency) -> void {
|
||||
_frequency = frequency + 0.5;
|
||||
_scalar = ((uintmax)1 << (8 * sizeof(uintmax) - 1)) / _frequency;
|
||||
_scalar = Second / _frequency;
|
||||
}
|
||||
|
||||
auto setScalar(uintmax scalar) -> void {
|
||||
@@ -30,6 +32,7 @@ struct Thread {
|
||||
if(_handle) co_delete(_handle);
|
||||
_handle = co_create(64 * 1024 * sizeof(void*), entrypoint);
|
||||
setFrequency(frequency);
|
||||
setClock(0);
|
||||
}
|
||||
|
||||
inline auto step(uint clocks) -> void {
|
||||
|
129
higan/emulator/types.hpp
Normal file
129
higan/emulator/types.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
using int1 = nall::Integer< 1>;
|
||||
using int2 = nall::Integer< 2>;
|
||||
using int3 = nall::Integer< 3>;
|
||||
using int4 = nall::Integer< 4>;
|
||||
using int5 = nall::Integer< 5>;
|
||||
using int6 = nall::Integer< 6>;
|
||||
using int7 = nall::Integer< 7>;
|
||||
using int8 = nall::Integer< 8>;
|
||||
using int9 = nall::Integer< 9>;
|
||||
using int10 = nall::Integer<10>;
|
||||
using int11 = nall::Integer<11>;
|
||||
using int12 = nall::Integer<12>;
|
||||
using int13 = nall::Integer<13>;
|
||||
using int14 = nall::Integer<14>;
|
||||
using int15 = nall::Integer<15>;
|
||||
using int16 = nall::Integer<16>;
|
||||
using int17 = nall::Integer<17>;
|
||||
using int18 = nall::Integer<18>;
|
||||
using int19 = nall::Integer<19>;
|
||||
using int20 = nall::Integer<20>;
|
||||
using int21 = nall::Integer<21>;
|
||||
using int22 = nall::Integer<22>;
|
||||
using int23 = nall::Integer<23>;
|
||||
using int24 = nall::Integer<24>;
|
||||
using int25 = nall::Integer<25>;
|
||||
using int26 = nall::Integer<26>;
|
||||
using int27 = nall::Integer<27>;
|
||||
using int28 = nall::Integer<28>;
|
||||
using int29 = nall::Integer<29>;
|
||||
using int30 = nall::Integer<30>;
|
||||
using int31 = nall::Integer<31>;
|
||||
using int32 = nall::Integer<32>;
|
||||
using int33 = nall::Integer<33>;
|
||||
using int34 = nall::Integer<34>;
|
||||
using int35 = nall::Integer<35>;
|
||||
using int36 = nall::Integer<36>;
|
||||
using int37 = nall::Integer<37>;
|
||||
using int38 = nall::Integer<38>;
|
||||
using int39 = nall::Integer<39>;
|
||||
using int40 = nall::Integer<40>;
|
||||
using int41 = nall::Integer<41>;
|
||||
using int42 = nall::Integer<42>;
|
||||
using int43 = nall::Integer<43>;
|
||||
using int44 = nall::Integer<44>;
|
||||
using int45 = nall::Integer<45>;
|
||||
using int46 = nall::Integer<46>;
|
||||
using int47 = nall::Integer<47>;
|
||||
using int48 = nall::Integer<48>;
|
||||
using int49 = nall::Integer<49>;
|
||||
using int50 = nall::Integer<50>;
|
||||
using int51 = nall::Integer<51>;
|
||||
using int52 = nall::Integer<52>;
|
||||
using int53 = nall::Integer<53>;
|
||||
using int54 = nall::Integer<54>;
|
||||
using int55 = nall::Integer<55>;
|
||||
using int56 = nall::Integer<56>;
|
||||
using int57 = nall::Integer<57>;
|
||||
using int58 = nall::Integer<58>;
|
||||
using int59 = nall::Integer<59>;
|
||||
using int60 = nall::Integer<60>;
|
||||
using int61 = nall::Integer<61>;
|
||||
using int62 = nall::Integer<62>;
|
||||
using int63 = nall::Integer<63>;
|
||||
using int64 = nall::Integer<64>;
|
||||
|
||||
using uint1 = nall::Natural< 1>;
|
||||
using uint2 = nall::Natural< 2>;
|
||||
using uint3 = nall::Natural< 3>;
|
||||
using uint4 = nall::Natural< 4>;
|
||||
using uint5 = nall::Natural< 5>;
|
||||
using uint6 = nall::Natural< 6>;
|
||||
using uint7 = nall::Natural< 7>;
|
||||
using uint8 = nall::Natural< 8>;
|
||||
using uint9 = nall::Natural< 9>;
|
||||
using uint10 = nall::Natural<10>;
|
||||
using uint11 = nall::Natural<11>;
|
||||
using uint12 = nall::Natural<12>;
|
||||
using uint13 = nall::Natural<13>;
|
||||
using uint14 = nall::Natural<14>;
|
||||
using uint15 = nall::Natural<15>;
|
||||
using uint16 = nall::Natural<16>;
|
||||
using uint17 = nall::Natural<17>;
|
||||
using uint18 = nall::Natural<18>;
|
||||
using uint19 = nall::Natural<19>;
|
||||
using uint20 = nall::Natural<20>;
|
||||
using uint21 = nall::Natural<21>;
|
||||
using uint22 = nall::Natural<22>;
|
||||
using uint23 = nall::Natural<23>;
|
||||
using uint24 = nall::Natural<24>;
|
||||
using uint25 = nall::Natural<25>;
|
||||
using uint26 = nall::Natural<26>;
|
||||
using uint27 = nall::Natural<27>;
|
||||
using uint28 = nall::Natural<28>;
|
||||
using uint29 = nall::Natural<29>;
|
||||
using uint30 = nall::Natural<30>;
|
||||
using uint31 = nall::Natural<31>;
|
||||
using uint32 = nall::Natural<32>;
|
||||
using uint33 = nall::Natural<33>;
|
||||
using uint34 = nall::Natural<34>;
|
||||
using uint35 = nall::Natural<35>;
|
||||
using uint36 = nall::Natural<36>;
|
||||
using uint37 = nall::Natural<37>;
|
||||
using uint38 = nall::Natural<38>;
|
||||
using uint39 = nall::Natural<39>;
|
||||
using uint40 = nall::Natural<40>;
|
||||
using uint41 = nall::Natural<41>;
|
||||
using uint42 = nall::Natural<42>;
|
||||
using uint43 = nall::Natural<43>;
|
||||
using uint44 = nall::Natural<44>;
|
||||
using uint45 = nall::Natural<45>;
|
||||
using uint46 = nall::Natural<46>;
|
||||
using uint47 = nall::Natural<47>;
|
||||
using uint48 = nall::Natural<48>;
|
||||
using uint49 = nall::Natural<49>;
|
||||
using uint50 = nall::Natural<50>;
|
||||
using uint51 = nall::Natural<51>;
|
||||
using uint52 = nall::Natural<52>;
|
||||
using uint53 = nall::Natural<53>;
|
||||
using uint54 = nall::Natural<54>;
|
||||
using uint55 = nall::Natural<55>;
|
||||
using uint56 = nall::Natural<56>;
|
||||
using uint57 = nall::Natural<57>;
|
||||
using uint58 = nall::Natural<58>;
|
||||
using uint59 = nall::Natural<59>;
|
||||
using uint60 = nall::Natural<60>;
|
||||
using uint61 = nall::Natural<61>;
|
||||
using uint62 = nall::Natural<62>;
|
||||
using uint63 = nall::Natural<63>;
|
||||
using uint64 = nall::Natural<64>;
|
@@ -42,22 +42,22 @@ Board::Board(Markup::Node& document) {
|
||||
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
|
||||
|
||||
if(prgrom.name = prom["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), prgrom.name, File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), prgrom.name, File::Read, File::Required)) {
|
||||
fp->read(prgrom.data, min(prgrom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(prgram.name = pram["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), prgram.name, File::Read)) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), prgram.name, File::Read)) {
|
||||
fp->read(prgram.data, min(prgram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(chrrom.name = crom["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), chrrom.name, File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), chrrom.name, File::Read, File::Required)) {
|
||||
fp->read(chrrom.data, min(chrrom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(chrram.name = cram["name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), chrram.name, File::Read)) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), chrram.name, File::Read)) {
|
||||
fp->read(chrram.data, min(chrram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
@@ -70,13 +70,13 @@ auto Board::save() -> void {
|
||||
auto document = BML::unserialize(cartridge.manifest());
|
||||
|
||||
if(auto name = document["board/prg/ram/name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), name, File::Write)) {
|
||||
fp->write(prgram.data, prgram.size);
|
||||
}
|
||||
}
|
||||
|
||||
if(auto name = document["board/chr/ram/name"].text()) {
|
||||
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), name, File::Write)) {
|
||||
fp->write(chrram.data, chrram.size);
|
||||
}
|
||||
}
|
||||
|
@@ -15,11 +15,11 @@ auto Cartridge::main() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
if(auto pathID = interface->load(ID::Famicom, "Famicom", "fc")) {
|
||||
if(auto pathID = platform->load(ID::Famicom, "Famicom", "fc")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
|
||||
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else {
|
||||
return false;
|
||||
@@ -29,8 +29,8 @@ auto Cartridge::load() -> bool {
|
||||
if(!board) return false;
|
||||
|
||||
Hash::SHA256 sha;
|
||||
sha.data(board->prgrom.data, board->prgrom.size);
|
||||
sha.data(board->chrrom.data, board->chrrom.size);
|
||||
sha.input(board->prgrom.data, board->prgrom.size);
|
||||
sha.input(board->chrrom.data, board->chrrom.size);
|
||||
information.sha256 = sha.digest();
|
||||
return true;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ Gamepad::Gamepad(bool port) : Controller(port) {
|
||||
|
||||
auto Gamepad::data() -> uint3 {
|
||||
if(counter >= 8) return 1;
|
||||
if(latched == 1) return interface->inputPoll(port, ID::Device::Gamepad, A);
|
||||
if(latched == 1) return platform->inputPoll(port, ID::Device::Gamepad, A);
|
||||
|
||||
switch(counter++) {
|
||||
case 0: return a;
|
||||
@@ -24,13 +24,13 @@ auto Gamepad::latch(bool data) -> void {
|
||||
counter = 0;
|
||||
|
||||
if(latched == 0) {
|
||||
a = interface->inputPoll(port, ID::Device::Gamepad, A);
|
||||
b = interface->inputPoll(port, ID::Device::Gamepad, B);
|
||||
select = interface->inputPoll(port, ID::Device::Gamepad, Select);
|
||||
start = interface->inputPoll(port, ID::Device::Gamepad, Start);
|
||||
up = interface->inputPoll(port, ID::Device::Gamepad, Up);
|
||||
down = interface->inputPoll(port, ID::Device::Gamepad, Down);
|
||||
left = interface->inputPoll(port, ID::Device::Gamepad, Left);
|
||||
right = interface->inputPoll(port, ID::Device::Gamepad, Right);
|
||||
a = platform->inputPoll(port, ID::Device::Gamepad, A);
|
||||
b = platform->inputPoll(port, ID::Device::Gamepad, B);
|
||||
select = platform->inputPoll(port, ID::Device::Gamepad, Select);
|
||||
start = platform->inputPoll(port, ID::Device::Gamepad, Start);
|
||||
up = platform->inputPoll(port, ID::Device::Gamepad, Up);
|
||||
down = platform->inputPoll(port, ID::Device::Gamepad, Down);
|
||||
left = platform->inputPoll(port, ID::Device::Gamepad, Left);
|
||||
right = platform->inputPoll(port, ID::Device::Gamepad, Right);
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@
|
||||
#include <processor/r6502/r6502.hpp>
|
||||
|
||||
namespace Famicom {
|
||||
#define platform Emulator::platform
|
||||
using File = Emulator::File;
|
||||
using Scheduler = Emulator::Scheduler;
|
||||
using Cheat = Emulator::Cheat;
|
||||
|
@@ -2,18 +2,12 @@
|
||||
|
||||
namespace Famicom {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Famicom";
|
||||
information.width = 256;
|
||||
information.height = 240;
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 8.0 / 7.0;
|
||||
information.resettable = true;
|
||||
|
||||
information.capability.states = true;
|
||||
@@ -54,6 +48,17 @@ auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoSize() -> VideoSize {
|
||||
return {256, 240};
|
||||
}
|
||||
|
||||
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
uint w = 256 * (arc ? 8.0 / 7.0 : 1.0);
|
||||
uint h = 240;
|
||||
uint m = min(width / w, height / h);
|
||||
return {w * m, h * m};
|
||||
}
|
||||
|
||||
auto Interface::videoFrequency() -> double {
|
||||
return 21477272.0 / (262.0 * 1364.0 - 4.0);
|
||||
}
|
||||
@@ -126,7 +131,7 @@ auto Interface::sha256() -> string {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> bool {
|
||||
return system.load();
|
||||
return system.load(this);
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -139,7 +144,7 @@ auto Interface::unload() -> void {
|
||||
}
|
||||
|
||||
auto Interface::connect(uint port, uint device) -> void {
|
||||
Famicom::peripherals.connect(port, device);
|
||||
peripherals.connect(port, device);
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
|
@@ -25,9 +25,13 @@ struct Interface : Emulator::Interface {
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto videoSize() -> VideoSize override;
|
||||
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
@@ -60,7 +64,6 @@ struct Settings {
|
||||
uint expansionPort = 0;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
|
@@ -21,15 +21,17 @@ auto System::runToSave() -> void {
|
||||
for(auto peripheral : cpu.peripherals) scheduler.synchronize(*peripheral);
|
||||
}
|
||||
|
||||
auto System::load() -> bool {
|
||||
auto System::load(Emulator::Interface* interface) -> bool {
|
||||
information = Information();
|
||||
if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
if(!cartridge.load()) return false;
|
||||
|
||||
this->interface = interface;
|
||||
information.colorburst = Emulator::Constants::Colorburst::NTSC;
|
||||
serializeInit();
|
||||
return information.loaded = true;
|
||||
|
@@ -5,7 +5,7 @@ struct System {
|
||||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto load() -> bool;
|
||||
auto load(Emulator::Interface*) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
@@ -26,13 +26,15 @@ struct System {
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
private:
|
||||
Emulator::Interface* interface = nullptr;
|
||||
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
double colorburst = 0.0;
|
||||
string manifest;
|
||||
} information;
|
||||
|
||||
private:
|
||||
uint _serializeSize = 0;
|
||||
};
|
||||
|
||||
|
@@ -29,7 +29,7 @@ auto APU::main() -> void {
|
||||
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
|
||||
} else {
|
||||
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
|
||||
interface->audioSample(samples, 2);
|
||||
//interface->audioSample(samples, 2);
|
||||
}
|
||||
|
||||
if(cycle == 0) { //512hz
|
||||
|
@@ -19,23 +19,23 @@ auto Cartridge::load(System::Revision revision) -> bool {
|
||||
|
||||
switch(revision) {
|
||||
case System::Revision::GameBoy:
|
||||
if(auto pathID = interface->load(ID::GameBoy, "Game Boy", "gb")) {
|
||||
if(auto pathID = platform->load(ID::GameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
case System::Revision::SuperGameBoy:
|
||||
if(auto pathID = interface->load(ID::SuperGameBoy, "Game Boy", "gb")) {
|
||||
if(auto pathID = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
case System::Revision::GameBoyColor:
|
||||
if(auto pathID = interface->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
|
||||
if(auto pathID = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
@@ -64,12 +64,12 @@ auto Cartridge::load(System::Revision revision) -> bool {
|
||||
ram.data = (uint8*)memory::allocate(ram.size, 0xff);
|
||||
|
||||
if(auto name = board["rom/name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
|
||||
fp->read(rom.data, min(rom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
if(auto name = board["ram/name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Read, File::Optional)) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Optional)) {
|
||||
fp->read(ram.data, min(ram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ auto Cartridge::save() -> void {
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto name = document["board/ram/name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Write)) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Write)) {
|
||||
fp->write(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
|
@@ -8,15 +8,15 @@ auto CPU::wramAddress(uint16 addr) const -> uint {
|
||||
auto CPU::joypPoll() -> void {
|
||||
uint button = 0, dpad = 0;
|
||||
|
||||
button |= interface->inputPoll(0, 0, (uint)Input::Start) << 3;
|
||||
button |= interface->inputPoll(0, 0, (uint)Input::Select) << 2;
|
||||
button |= interface->inputPoll(0, 0, (uint)Input::B) << 1;
|
||||
button |= interface->inputPoll(0, 0, (uint)Input::A) << 0;
|
||||
button |= platform->inputPoll(0, 0, (uint)Input::Start) << 3;
|
||||
button |= platform->inputPoll(0, 0, (uint)Input::Select) << 2;
|
||||
button |= platform->inputPoll(0, 0, (uint)Input::B) << 1;
|
||||
button |= platform->inputPoll(0, 0, (uint)Input::A) << 0;
|
||||
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Down) << 3;
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Up) << 2;
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Left) << 1;
|
||||
dpad |= interface->inputPoll(0, 0, (uint)Input::Right) << 0;
|
||||
dpad |= platform->inputPoll(0, 0, (uint)Input::Down) << 3;
|
||||
dpad |= platform->inputPoll(0, 0, (uint)Input::Up) << 2;
|
||||
dpad |= platform->inputPoll(0, 0, (uint)Input::Left) << 1;
|
||||
dpad |= platform->inputPoll(0, 0, (uint)Input::Right) << 0;
|
||||
|
||||
if(system.revision() != System::Revision::SuperGameBoy) {
|
||||
//D-pad pivot makes it impossible to press opposing directions at the same time
|
||||
@@ -145,7 +145,7 @@ auto CPU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
if(addr == 0xff00) { //JOYP
|
||||
status.p15 = data & 0x20;
|
||||
status.p14 = data & 0x10;
|
||||
interface->joypWrite(status.p15, status.p14);
|
||||
//interface->joypWrite(status.p15, status.p14);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,7 @@
|
||||
#include <processor/lr35902/lr35902.hpp>
|
||||
|
||||
namespace GameBoy {
|
||||
#define platform Emulator::platform
|
||||
using File = Emulator::File;
|
||||
using Scheduler = Emulator::Scheduler;
|
||||
using Cheat = Emulator::Cheat;
|
||||
|
@@ -2,19 +2,14 @@
|
||||
|
||||
namespace GameBoy {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
hook = nullptr;
|
||||
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy";
|
||||
information.width = 160;
|
||||
information.height = 144;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = true;
|
||||
@@ -48,6 +43,17 @@ auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoSize() -> VideoSize {
|
||||
return {160, 144};
|
||||
}
|
||||
|
||||
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
uint w = 160;
|
||||
uint h = 144;
|
||||
uint m = min(width / w, height / h);
|
||||
return {w * m, h * m};
|
||||
}
|
||||
|
||||
auto Interface::videoFrequency() -> double {
|
||||
return 4194304.0 / (154.0 * 456.0);
|
||||
}
|
||||
@@ -126,9 +132,9 @@ auto Interface::sha256() -> string {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> bool {
|
||||
if(id == ID::GameBoy) return system.load(System::Revision::GameBoy);
|
||||
if(id == ID::SuperGameBoy) return system.load(System::Revision::SuperGameBoy);
|
||||
if(id == ID::GameBoyColor) return system.load(System::Revision::GameBoyColor);
|
||||
if(id == ID::GameBoy) return system.load(this, System::Revision::GameBoy);
|
||||
if(id == ID::SuperGameBoy) return system.load(this, System::Revision::SuperGameBoy);
|
||||
if(id == ID::GameBoyColor) return system.load(this, System::Revision::GameBoyColor);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -24,9 +24,13 @@ struct Interface : Emulator::Interface {
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto videoSize() -> VideoSize override;
|
||||
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
@@ -66,7 +70,6 @@ struct Settings {
|
||||
bool colorEmulation = true;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@ auto PPU::runDMG() -> void {
|
||||
|
||||
uint32* output = screen + status.ly * 160 + px++;
|
||||
*output = color;
|
||||
interface->lcdOutput(color); //Super Game Boy notification
|
||||
//interface->lcdOutput(color); //Super Game Boy notification
|
||||
}
|
||||
|
||||
auto PPU::runBackgroundDMG() -> void {
|
||||
|
@@ -16,7 +16,7 @@ auto PPU::Enter() -> void {
|
||||
|
||||
auto PPU::main() -> void {
|
||||
status.lx = 0;
|
||||
interface->lcdScanline(); //Super Game Boy notification
|
||||
//interface->lcdScanline(); //Super Game Boy notification
|
||||
|
||||
if(status.ly <= 143) {
|
||||
mode(2);
|
||||
|
@@ -22,10 +22,10 @@ auto System::init() -> void {
|
||||
assert(interface != nullptr);
|
||||
}
|
||||
|
||||
auto System::load(Revision revision) -> bool {
|
||||
auto System::load(Emulator::Interface* interface, Revision revision) -> bool {
|
||||
_revision = revision;
|
||||
|
||||
if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
@@ -34,7 +34,7 @@ auto System::load(Revision revision) -> bool {
|
||||
if(revision == Revision::SuperGameBoy) path = "board/icd2/rom/name";
|
||||
|
||||
if(auto name = document[path].text()) {
|
||||
if(auto fp = interface->open(ID::System, name, File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(ID::System, name, File::Read, File::Required)) {
|
||||
if(revision == Revision::GameBoy) fp->read(bootROM.dmg, 256);
|
||||
if(revision == Revision::SuperGameBoy) fp->read(bootROM.sgb, 256);
|
||||
if(revision == Revision::GameBoyColor) fp->read(bootROM.cgb, 2048);
|
||||
@@ -43,6 +43,7 @@ auto System::load(Revision revision) -> bool {
|
||||
|
||||
if(!cartridge.load(revision)) return false;
|
||||
serializeInit();
|
||||
this->interface = interface;
|
||||
return _loaded = true;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,3 @@
|
||||
struct Interface;
|
||||
|
||||
enum class Input : uint {
|
||||
Up, Down, Left, Right, B, A, Select, Start,
|
||||
};
|
||||
@@ -23,7 +21,7 @@ struct System {
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto load(Revision) -> bool;
|
||||
auto load(Emulator::Interface*, Revision) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
@@ -40,6 +38,8 @@ struct System {
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
Emulator::Interface* interface = nullptr;
|
||||
|
||||
struct BootROM {
|
||||
uint8 dmg[ 256];
|
||||
uint8 sgb[ 256];
|
||||
|
@@ -26,11 +26,11 @@ Cartridge::~Cartridge() {
|
||||
auto Cartridge::load() -> bool {
|
||||
information = Information();
|
||||
|
||||
if(auto pathID = interface->load(ID::GameBoyAdvance, "Game Boy Advance", "gba")) {
|
||||
if(auto pathID = platform->load(ID::GameBoyAdvance, "Game Boy Advance", "gba")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
|
||||
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
@@ -43,7 +43,7 @@ auto Cartridge::load() -> bool {
|
||||
|
||||
if(auto node = document["board/rom"]) {
|
||||
mrom.size = min(32 * 1024 * 1024, node["size"].natural());
|
||||
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(pathID(), node["name"].text(), File::Read, File::Required)) {
|
||||
fp->read(mrom.data, mrom.size);
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ auto Cartridge::load() -> bool {
|
||||
sram.mask = sram.size - 1;
|
||||
for(auto n : range(sram.size)) sram.data[n] = 0xff;
|
||||
|
||||
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read)) {
|
||||
if(auto fp = platform->open(pathID(), node["name"].text(), File::Read)) {
|
||||
fp->read(sram.data, sram.size);
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ auto Cartridge::load() -> bool {
|
||||
eeprom.test = mrom.size > 16 * 1024 * 1024 ? 0x0dffff00 : 0x0d000000;
|
||||
for(auto n : range(eeprom.size)) eeprom.data[n] = 0xff;
|
||||
|
||||
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read)) {
|
||||
if(auto fp = platform->open(pathID(), node["name"].text(), File::Read)) {
|
||||
fp->read(eeprom.data, eeprom.size);
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ auto Cartridge::load() -> bool {
|
||||
if(!flash.id && flash.size == 64 * 1024) flash.id = 0x1cc2;
|
||||
if(!flash.id && flash.size == 128 * 1024) flash.id = 0x09c2;
|
||||
|
||||
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read)) {
|
||||
if(auto fp = platform->open(pathID(), node["name"].text(), File::Read)) {
|
||||
fp->read(flash.data, flash.size);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ auto Cartridge::load() -> bool {
|
||||
auto Cartridge::save() -> void {
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
if(auto node = document["board/ram"]) {
|
||||
if(auto fp = interface->open(pathID(), node["name"].text(), File::Write)) {
|
||||
if(auto fp = platform->open(pathID(), node["name"].text(), File::Write)) {
|
||||
if(node["type"].text() == "sram") fp->write(sram.data, sram.size);
|
||||
if(node["type"].text() == "eeprom") fp->write(eeprom.data, eeprom.size);
|
||||
if(node["type"].text() == "flash") fp->write(flash.data, flash.size);
|
||||
|
@@ -97,7 +97,7 @@ auto CPU::keypadRun() -> void {
|
||||
bool test = regs.keypad.control.condition; //0 = OR, 1 = AND
|
||||
for(auto n : range(10)) {
|
||||
if(!regs.keypad.control.flag[n]) continue;
|
||||
bool input = interface->inputPoll(0, 0, lookup[n]);
|
||||
bool input = platform->inputPoll(0, 0, lookup[n]);
|
||||
if(regs.keypad.control.condition == 0) test |= input;
|
||||
if(regs.keypad.control.condition == 1) test &= input;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ auto CPU::power() -> void {
|
||||
for(auto& swait : regs.wait.control.swait) swait = 0;
|
||||
regs.wait.control.phi = 0;
|
||||
regs.wait.control.prefetch = 0;
|
||||
regs.wait.control.gametype = 0;
|
||||
regs.wait.control.gametype = 0; //0 = GBA, 1 = GBC
|
||||
regs.postboot = 0;
|
||||
regs.mode = Registers::Mode::Normal;
|
||||
regs.clock = 0;
|
||||
|
@@ -64,7 +64,7 @@ auto CPU::readIO(uint32 addr) -> uint8 {
|
||||
static const uint lookup[] = {5, 4, 8, 9, 3, 2, 0, 1};
|
||||
if(auto result = player.keyinput()) return result() >> 0;
|
||||
uint8 result = 0;
|
||||
for(uint n = 0; n < 8; n++) result |= interface->inputPoll(0, 0, lookup[n]) << n;
|
||||
for(uint n = 0; n < 8; n++) result |= platform->inputPoll(0, 0, lookup[n]) << n;
|
||||
if((result & 0xc0) == 0xc0) result &= (uint8)~0xc0; //up+down cannot be pressed simultaneously
|
||||
if((result & 0x30) == 0x30) result &= (uint8)~0x30; //left+right cannot be pressed simultaneously
|
||||
return result ^ 0xff;
|
||||
@@ -72,8 +72,8 @@ auto CPU::readIO(uint32 addr) -> uint8 {
|
||||
case 0x04000131: {
|
||||
if(auto result = player.keyinput()) return result() >> 8;
|
||||
uint8 result = 0;
|
||||
result |= interface->inputPoll(0, 0, 7) << 0;
|
||||
result |= interface->inputPoll(0, 0, 6) << 1;
|
||||
result |= platform->inputPoll(0, 0, 7) << 0;
|
||||
result |= platform->inputPoll(0, 0, 6) << 1;
|
||||
return result ^ 0x03;
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ auto CPU::writeIO(uint32 addr, uint8 data) -> void {
|
||||
regs.wait.control.swait[2] = data.bit (2);
|
||||
regs.wait.control.phi = data.bit (3);
|
||||
regs.wait.control.prefetch = data.bit (6);
|
||||
regs.wait.control.gametype = data.bit (7);
|
||||
//regs.wait.control.gametype is read-only
|
||||
return;
|
||||
|
||||
//IME
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#include <processor/arm/arm.hpp>
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
#define platform Emulator::platform
|
||||
using File = Emulator::File;
|
||||
using Scheduler = Emulator::Scheduler;
|
||||
extern Scheduler scheduler;
|
||||
|
@@ -2,18 +2,12 @@
|
||||
|
||||
namespace GameBoyAdvance {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy Advance";
|
||||
information.width = 240;
|
||||
information.height = 160;
|
||||
information.overscan = false;
|
||||
information.aspectRatio = 1.0;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = true;
|
||||
@@ -49,6 +43,17 @@ auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoSize() -> VideoSize {
|
||||
return {240, 160};
|
||||
}
|
||||
|
||||
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
uint w = 240;
|
||||
uint h = 160;
|
||||
uint m = min(width / w, height / h);
|
||||
return {w * m, h * m};
|
||||
}
|
||||
|
||||
auto Interface::videoFrequency() -> double {
|
||||
return 16777216.0 / (228.0 * 1232.0);
|
||||
}
|
||||
@@ -88,7 +93,7 @@ auto Interface::loaded() -> bool {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> bool {
|
||||
return system.load();
|
||||
return system.load(this);
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
|
@@ -22,9 +22,13 @@ struct Interface : Emulator::Interface {
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto videoSize() -> VideoSize override;
|
||||
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
@@ -49,7 +53,6 @@ struct Settings {
|
||||
bool colorEmulation = true;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
|
@@ -100,7 +100,7 @@ auto Player::write(uint2 addr, uint8 byte) -> void {
|
||||
|
||||
if(addr == 3 && status.packet == 15) {
|
||||
status.rumble = (status.recv & 0xff) == 0x26; //on = 0x26, off = 0x04
|
||||
interface->inputRumble(0, 0, 10, status.rumble);
|
||||
platform->inputRumble(0, 0, 10, status.rumble);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -34,20 +34,23 @@ auto System::power() -> void {
|
||||
scheduler.primary(cpu);
|
||||
}
|
||||
|
||||
auto System::load() -> bool {
|
||||
if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
auto System::load(Emulator::Interface* interface) -> bool {
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto name = document["system/cpu/rom/name"].text()) {
|
||||
if(auto fp = interface->open(ID::System, name, File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(ID::System, name, File::Read, File::Required)) {
|
||||
fp->read(bios.data, bios.size);
|
||||
}
|
||||
}
|
||||
|
||||
if(!cartridge.load()) return false;
|
||||
|
||||
serializeInit();
|
||||
this->interface = interface;
|
||||
return _loaded = true;
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,7 @@ struct System {
|
||||
|
||||
auto init() -> void;
|
||||
auto term() -> void;
|
||||
auto load() -> bool;
|
||||
auto load(Emulator::Interface*) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
@@ -38,6 +38,9 @@ struct System {
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
private:
|
||||
Emulator::Interface* interface = nullptr;
|
||||
|
||||
struct Information {
|
||||
string manifest;
|
||||
} information;
|
||||
|
@@ -2,13 +2,16 @@ processors += m68k z80
|
||||
|
||||
objects += md-interface
|
||||
objects += md-cpu md-apu md-vdp md-psg md-ym2612
|
||||
objects += md-system md-cartridge
|
||||
objects += md-system md-cartridge md-bus
|
||||
objects += md-controller
|
||||
|
||||
obj/md-interface.o: md/interface/interface.cpp $(call rwildcard,md/interface)
|
||||
obj/md-cpu.o: md/cpu/cpu.cpp $(call rwildcard,md/cpu)
|
||||
obj/md-apu.o: md/apu/apu.cpp $(call rwildcard,md/apu)
|
||||
obj/md-vdp.o: md/vdp/vdp.cpp $(call rwildcard,md/vdp)
|
||||
obj/md-psg.o: md/psg/psg.cpp $(call rwildcard,md/psg)
|
||||
obj/md-ym2612.o: md/ym2612/ym2612.cpp $(call rwildcard,md/ym2612)
|
||||
obj/md-system.o: md/system/system.cpp $(call rwildcard,md/system)
|
||||
obj/md-cartridge.o: md/cartridge/cartridge.cpp $(call rwildcard,md/cartridge)
|
||||
obj/md-interface.o: md/interface/interface.cpp $(call rwildcard,md/interface)
|
||||
obj/md-cpu.o: md/cpu/cpu.cpp $(call rwildcard,md/cpu)
|
||||
obj/md-apu.o: md/apu/apu.cpp $(call rwildcard,md/apu)
|
||||
obj/md-vdp.o: md/vdp/vdp.cpp $(call rwildcard,md/vdp)
|
||||
obj/md-psg.o: md/psg/psg.cpp $(call rwildcard,md/psg)
|
||||
obj/md-ym2612.o: md/ym2612/ym2612.cpp $(call rwildcard,md/ym2612)
|
||||
obj/md-system.o: md/system/system.cpp $(call rwildcard,md/system)
|
||||
obj/md-cartridge.o: md/cartridge/cartridge.cpp $(call rwildcard,md/cartridge)
|
||||
obj/md-bus.o: md/bus/bus.cpp $(call rwildcard,md/bus)
|
||||
obj/md-controller.o: md/controller/controller.cpp $(call rwildcard,md/controller)
|
||||
|
@@ -9,16 +9,17 @@ auto APU::Enter() -> void {
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
step(system.colorburst());
|
||||
step(1);
|
||||
}
|
||||
|
||||
auto APU::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
}
|
||||
|
||||
auto APU::reset() -> void {
|
||||
Z80::bus = &busAPU;
|
||||
Z80::power();
|
||||
create(APU::Enter, system.colorburst());
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,6 @@ struct APU : Processor::Z80, Thread {
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
};
|
||||
|
||||
extern APU apu;
|
||||
|
94
higan/md/bus/bus.cpp
Normal file
94
higan/md/bus/bus.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include <md/md.hpp>
|
||||
|
||||
namespace MegaDrive {
|
||||
|
||||
BusCPU busCPU;
|
||||
BusAPU busAPU;
|
||||
|
||||
auto BusCPU::readByte(uint24 addr) -> uint16 {
|
||||
if(addr < 0x400000) return cartridge.read(addr & ~1).byte(!addr.bit(0));
|
||||
if(addr < 0xa00000) return 0x0000;
|
||||
if(addr < 0xa10000) return 0x0000;
|
||||
if(addr < 0xa10020) return readIO(addr);
|
||||
if(addr < 0xc00000) return 0x0000;
|
||||
if(addr < 0xe00000) return vdp.read(addr & ~1).byte(!addr.bit(0));
|
||||
return ram[addr & 0xffff];
|
||||
}
|
||||
|
||||
auto BusCPU::readWord(uint24 addr) -> uint16 {
|
||||
if(addr < 0x400000) return cartridge.read(addr);
|
||||
if(addr < 0xa00000) return 0x0000;
|
||||
if(addr < 0xa10000) return 0x0000;
|
||||
if(addr < 0xa10020) return readIO(addr);
|
||||
if(addr < 0xc00000) return 0x0000;
|
||||
if(addr < 0xe00000) return vdp.read(addr);
|
||||
uint16 data = ram[addr + 0 & 0xffff] << 8;
|
||||
return data | ram[addr + 1 & 0xffff] << 0;
|
||||
}
|
||||
|
||||
auto BusCPU::writeByte(uint24 addr, uint16 data) -> void {
|
||||
if(addr < 0x400000) return cartridge.write(addr & ~1, data << 8 | data << 0);
|
||||
if(addr < 0xa00000) return;
|
||||
if(addr < 0xa10000) return;
|
||||
if(addr < 0xa10020) return writeIO(addr, data);
|
||||
if(addr < 0xc00000) return;
|
||||
if(addr < 0xe00000) return vdp.write(addr & ~1, data << 8 | data << 0);
|
||||
ram[addr & 0xffff] = data;
|
||||
}
|
||||
|
||||
auto BusCPU::writeWord(uint24 addr, uint16 data) -> void {
|
||||
if(addr < 0x400000) return cartridge.write(addr, data);
|
||||
if(addr < 0xa00000) return;
|
||||
if(addr < 0xa10000) return;
|
||||
if(addr < 0xa10020) return writeIO(addr, data);
|
||||
if(addr < 0xc00000) return;
|
||||
if(addr < 0xe00000) return vdp.write(addr, data);
|
||||
ram[addr + 0 & 0xffff] = data >> 8;
|
||||
ram[addr + 1 & 0xffff] = data >> 0;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto BusCPU::readIO(uint24 addr) -> uint16 {
|
||||
switch(addr & ~1) {
|
||||
case 0xa10002: return peripherals.controllerPort1->readData();
|
||||
case 0xa10004: return peripherals.controllerPort2->readData();
|
||||
case 0xa10006: return peripherals.extensionPort->readData();
|
||||
|
||||
case 0xa10008: return peripherals.controllerPort1->readControl();
|
||||
case 0xa1000a: return peripherals.controllerPort2->readControl();
|
||||
case 0xa1000c: return peripherals.extensionPort->readControl();
|
||||
}
|
||||
|
||||
return 0x0000;
|
||||
}
|
||||
|
||||
auto BusCPU::writeIO(uint24 addr, uint16 data) -> void {
|
||||
switch(addr & ~1) {
|
||||
case 0xa10002: return peripherals.controllerPort1->writeData(data);
|
||||
case 0xa10004: return peripherals.controllerPort2->writeData(data);
|
||||
case 0xa10006: return peripherals.extensionPort->writeData(data);
|
||||
|
||||
case 0xa10008: return peripherals.controllerPort1->writeControl(data);
|
||||
case 0xa1000a: return peripherals.controllerPort2->writeControl(data);
|
||||
case 0xa1000c: return peripherals.extensionPort->writeControl(data);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto BusAPU::read(uint16 addr) -> uint8 {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto BusAPU::write(uint16 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto BusAPU::in(uint8 addr) -> uint8 {
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto BusAPU::out(uint8 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
}
|
22
higan/md/bus/bus.hpp
Normal file
22
higan/md/bus/bus.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
struct BusCPU : Processor::M68K::Bus {
|
||||
auto readByte(uint24 addr) -> uint16 override;
|
||||
auto readWord(uint24 addr) -> uint16 override;
|
||||
auto writeByte(uint24 addr, uint16 data) -> void override;
|
||||
auto writeWord(uint24 addr, uint16 data) -> void override;
|
||||
|
||||
auto readIO(uint24 addr) -> uint16;
|
||||
auto writeIO(uint24 addr, uint16 data) -> void;
|
||||
|
||||
private:
|
||||
uint8 ram[64 * 1024];
|
||||
};
|
||||
|
||||
struct BusAPU : Processor::Z80::Bus {
|
||||
auto read(uint16 addr) -> uint8 override;
|
||||
auto write(uint16 addr, uint8 data) -> void override;
|
||||
auto in(uint8 addr) -> uint8 override;
|
||||
auto out(uint8 addr, uint8 data) -> void override;
|
||||
};
|
||||
|
||||
extern BusCPU busCPU;
|
||||
extern BusAPU busAPU;
|
@@ -5,13 +5,13 @@ namespace MegaDrive {
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
information = Information();
|
||||
information = {};
|
||||
|
||||
if(auto pathID = interface->load(ID::MegaDrive, "Mega Drive", "md")) {
|
||||
if(auto pathID = platform->load(ID::MegaDrive, "Mega Drive", "md")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
|
||||
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
@@ -24,7 +24,7 @@ auto Cartridge::load() -> bool {
|
||||
if(rom.size) {
|
||||
rom.data = new uint8[rom.mask + 1];
|
||||
if(auto name = node["name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
|
||||
fp->read(rom.data, rom.size);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ auto Cartridge::load() -> bool {
|
||||
if(ram.size) {
|
||||
ram.data = new uint8[ram.mask + 1];
|
||||
if(auto name = node["name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Read)) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read)) {
|
||||
fp->read(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ auto Cartridge::save() -> void {
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto name = document["board/ram/name"].text()) {
|
||||
if(auto fp = interface->open(pathID(), name, File::Write)) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Write)) {
|
||||
fp->write(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
@@ -70,13 +70,12 @@ auto Cartridge::power() -> void {
|
||||
auto Cartridge::reset() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::read(bool word, uint24 addr) -> uint16 {
|
||||
uint16 data = rom.data[addr & rom.mask];
|
||||
if(!word) return data;
|
||||
return data << 8 | rom.data[addr + 1 & rom.mask];
|
||||
auto Cartridge::read(uint24 addr) -> uint16 {
|
||||
uint16 data = rom.data[addr + 0 & rom.mask] << 8;
|
||||
return data | rom.data[addr + 1 & rom.mask] << 0;
|
||||
}
|
||||
|
||||
auto Cartridge::write(bool word, uint24 addr, uint16 data) -> void {
|
||||
auto Cartridge::write(uint24 addr, uint16 data) -> void {
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -10,8 +10,8 @@ struct Cartridge {
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto read(bool word, uint24 addr) -> uint16;
|
||||
auto write(bool word, uint24 addr, uint16 data) -> void;
|
||||
auto read(uint24 addr) -> uint16;
|
||||
auto write(uint24 addr, uint16 data) -> void;
|
||||
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
|
28
higan/md/controller/controller.cpp
Normal file
28
higan/md/controller/controller.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <md/md.hpp>
|
||||
|
||||
namespace MegaDrive {
|
||||
|
||||
#include "gamepad/gamepad.cpp"
|
||||
|
||||
Controller::Controller(uint port) : port(port) {
|
||||
if(!handle()) create(Controller::Enter, 100);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
}
|
||||
|
||||
auto Controller::Enter() -> void {
|
||||
while(true) {
|
||||
scheduler.synchronize();
|
||||
if(peripherals.controllerPort1->active()) peripherals.controllerPort1->main();
|
||||
if(peripherals.controllerPort2->active()) peripherals.controllerPort2->main();
|
||||
if(peripherals.extensionPort->active()) peripherals.extensionPort->main();
|
||||
}
|
||||
}
|
||||
|
||||
auto Controller::main() -> void {
|
||||
step(1);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
}
|
17
higan/md/controller/controller.hpp
Normal file
17
higan/md/controller/controller.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
struct Controller : Thread {
|
||||
Controller(uint port);
|
||||
virtual ~Controller();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
virtual auto readData() -> uint8 { return 0xff; }
|
||||
virtual auto writeData(uint8 data) -> void {}
|
||||
|
||||
virtual auto readControl() -> uint8 { return 0x00; }
|
||||
virtual auto writeControl(uint8 data) -> void {}
|
||||
|
||||
const uint port;
|
||||
};
|
||||
|
||||
#include "gamepad/gamepad.hpp"
|
30
higan/md/controller/gamepad/gamepad.cpp
Normal file
30
higan/md/controller/gamepad/gamepad.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
Gamepad::Gamepad(uint port) : Controller(port) {
|
||||
}
|
||||
|
||||
auto Gamepad::readData() -> uint8 {
|
||||
uint6 data;
|
||||
|
||||
if(select == 0) {
|
||||
data.bit(0) = platform->inputPoll(port, ID::Device::Gamepad, Up);
|
||||
data.bit(1) = platform->inputPoll(port, ID::Device::Gamepad, Down);
|
||||
data.bit(2) = 1;
|
||||
data.bit(3) = 1;
|
||||
data.bit(4) = platform->inputPoll(port, ID::Device::Gamepad, A);
|
||||
data.bit(5) = platform->inputPoll(port, ID::Device::Gamepad, Start);
|
||||
} else {
|
||||
data.bit(0) = platform->inputPoll(port, ID::Device::Gamepad, Up);
|
||||
data.bit(1) = platform->inputPoll(port, ID::Device::Gamepad, Down);
|
||||
data.bit(2) = platform->inputPoll(port, ID::Device::Gamepad, Left);
|
||||
data.bit(3) = platform->inputPoll(port, ID::Device::Gamepad, Right);
|
||||
data.bit(4) = platform->inputPoll(port, ID::Device::Gamepad, B);
|
||||
data.bit(5) = platform->inputPoll(port, ID::Device::Gamepad, C);
|
||||
}
|
||||
|
||||
data = ~data;
|
||||
return latch << 7 | select << 6 | data;
|
||||
}
|
||||
|
||||
auto Gamepad::writeData(uint8 data) -> void {
|
||||
select = data.bit(6);
|
||||
latch = data.bit(7);
|
||||
}
|
13
higan/md/controller/gamepad/gamepad.hpp
Normal file
13
higan/md/controller/gamepad/gamepad.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
struct Gamepad : Controller {
|
||||
enum : uint {
|
||||
Up, Down, Left, Right, A, B, C, X, Y, Z, Start,
|
||||
};
|
||||
|
||||
Gamepad(uint port);
|
||||
|
||||
auto readData() -> uint8 override;
|
||||
auto writeData(uint8 data) -> void override;
|
||||
|
||||
boolean select;
|
||||
boolean latch;
|
||||
};
|
@@ -10,54 +10,76 @@ auto CPU::Enter() -> void {
|
||||
}
|
||||
|
||||
auto CPU::boot() -> void {
|
||||
r.a[7] = read(1, 0) << 16 | read(1, 2) << 0;
|
||||
r.pc = read(1, 4) << 16 | read(1, 6) << 0;
|
||||
r.a[7] = bus->readWord(0) << 16 | bus->readWord(2) << 0;
|
||||
r.pc = bus->readWord(4) << 16 | bus->readWord(6) << 0;
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
#if 0
|
||||
static file fp;
|
||||
if(!fp) fp.open({Path::user(), "Desktop/tracer.log"}, file::mode::write);
|
||||
fp.print(pad(disassemble(r.pc), -60, ' '), " ", disassembleRegisters().replace("\n", " "), "\n");
|
||||
#endif
|
||||
|
||||
if(state.interruptPending) {
|
||||
if(state.interruptPending.bit((uint)Interrupt::HorizontalBlank)) {
|
||||
if(4 > r.i) {
|
||||
state.interruptPending.bit((uint)Interrupt::HorizontalBlank) = 0;
|
||||
return exception(Exception::Interrupt, Vector::HorizontalBlank, 4);
|
||||
}
|
||||
}
|
||||
|
||||
if(state.interruptPending.bit((uint)Interrupt::VerticalBlank)) {
|
||||
if(6 > r.i) {
|
||||
state.interruptPending.bit((uint)Interrupt::VerticalBlank) = 0;
|
||||
return exception(Exception::Interrupt, Vector::VerticalBlank, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::step(uint clocks) -> void {
|
||||
while(wait) {
|
||||
Thread::step(1);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
Thread::step(clocks);
|
||||
cycles += clocks;
|
||||
if(cycles >= frequency() / 60) {
|
||||
cycles = 0;
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
auto CPU::synchronize() -> void {
|
||||
synchronize(apu);
|
||||
synchronize(vdp);
|
||||
synchronize(psg);
|
||||
synchronize(ym2612);
|
||||
for(auto peripheral : peripherals) synchronize(*peripheral);
|
||||
}
|
||||
|
||||
auto CPU::raise(Interrupt interrupt) -> void {
|
||||
if(!state.interruptLine.bit((uint)interrupt)) {
|
||||
state.interruptLine.bit((uint)interrupt) = 1;
|
||||
state.interruptPending.bit((uint)interrupt) = 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
M68K::power();
|
||||
auto CPU::lower(Interrupt interrupt) -> void {
|
||||
state.interruptLine.bit((uint)interrupt) = 0;
|
||||
state.interruptPending.bit((uint)interrupt) = 0;
|
||||
}
|
||||
|
||||
for(auto& byte : ram) byte = 0x00;
|
||||
auto CPU::power() -> void {
|
||||
M68K::bus = &busCPU;
|
||||
M68K::power();
|
||||
}
|
||||
|
||||
auto CPU::reset() -> void {
|
||||
M68K::reset();
|
||||
create(CPU::Enter, system.colorburst() * 15.0 / 7.0);
|
||||
cycles = 0;
|
||||
}
|
||||
|
||||
auto CPU::read(bool word, uint24 addr) -> uint16 {
|
||||
if(addr < 0x400000) return cartridge.read(word, addr);
|
||||
if(addr < 0xe00000) return 0x0000;
|
||||
|
||||
uint16 data = ram[addr & 65535];
|
||||
if(word) data = data << 8 | ram[addr + 1 & 65535];
|
||||
return data;
|
||||
}
|
||||
|
||||
auto CPU::write(bool word, uint24 addr, uint16 data) -> void {
|
||||
if(addr < 0x400000) return cartridge.write(word, addr, data);
|
||||
if(addr < 0xe00000) return;
|
||||
|
||||
if(!word) {
|
||||
ram[addr & 65535] = data;
|
||||
} else {
|
||||
ram[addr + 0 & 65535] = data >> 8;
|
||||
ram[addr + 1 & 65535] = data >> 0;
|
||||
}
|
||||
memory::fill(&state, sizeof(State));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,21 +1,32 @@
|
||||
//Motorola 68000
|
||||
|
||||
struct CPU : Processor::M68K, Thread {
|
||||
enum class Interrupt : uint {
|
||||
HorizontalBlank,
|
||||
VerticalBlank,
|
||||
};
|
||||
|
||||
using Thread::synchronize;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto boot() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void override;
|
||||
auto synchronize() -> void;
|
||||
|
||||
auto raise(Interrupt) -> void;
|
||||
auto lower(Interrupt) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
auto read(bool word, uint24 addr) -> uint16 override;
|
||||
auto write(bool word, uint24 addr, uint16 data) -> void override;
|
||||
vector<Thread*> peripherals;
|
||||
|
||||
private:
|
||||
uint8 ram[64 * 1024];
|
||||
|
||||
uint cycles = 0;
|
||||
struct State {
|
||||
uint32 interruptLine;
|
||||
uint32 interruptPending;
|
||||
} state;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
|
@@ -2,18 +2,12 @@
|
||||
|
||||
namespace MegaDrive {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.manufacturer = "Sega";
|
||||
information.name = "Mega Drive";
|
||||
information.width = 320; //1280
|
||||
information.height = 240; // 480
|
||||
information.overscan = true;
|
||||
information.aspectRatio = 4.0 / 3.0;
|
||||
information.resettable = true;
|
||||
|
||||
information.capability.states = false;
|
||||
@@ -23,6 +17,13 @@ Interface::Interface() {
|
||||
|
||||
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
|
||||
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
|
||||
Port extensionPort{ID::Port::Extension, "Extension Port"};
|
||||
|
||||
{ Device device{ID::Device::None, "None"};
|
||||
controllerPort1.devices.append(device);
|
||||
controllerPort2.devices.append(device);
|
||||
extensionPort.devices.append(device);
|
||||
}
|
||||
|
||||
{ Device device{ID::Device::Gamepad, "Gamepad"};
|
||||
device.inputs.append({0, "Up" });
|
||||
@@ -42,6 +43,7 @@ Interface::Interface() {
|
||||
|
||||
ports.append(move(controllerPort1));
|
||||
ports.append(move(controllerPort2));
|
||||
ports.append(move(extensionPort));
|
||||
}
|
||||
|
||||
auto Interface::manifest() -> string {
|
||||
@@ -52,6 +54,17 @@ auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoSize() -> VideoSize {
|
||||
return {1280, 480};
|
||||
}
|
||||
|
||||
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
uint w = 320;
|
||||
uint h = 240;
|
||||
uint m = min(width / w, height / h);
|
||||
return {w * m, h * m};
|
||||
}
|
||||
|
||||
auto Interface::videoFrequency() -> double {
|
||||
return 60.0;
|
||||
}
|
||||
@@ -61,9 +74,9 @@ auto Interface::videoColors() -> uint32 {
|
||||
}
|
||||
|
||||
auto Interface::videoColor(uint32 color) -> uint64 {
|
||||
uint B = color.bits(0,2);
|
||||
uint R = color.bits(0,2);
|
||||
uint G = color.bits(3,5);
|
||||
uint R = color.bits(6,8);
|
||||
uint B = color.bits(6,8);
|
||||
|
||||
uint64 r = image::normalize(R, 3, 16);
|
||||
uint64 g = image::normalize(G, 3, 16);
|
||||
@@ -81,7 +94,7 @@ auto Interface::loaded() -> bool {
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> bool {
|
||||
return system.load();
|
||||
return system.load(this);
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
@@ -92,6 +105,10 @@ auto Interface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::connect(uint port, uint device) -> void {
|
||||
peripherals.connect(port, device);
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
|
@@ -9,9 +9,11 @@ struct ID {
|
||||
struct Port { enum : uint {
|
||||
Controller1,
|
||||
Controller2,
|
||||
Extension,
|
||||
};};
|
||||
|
||||
struct Device { enum : uint {
|
||||
None,
|
||||
Gamepad,
|
||||
};};
|
||||
};
|
||||
@@ -23,9 +25,13 @@ struct Interface : Emulator::Interface {
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto videoSize() -> VideoSize override;
|
||||
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
@@ -33,6 +39,7 @@ struct Interface : Emulator::Interface {
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto connect(uint port, uint device) -> void override;
|
||||
auto power() -> void override;
|
||||
auto reset() -> void override;
|
||||
auto run() -> void override;
|
||||
@@ -46,9 +53,11 @@ struct Interface : Emulator::Interface {
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
uint controllerPort1 = 0;
|
||||
uint controllerPort2 = 0;
|
||||
uint extensionPort = 0;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
|
@@ -11,21 +11,33 @@
|
||||
#include <processor/z80/z80.hpp>
|
||||
|
||||
namespace MegaDrive {
|
||||
#define platform Emulator::platform
|
||||
using File = Emulator::File;
|
||||
using Scheduler = Emulator::Scheduler;
|
||||
extern Scheduler scheduler;
|
||||
|
||||
struct Wait {
|
||||
enum : uint {
|
||||
VDP_DMA = 1 << 0,
|
||||
};
|
||||
};
|
||||
|
||||
struct Thread : Emulator::Thread {
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
Emulator::Thread::create(entrypoint, frequency);
|
||||
scheduler.append(*this);
|
||||
wait = 0;
|
||||
}
|
||||
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(clock() >= thread.clock()) scheduler.resume(thread);
|
||||
}
|
||||
|
||||
uint wait = 0;
|
||||
};
|
||||
|
||||
#include <md/controller/controller.hpp>
|
||||
|
||||
#include <md/cpu/cpu.hpp>
|
||||
#include <md/apu/apu.hpp>
|
||||
#include <md/vdp/vdp.hpp>
|
||||
@@ -34,6 +46,7 @@ namespace MegaDrive {
|
||||
|
||||
#include <md/system/system.hpp>
|
||||
#include <md/cartridge/cartridge.hpp>
|
||||
#include <md/bus/bus.hpp>
|
||||
}
|
||||
|
||||
#include <md/interface/interface.hpp>
|
||||
|
@@ -9,17 +9,21 @@ auto PSG::Enter() -> void {
|
||||
}
|
||||
|
||||
auto PSG::main() -> void {
|
||||
step(system.colorburst());
|
||||
stream->sample(0.0, 0.0);
|
||||
step(1);
|
||||
}
|
||||
|
||||
auto PSG::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto PSG::power() -> void {
|
||||
}
|
||||
|
||||
auto PSG::reset() -> void {
|
||||
create(PSG::Enter, system.colorburst());
|
||||
create(PSG::Enter, 52'000); //system.colorburst());
|
||||
stream = Emulator::audio.createStream(2, 52'000.0);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
//TI SN76489
|
||||
|
||||
struct PSG : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
55
higan/md/system/peripherals.cpp
Normal file
55
higan/md/system/peripherals.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
Peripherals peripherals;
|
||||
|
||||
auto Peripherals::unload() -> void {
|
||||
delete controllerPort1;
|
||||
delete controllerPort2;
|
||||
delete extensionPort;
|
||||
controllerPort1 = nullptr;
|
||||
controllerPort2 = nullptr;
|
||||
extensionPort = nullptr;
|
||||
}
|
||||
|
||||
auto Peripherals::reset() -> void {
|
||||
connect(ID::Port::Controller1, settings.controllerPort1);
|
||||
connect(ID::Port::Controller2, settings.controllerPort2);
|
||||
connect(ID::Port::Extension, settings.extensionPort);
|
||||
}
|
||||
|
||||
auto Peripherals::connect(uint port, uint device) -> void {
|
||||
if(port == ID::Port::Controller1) {
|
||||
settings.controllerPort1 = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete controllerPort1;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: controllerPort1 = new Controller(0); break;
|
||||
case ID::Device::Gamepad: controllerPort1 = new Gamepad(0); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(port == ID::Port::Controller2) {
|
||||
settings.controllerPort2 = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete controllerPort2;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: controllerPort2 = new Controller(1); break;
|
||||
case ID::Device::Gamepad: controllerPort2 = new Gamepad(1); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(port == ID::Port::Extension) {
|
||||
settings.extensionPort = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete extensionPort;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: extensionPort = new Controller(2); break;
|
||||
}
|
||||
}
|
||||
|
||||
cpu.peripherals.reset();
|
||||
cpu.peripherals.append(controllerPort1);
|
||||
cpu.peripherals.append(controllerPort2);
|
||||
cpu.peripherals.append(extensionPort);
|
||||
}
|
@@ -2,24 +2,26 @@
|
||||
|
||||
namespace MegaDrive {
|
||||
|
||||
#include "peripherals.cpp"
|
||||
System system;
|
||||
Scheduler scheduler;
|
||||
|
||||
auto System::run() -> void {
|
||||
if(scheduler.enter() == Scheduler::Event::Frame) {
|
||||
static uint32 output[320 * 240] = {0};
|
||||
Emulator::video.refresh(output, 320 * sizeof(uint32), 320, 240);
|
||||
}
|
||||
if(scheduler.enter() == Scheduler::Event::Frame) vdp.refresh();
|
||||
}
|
||||
|
||||
auto System::load() -> bool {
|
||||
information = Information();
|
||||
if(auto fp = interface->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
auto System::load(Emulator::Interface* interface) -> bool {
|
||||
information = {};
|
||||
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
if(!cartridge.load()) return false;
|
||||
|
||||
information.colorburst = Emulator::Constants::Colorburst::NTSC;
|
||||
this->interface = interface;
|
||||
return information.loaded = true;
|
||||
}
|
||||
|
||||
@@ -28,6 +30,7 @@ auto System::save() -> void {
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
peripherals.unload();
|
||||
cartridge.unload();
|
||||
}
|
||||
|
||||
@@ -52,11 +55,13 @@ auto System::reset() -> void {
|
||||
scheduler.reset();
|
||||
cartridge.reset();
|
||||
cpu.reset();
|
||||
apu.reset();
|
||||
apu.power();
|
||||
vdp.reset();
|
||||
psg.reset();
|
||||
ym2612.reset();
|
||||
scheduler.primary(cpu);
|
||||
|
||||
peripherals.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,12 +4,15 @@ struct System {
|
||||
|
||||
auto run() -> void;
|
||||
|
||||
auto load() -> bool;
|
||||
auto load(Emulator::Interface*) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
private:
|
||||
Emulator::Interface* interface = nullptr;
|
||||
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
string manifest;
|
||||
@@ -17,4 +20,15 @@ struct System {
|
||||
} information;
|
||||
};
|
||||
|
||||
struct Peripherals {
|
||||
auto unload() -> void;
|
||||
auto reset() -> void;
|
||||
auto connect(uint port, uint device) -> void;
|
||||
|
||||
Controller* controllerPort1 = nullptr;
|
||||
Controller* controllerPort2 = nullptr;
|
||||
Controller* extensionPort = nullptr;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
extern Peripherals peripherals;
|
||||
|
85
higan/md/vdp/background.cpp
Normal file
85
higan/md/vdp/background.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
auto VDP::Background::isWindowed(uint x, uint y) -> bool {
|
||||
if((x < io.horizontalOffset) ^ io.horizontalDirection) return true;
|
||||
if((y < io.verticalOffset ) ^ io.verticalDirection ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto VDP::Background::updateHorizontalScroll(uint y) -> void {
|
||||
if(id == ID::Window) return;
|
||||
|
||||
uint15 address = io.horizontalScrollAddress;
|
||||
|
||||
static const uint mask[] = {0u, 7u, ~7u, ~0u};
|
||||
address += (y & mask[io.horizontalScrollMode]) << 1;
|
||||
address += id == ID::PlaneB;
|
||||
|
||||
state.horizontalScroll = vdp.vram[address].bits(0,9);
|
||||
}
|
||||
|
||||
auto VDP::Background::updateVerticalScroll(uint x, uint y) -> void {
|
||||
if(id == ID::Window) return;
|
||||
|
||||
auto address = (x >> 4 & 0 - io.verticalScrollMode) << 1;
|
||||
address += id == ID::PlaneB;
|
||||
|
||||
state.verticalScroll = vdp.vsram[address];
|
||||
}
|
||||
|
||||
auto VDP::Background::nametableAddress() -> uint15 {
|
||||
if(id == ID::Window && vdp.screenWidth() == 320) return io.nametableAddress & ~0x0400;
|
||||
return io.nametableAddress;
|
||||
}
|
||||
|
||||
auto VDP::Background::nametableWidth() -> uint {
|
||||
if(id == ID::Window) return vdp.screenWidth() == 320 ? 64 : 32;
|
||||
return 32 * (1 + io.nametableWidth);
|
||||
}
|
||||
|
||||
auto VDP::Background::nametableHeight() -> uint {
|
||||
if(id == ID::Window) return 32;
|
||||
return 32 * (1 + io.nametableHeight);
|
||||
}
|
||||
|
||||
auto VDP::Background::scanline(uint y) -> void {
|
||||
updateHorizontalScroll(y);
|
||||
}
|
||||
|
||||
auto VDP::Background::run(uint x, uint y) -> void {
|
||||
updateVerticalScroll(x, y);
|
||||
|
||||
output.priority = 0;
|
||||
output.color = 0;
|
||||
|
||||
x -= state.horizontalScroll;
|
||||
y += state.verticalScroll;
|
||||
|
||||
uint width = nametableWidth();
|
||||
uint height = nametableHeight();
|
||||
|
||||
uint tileX = x >> 3 & width - 1;
|
||||
uint tileY = y >> 3 & height - 1;
|
||||
|
||||
auto address = nametableAddress();
|
||||
address += (tileY * width + tileX) & 0x0fff;
|
||||
|
||||
uint16 tileAttributes = vdp.vram[address];
|
||||
uint15 tileAddress = tileAttributes.bits(0,10) << 4;
|
||||
uint pixelX = (x & 7) ^ (tileAttributes.bit(11) ? 7 : 0);
|
||||
uint pixelY = (y & 7) ^ (tileAttributes.bit(12) ? 7 : 0);
|
||||
tileAddress += pixelY << 1 | pixelX >> 2;
|
||||
|
||||
uint16 tileData = vdp.vram[tileAddress];
|
||||
uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2);
|
||||
if(color) {
|
||||
output.color = tileAttributes.bits(13,14) << 4 | color;
|
||||
output.priority = tileAttributes.bit(15);
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::Background::power() -> void {
|
||||
}
|
||||
|
||||
auto VDP::Background::reset() -> void {
|
||||
memory::fill(&io, sizeof(IO));
|
||||
memory::fill(&state, sizeof(State));
|
||||
}
|
36
higan/md/vdp/dma.cpp
Normal file
36
higan/md/vdp/dma.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
auto VDP::dmaRun() -> void {
|
||||
if(!io.dmaEnable) return;
|
||||
if(!io.command.bit(5)) return;
|
||||
|
||||
if(io.dmaMode <= 1) return dmaLoad();
|
||||
if(io.dmaMode == 2) return dmaFill();
|
||||
if(io.dmaMode == 3) return dmaCopy();
|
||||
}
|
||||
|
||||
auto VDP::dmaLoad() -> void {
|
||||
cpu.wait |= Wait::VDP_DMA;
|
||||
|
||||
auto data = busCPU.readWord(io.dmaMode.bit(0) << 23 | io.dmaSource << 1);
|
||||
writeDataPort(data);
|
||||
|
||||
io.dmaSource.bits(0,15)++;
|
||||
if(--io.dmaLength == 0) {
|
||||
io.command.bit(5) = 0;
|
||||
cpu.wait &=~ Wait::VDP_DMA;
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::dmaFill() -> void {
|
||||
if(io.dmaFillWait) return;
|
||||
|
||||
auto data = io.dmaFillByte;
|
||||
writeDataPort(data << 8 | data << 0);
|
||||
|
||||
io.dmaSource.bits(0,15)++;
|
||||
if(--io.dmaLength == 0) {
|
||||
io.command.bit(5) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::dmaCopy() -> void {
|
||||
}
|
318
higan/md/vdp/io.cpp
Normal file
318
higan/md/vdp/io.cpp
Normal file
@@ -0,0 +1,318 @@
|
||||
auto VDP::read(uint24 addr) -> uint16 {
|
||||
switch(addr & 0xc0001e) {
|
||||
|
||||
//data port
|
||||
case 0xc00000: case 0xc00002: {
|
||||
return readDataPort();
|
||||
}
|
||||
|
||||
//control port
|
||||
case 0xc00004: case 0xc00006: {
|
||||
return readControlPort();
|
||||
}
|
||||
|
||||
//counter
|
||||
case 0xc00008: case 0xc0000a: case 0xc0000c: case 0xc0000e: {
|
||||
return state.y << 8 | (state.x >> 1) << 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0x0000;
|
||||
}
|
||||
|
||||
auto VDP::write(uint24 addr, uint16 data) -> void {
|
||||
switch(addr & 0xc0001e) {
|
||||
|
||||
//data port
|
||||
case 0xc00000: case 0xc00002: {
|
||||
return writeDataPort(data);
|
||||
}
|
||||
|
||||
//control port
|
||||
case 0xc00004: case 0xc00006: {
|
||||
return writeControlPort(data);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto VDP::readDataPort() -> uint16 {
|
||||
io.commandPending = false;
|
||||
|
||||
//VRAM read
|
||||
if(io.command.bits(0,3) == 0) {
|
||||
auto address = io.address.bits(1,15);
|
||||
auto data = vram[address];
|
||||
io.address += io.dataIncrement;
|
||||
return data;
|
||||
}
|
||||
|
||||
//VSRAM read
|
||||
if(io.command.bits(0,3) == 4) {
|
||||
auto address = io.address.bits(1,6);
|
||||
if(address >= 40) return 0x0000;
|
||||
auto data = vsram[address];
|
||||
io.address += io.dataIncrement;
|
||||
return data;
|
||||
}
|
||||
|
||||
//CRAM read
|
||||
if(io.command.bits(0,3) == 8) {
|
||||
auto address = io.address.bits(1,6);
|
||||
auto data = cram[address];
|
||||
io.address += io.dataIncrement;
|
||||
return data.bits(0,2) << 1 | data.bits(3,5) << 2 | data.bits(6,8) << 3;
|
||||
}
|
||||
|
||||
return 0x0000;
|
||||
}
|
||||
|
||||
auto VDP::writeDataPort(uint16 data) -> void {
|
||||
io.commandPending = false;
|
||||
|
||||
//DMA VRAM fill
|
||||
if(io.dmaFillWait.lower()) {
|
||||
io.dmaFillByte = data >> 8;
|
||||
}
|
||||
|
||||
//VRAM write
|
||||
if(io.command.bits(0,3) == 1) {
|
||||
auto address = io.address.bits(1,15);
|
||||
if(io.address.bit(0)) data = data >> 8 | data << 8;
|
||||
vram[address] = data;
|
||||
if(address >= sprite.io.attributeAddress && address < sprite.io.attributeAddress + 320) {
|
||||
sprite.write(address, data);
|
||||
}
|
||||
io.address += io.dataIncrement;
|
||||
return;
|
||||
}
|
||||
|
||||
//VSRAM write
|
||||
if(io.command.bits(0,3) == 5) {
|
||||
auto address = io.address.bits(1,6);
|
||||
if(address >= 40) return;
|
||||
//data format: ---- --yy yyyy yyyy
|
||||
vsram[address] = data.bits(0,9);
|
||||
io.address += io.dataIncrement;
|
||||
return;
|
||||
}
|
||||
|
||||
//CRAM write
|
||||
if(io.command.bits(0,3) == 3) {
|
||||
auto address = io.address.bits(1,6);
|
||||
//data format: ---- bbb- ggg- rrr-
|
||||
cram[address] = data.bits(1,3) << 0 | data.bits(5,7) << 3 | data.bits(9,11) << 6;
|
||||
io.address += io.dataIncrement;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto VDP::readControlPort() -> uint16 {
|
||||
io.commandPending = false;
|
||||
|
||||
uint16 result = 0b0011'0100'0000'0000;
|
||||
result |= 1 << 9; //FIFO empty
|
||||
result |= (state.y >= 240) << 3; //vertical blank
|
||||
result |= (state.y >= 240 || state.x >= 320) << 2; //horizontal blank
|
||||
result |= io.command.bit(5) << 1; //DMA active
|
||||
return result;
|
||||
}
|
||||
|
||||
auto VDP::writeControlPort(uint16 data) -> void {
|
||||
//print("[VDPC] ", hex(data, 4L), "\n");
|
||||
|
||||
//command write (lo)
|
||||
if(io.commandPending) {
|
||||
io.commandPending = false;
|
||||
|
||||
io.command.bits(2,5) = data.bits(4,7);
|
||||
io.address.bits(14,15) = data.bits(0,1);
|
||||
io.dmaFillWait = io.dmaMode == 2 && io.command.bits(4,5) == 2;
|
||||
return;
|
||||
}
|
||||
|
||||
//command write (hi)
|
||||
if(data.bits(14,15) != 2) {
|
||||
io.commandPending = true;
|
||||
|
||||
io.command.bits(0,1) = data.bits(14,15);
|
||||
io.address.bits(0,13) = data.bits(0,13);
|
||||
return;
|
||||
}
|
||||
|
||||
//register write (d13 is ignored)
|
||||
if(data.bits(14,15) == 2)
|
||||
switch(data.bits(8,12)) {
|
||||
|
||||
//mode register 1
|
||||
case 0x00: {
|
||||
io.displayOverlayEnable = data.bit(0);
|
||||
io.counterLatch = data.bit(1);
|
||||
io.horizontalBlankInterruptEnable = data.bit(4);
|
||||
io.leftColumnBlank = data.bit(5);
|
||||
return;
|
||||
}
|
||||
|
||||
//mode register 2
|
||||
case 0x01: {
|
||||
io.videoMode = data.bit(2);
|
||||
io.overscan = data.bit(3);
|
||||
io.dmaEnable = data.bit(4);
|
||||
io.verticalBlankInterruptEnable = data.bit(5);
|
||||
io.displayEnable = data.bit(6);
|
||||
io.externalVRAM = data.bit(7);
|
||||
|
||||
if(!io.dmaEnable) io.command.bit(5) = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//plane A name table location
|
||||
case 0x02: {
|
||||
planeA.io.nametableAddress = data.bits(3,6) << 12;
|
||||
return;
|
||||
}
|
||||
|
||||
//window name table location
|
||||
case 0x03: {
|
||||
window.io.nametableAddress = data.bits(1,6) << 10;
|
||||
return;
|
||||
}
|
||||
|
||||
//plane B name table location
|
||||
case 0x04: {
|
||||
planeB.io.nametableAddress = data.bits(0,3) << 12;
|
||||
return;
|
||||
}
|
||||
|
||||
//sprite attribute table location
|
||||
case 0x05: {
|
||||
sprite.io.attributeAddress = data.bits(0,7) << 8;
|
||||
return;
|
||||
}
|
||||
|
||||
//sprite pattern base address
|
||||
case 0x06: {
|
||||
sprite.io.nametableAddressBase = data.bit(5);
|
||||
return;
|
||||
}
|
||||
|
||||
//background color
|
||||
case 0x07: {
|
||||
io.backgroundColor = data.bits(0,5);
|
||||
return;
|
||||
}
|
||||
|
||||
//horizontal interrupt counter
|
||||
case 0x0a: {
|
||||
io.horizontalInterruptCounter = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//mode register 3
|
||||
case 0x0b: {
|
||||
planeA.io.horizontalScrollMode = data.bits(0,1);
|
||||
planeB.io.horizontalScrollMode = data.bits(0,1);
|
||||
planeA.io.verticalScrollMode = data.bit(2);
|
||||
planeB.io.verticalScrollMode = data.bit(2);
|
||||
io.externalInterruptEnable = data.bit(3);
|
||||
return;
|
||||
}
|
||||
|
||||
//mode register 4
|
||||
case 0x0c: {
|
||||
io.tileWidth = data.bit(0) | data.bit(7) << 1;
|
||||
io.interlaceMode = data.bits(1,2);
|
||||
io.shadowHighlightEnable = data.bit(3);
|
||||
io.externalColorEnable = data.bit(4);
|
||||
io.horizontalSync = data.bit(5);
|
||||
io.verticalSync = data.bit(6);
|
||||
return;
|
||||
}
|
||||
|
||||
//horizontal scroll data location
|
||||
case 0x0d: {
|
||||
planeA.io.horizontalScrollAddress = data.bits(0,6) << 9;
|
||||
planeB.io.horizontalScrollAddress = data.bits(0,6) << 9;
|
||||
return;
|
||||
}
|
||||
|
||||
//nametable pattern base address
|
||||
case 0x0e: {
|
||||
io.nametableBasePatternA = data.bit(0);
|
||||
io.nametableBasePatternB = data.bit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
//data port auto-increment value
|
||||
case 0x0f: {
|
||||
io.dataIncrement = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//plane size
|
||||
case 0x10: {
|
||||
planeA.io.nametableWidth = data.bits(0,1);
|
||||
planeB.io.nametableWidth = data.bits(0,1);
|
||||
planeA.io.nametableHeight = data.bits(4,5);
|
||||
planeB.io.nametableHeight = data.bits(4,5);
|
||||
return;
|
||||
}
|
||||
|
||||
//window plane horizontal position
|
||||
case 0x11: {
|
||||
window.io.horizontalDirection = data.bit(7);
|
||||
window.io.horizontalOffset = data.bits(0,4) << 4;
|
||||
return;
|
||||
}
|
||||
|
||||
//window plane vertical position
|
||||
case 0x12: {
|
||||
window.io.verticalDirection = data.bit(7);
|
||||
window.io.verticalOffset = data.bits(0,4) << 3;
|
||||
return;
|
||||
}
|
||||
|
||||
//DMA length
|
||||
case 0x13: {
|
||||
io.dmaLength.bits(0,7) = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//DMA length
|
||||
case 0x14: {
|
||||
io.dmaLength.bits(8,15) = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//DMA source
|
||||
case 0x15: {
|
||||
io.dmaSource.bits(0,7) = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//DMA source
|
||||
case 0x16: {
|
||||
io.dmaSource.bits(8,15) = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//DMA source
|
||||
case 0x17: {
|
||||
io.dmaSource.bits(16,21) = data.bits(0,5);
|
||||
io.dmaMode = data.bits(6,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//unused
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
44
higan/md/vdp/render.cpp
Normal file
44
higan/md/vdp/render.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
auto VDP::scanline() -> void {
|
||||
state.x = 0;
|
||||
if(++state.y >= 262) state.y = 0;
|
||||
|
||||
if(state.y < screenHeight()) {
|
||||
planeA.scanline(state.y);
|
||||
window.scanline(state.y);
|
||||
planeB.scanline(state.y);
|
||||
sprite.scanline(state.y);
|
||||
}
|
||||
|
||||
if(state.y == 240) scheduler.exit(Scheduler::Event::Frame);
|
||||
|
||||
state.output = buffer + (state.y * 2 + 0) * 1280;
|
||||
}
|
||||
|
||||
auto VDP::run() -> void {
|
||||
if(!io.displayEnable) return outputPixel(0);
|
||||
if(state.y >= screenHeight()) return outputPixel(0);
|
||||
|
||||
auto& planeA = window.isWindowed(state.x, state.y) ? window : this->planeA;
|
||||
planeA.run(state.x, state.y);
|
||||
planeB.run(state.x, state.y);
|
||||
sprite.run(state.x, state.y);
|
||||
|
||||
auto output = io.backgroundColor;
|
||||
if(auto color = planeB.output.color) output = color;
|
||||
if(auto color = planeA.output.color) output = color;
|
||||
if(auto color = sprite.output.color) output = color;
|
||||
if(planeB.output.priority) if(auto color = planeB.output.color) output = color;
|
||||
if(planeA.output.priority) if(auto color = planeA.output.color) output = color;
|
||||
if(sprite.output.priority) if(auto color = sprite.output.color) output = color;
|
||||
|
||||
outputPixel(cram[output]);
|
||||
state.x++;
|
||||
}
|
||||
|
||||
auto VDP::outputPixel(uint9 color) -> void {
|
||||
for(auto n : range(4)) {
|
||||
state.output[ 0 + n] = color;
|
||||
state.output[1280 + n] = color;
|
||||
}
|
||||
state.output += 4;
|
||||
}
|
90
higan/md/vdp/sprite.cpp
Normal file
90
higan/md/vdp/sprite.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
auto VDP::Sprite::write(uint9 address, uint16 data) -> void {
|
||||
if(address > 320) return;
|
||||
|
||||
auto& object = oam[address >> 2];
|
||||
switch(address.bits(0,1)) {
|
||||
|
||||
case 0: {
|
||||
object.y = data.bits(0,8);
|
||||
break;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
object.link = data.bits(0,6);
|
||||
object.height = 1 + data.bits(8,9) << 3;
|
||||
object.width = 1 + data.bits(10,11) << 3;
|
||||
break;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
object.address = data.bits(0,10) << 4;
|
||||
object.horizontalFlip = data.bit(11);
|
||||
object.verticalFlip = data.bit(12);
|
||||
object.palette = data.bits(13,14);
|
||||
object.priority = data.bit(15);
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: {
|
||||
object.x = data.bits(0,8);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::Sprite::scanline(uint y) -> void {
|
||||
objects.reset();
|
||||
|
||||
uint7 link = 0;
|
||||
uint tiles = 0;
|
||||
do {
|
||||
auto& object = oam[link];
|
||||
link = object.link;
|
||||
|
||||
if(128 + y < object.y) continue;
|
||||
if(128 + y >= object.y + object.height) continue;
|
||||
if(object.x == 0) break;
|
||||
|
||||
objects.append(object);
|
||||
tiles += object.width >> 3;
|
||||
} while(link && link < 80 && objects.size() < 20 && tiles < 40);
|
||||
}
|
||||
|
||||
auto VDP::Sprite::run(uint x, uint y) -> void {
|
||||
output.priority = 0;
|
||||
output.color = 0;
|
||||
|
||||
for(auto& o : objects) {
|
||||
if(128 + x < o.x) continue;
|
||||
if(128 + x >= o.x + o.width) continue;
|
||||
|
||||
uint objectX = 128 + x - o.x;
|
||||
uint objectY = 128 + y - o.y;
|
||||
if(o.horizontalFlip) objectX = (o.width - 1) - objectX;
|
||||
if(o.verticalFlip) objectY = (o.height - 1) - objectY;
|
||||
|
||||
uint tileX = objectX >> 3;
|
||||
uint tileY = objectY >> 3;
|
||||
uint tileNumber = tileX * (o.height >> 3) + tileY;
|
||||
uint15 tileAddress = o.address + (tileNumber << 4);
|
||||
uint pixelX = objectX & 7;
|
||||
uint pixelY = objectY & 7;
|
||||
tileAddress += pixelY << 1 | pixelX >> 2;
|
||||
|
||||
uint16 tileData = vdp.vram[tileAddress];
|
||||
uint4 color = tileData >> (((pixelX & 3) ^ 3) << 2);
|
||||
if(color) {
|
||||
output.color = o.palette << 4 | color;
|
||||
output.priority = o.priority;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::Sprite::power() -> void {
|
||||
}
|
||||
|
||||
auto VDP::Sprite::reset() -> void {
|
||||
memory::fill(&io, sizeof(IO));
|
||||
}
|
@@ -1,28 +1,72 @@
|
||||
#include <md/md.hpp>
|
||||
|
||||
//256-width = colorburst * 15 / 10
|
||||
//320-width = colorburst * 15 / 8
|
||||
|
||||
namespace MegaDrive {
|
||||
|
||||
VDP vdp;
|
||||
#include "io.cpp"
|
||||
#include "dma.cpp"
|
||||
#include "render.cpp"
|
||||
#include "background.cpp"
|
||||
#include "sprite.cpp"
|
||||
|
||||
auto VDP::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), vdp.main();
|
||||
}
|
||||
|
||||
auto VDP::main() -> void {
|
||||
step(system.colorburst() * 15.0 / 10.0);
|
||||
scanline();
|
||||
if(state.y < screenHeight()) {
|
||||
if(state.y == 0) {
|
||||
cpu.lower(CPU::Interrupt::VerticalBlank);
|
||||
}
|
||||
cpu.lower(CPU::Interrupt::HorizontalBlank);
|
||||
for(uint x : range(320)) {
|
||||
run();
|
||||
step(4);
|
||||
}
|
||||
if(io.horizontalBlankInterruptEnable) {
|
||||
cpu.raise(CPU::Interrupt::HorizontalBlank);
|
||||
}
|
||||
step(430);
|
||||
} else {
|
||||
if(state.y == screenHeight()) {
|
||||
if(io.verticalBlankInterruptEnable) {
|
||||
cpu.raise(CPU::Interrupt::VerticalBlank);
|
||||
}
|
||||
}
|
||||
step(1710);
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::step(uint clocks) -> void {
|
||||
while(clocks--) {
|
||||
dmaRun();
|
||||
Thread::step(1);
|
||||
synchronize(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::refresh() -> void {
|
||||
Emulator::video.refresh(buffer, 1280 * sizeof(uint32), 1280, 480);
|
||||
}
|
||||
|
||||
auto VDP::power() -> void {
|
||||
planeA.power();
|
||||
window.power();
|
||||
planeB.power();
|
||||
sprite.power();
|
||||
}
|
||||
|
||||
auto VDP::reset() -> void {
|
||||
create(VDP::Enter, system.colorburst() * 15.0 / 10.0);
|
||||
create(VDP::Enter, system.colorburst() * 15.0 / 2.0);
|
||||
|
||||
memory::fill(&io, sizeof(IO));
|
||||
memory::fill(&state, sizeof(State));
|
||||
|
||||
planeA.reset();
|
||||
window.reset();
|
||||
planeB.reset();
|
||||
sprite.reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,9 +4,191 @@ struct VDP : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
//io.cpp
|
||||
auto read(uint24 addr) -> uint16;
|
||||
auto write(uint24 addr, uint16 data) -> void;
|
||||
|
||||
auto readDataPort() -> uint16;
|
||||
auto writeDataPort(uint16 data) -> void;
|
||||
|
||||
auto readControlPort() -> uint16;
|
||||
auto writeControlPort(uint16 data) -> void;
|
||||
|
||||
//dma.cpp
|
||||
auto dmaRun() -> void;
|
||||
auto dmaLoad() -> void;
|
||||
auto dmaFill() -> void;
|
||||
auto dmaCopy() -> void;
|
||||
|
||||
//render.cpp
|
||||
auto scanline() -> void;
|
||||
auto run() -> void;
|
||||
auto outputPixel(uint9 color) -> void;
|
||||
|
||||
//background.cpp
|
||||
struct Background {
|
||||
enum class ID : uint { PlaneA, Window, PlaneB } id;
|
||||
|
||||
auto isWindowed(uint x, uint y) -> bool;
|
||||
|
||||
auto updateHorizontalScroll(uint y) -> void;
|
||||
auto updateVerticalScroll(uint x, uint y) -> void;
|
||||
|
||||
auto nametableAddress() -> uint15;
|
||||
auto nametableWidth() -> uint;
|
||||
auto nametableHeight() -> uint;
|
||||
|
||||
auto scanline(uint y) -> void;
|
||||
auto run(uint x, uint y) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
struct IO {
|
||||
uint15 nametableAddress;
|
||||
|
||||
//PlaneA, PlaneB
|
||||
uint2 nametableWidth;
|
||||
uint2 nametableHeight;
|
||||
uint15 horizontalScrollAddress;
|
||||
uint2 horizontalScrollMode;
|
||||
uint1 verticalScrollMode;
|
||||
|
||||
//Window
|
||||
uint1 horizontalDirection;
|
||||
uint10 horizontalOffset;
|
||||
uint1 verticalDirection;
|
||||
uint10 verticalOffset;
|
||||
} io;
|
||||
|
||||
struct State {
|
||||
uint10 horizontalScroll;
|
||||
uint10 verticalScroll;
|
||||
} state;
|
||||
|
||||
struct Output {
|
||||
uint6 color;
|
||||
boolean priority;
|
||||
} output;
|
||||
};
|
||||
Background planeA{Background::ID::PlaneA};
|
||||
Background window{Background::ID::Window};
|
||||
Background planeB{Background::ID::PlaneB};
|
||||
|
||||
//sprite.cpp
|
||||
struct Sprite {
|
||||
auto write(uint9 addr, uint16 data) -> void;
|
||||
auto scanline(uint y) -> void;
|
||||
auto run(uint x, uint y) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
struct IO {
|
||||
uint15 attributeAddress;
|
||||
uint1 nametableAddressBase;
|
||||
} io;
|
||||
|
||||
struct Object {
|
||||
uint9 x;
|
||||
uint9 y;
|
||||
uint width;
|
||||
uint height;
|
||||
bool horizontalFlip;
|
||||
bool verticalFlip;
|
||||
uint2 palette;
|
||||
uint1 priority;
|
||||
uint15 address;
|
||||
uint7 link;
|
||||
};
|
||||
|
||||
struct Output {
|
||||
uint6 color;
|
||||
boolean priority;
|
||||
} output;
|
||||
|
||||
array<Object, 80> oam;
|
||||
array<Object, 20> objects;
|
||||
};
|
||||
Sprite sprite;
|
||||
|
||||
private:
|
||||
auto screenWidth() const -> uint { return io.tileWidth ? 320 : 256; }
|
||||
auto screenHeight() const -> uint { return io.overscan ? 240 : 224; }
|
||||
|
||||
uint16 vram[32768];
|
||||
uint16 vramExpansion[32768]; //not present in stock Mega Drive hardware
|
||||
uint9 cram[64];
|
||||
uint10 vsram[40];
|
||||
|
||||
struct IO {
|
||||
//internal state
|
||||
boolean dmaFillWait;
|
||||
uint8 dmaFillByte;
|
||||
|
||||
//command
|
||||
uint6 command;
|
||||
uint16 address;
|
||||
boolean commandPending;
|
||||
|
||||
//$00 mode register 1
|
||||
uint1 displayOverlayEnable;
|
||||
uint1 counterLatch;
|
||||
uint1 horizontalBlankInterruptEnable;
|
||||
uint1 leftColumnBlank;
|
||||
|
||||
//$01 mode register 2
|
||||
uint1 videoMode; //0 = Master System; 1 = Mega Drive
|
||||
uint1 overscan; //0 = 224 lines; 1 = 240 lines
|
||||
uint1 dmaEnable;
|
||||
uint1 verticalBlankInterruptEnable;
|
||||
uint1 displayEnable;
|
||||
uint1 externalVRAM;
|
||||
|
||||
//$07 background color
|
||||
uint6 backgroundColor;
|
||||
|
||||
//$0a horizontal interrupt counter
|
||||
uint8 horizontalInterruptCounter;
|
||||
|
||||
//$0b mode register 3
|
||||
uint1 externalInterruptEnable;
|
||||
|
||||
//$0c mode register 4
|
||||
uint2 tileWidth;
|
||||
uint2 interlaceMode;
|
||||
uint1 shadowHighlightEnable;
|
||||
uint1 externalColorEnable;
|
||||
uint1 horizontalSync;
|
||||
uint1 verticalSync;
|
||||
|
||||
//$0e nametable pattern base address
|
||||
uint1 nametableBasePatternA;
|
||||
uint1 nametableBasePatternB;
|
||||
|
||||
//$0f data port auto-increment value
|
||||
uint8 dataIncrement;
|
||||
|
||||
//$13-$14 DMA length
|
||||
uint16 dmaLength;
|
||||
|
||||
//$15-$17 DMA source
|
||||
uint22 dmaSource;
|
||||
uint2 dmaMode;
|
||||
} io;
|
||||
|
||||
struct State {
|
||||
uint32* output = nullptr;
|
||||
uint x;
|
||||
uint y;
|
||||
} state;
|
||||
|
||||
uint32 buffer[1280 * 480];
|
||||
};
|
||||
|
||||
extern VDP vdp;
|
||||
|
@@ -9,10 +9,12 @@ auto YM2612::Enter() -> void {
|
||||
}
|
||||
|
||||
auto YM2612::main() -> void {
|
||||
step(system.colorburst() * 15.0 / 7.0);
|
||||
step(1);
|
||||
}
|
||||
|
||||
auto YM2612::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto YM2612::power() -> void {
|
||||
|
15
higan/ms/GNUmakefile
Normal file
15
higan/ms/GNUmakefile
Normal file
@@ -0,0 +1,15 @@
|
||||
processors += z80
|
||||
|
||||
objects += ms-interface
|
||||
objects += ms-cpu ms-vdp ms-psg
|
||||
objects += ms-system ms-cartridge ms-bus
|
||||
objects += ms-controller
|
||||
|
||||
obj/ms-interface.o: ms/interface/interface.cpp $(call rwildcard,ms/interface)
|
||||
obj/ms-cpu.o: ms/cpu/cpu.cpp $(call rwildcard,ms/cpu)
|
||||
obj/ms-vdp.o: ms/vdp/vdp.cpp $(call rwildcard,ms/vdp)
|
||||
obj/ms-psg.o: ms/psg/psg.cpp $(call rwildcard,ms/psg)
|
||||
obj/ms-system.o: ms/system/system.cpp $(call rwildcard,ms/system)
|
||||
obj/ms-cartridge.o: ms/cartridge/cartridge.cpp $(call rwildcard,ms/cartridge)
|
||||
obj/ms-bus.o: ms/bus/bus.cpp $(call rwildcard,ms/bus)
|
||||
obj/ms-controller.o: ms/controller/controller.cpp $(call rwildcard,ms/controller)
|
86
higan/ms/bus/bus.cpp
Normal file
86
higan/ms/bus/bus.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
Bus bus;
|
||||
|
||||
auto Bus::read(uint16 addr) -> uint8 {
|
||||
if(auto data = cartridge.read(addr)) return data();
|
||||
if(addr >= 0xc000) return ram[addr & 0x1fff];
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
auto Bus::write(uint16 addr, uint8 data) -> void {
|
||||
if(cartridge.write(addr, data)) return;
|
||||
if(addr >= 0xc000) ram[addr & 0x1fff] = data;
|
||||
}
|
||||
|
||||
auto Bus::in(uint8 addr) -> uint8 {
|
||||
switch(addr >> 6) {
|
||||
|
||||
case 0: {
|
||||
if(system.model() == Model::GameGear) {
|
||||
bool start = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 6);
|
||||
return start << 7 | 0x7f;
|
||||
}
|
||||
|
||||
return 0xff; //SMS1 = MDR, SMS2 = 0xff
|
||||
}
|
||||
|
||||
case 1: {
|
||||
return !addr.bit(0) ? vdp.vcounter() : vdp.hcounter();
|
||||
}
|
||||
|
||||
case 2: {
|
||||
return !addr.bit(0) ? vdp.data() : vdp.status();
|
||||
}
|
||||
|
||||
case 3: {
|
||||
if(system.model() == Model::MasterSystem) {
|
||||
bool reset = !platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 0);
|
||||
auto port1 = peripherals.controllerPort1->readData();
|
||||
auto port2 = peripherals.controllerPort2->readData();
|
||||
if(addr.bit(0) == 0) {
|
||||
return port1.bits(0,5) << 0 | port2.bits(0,1) << 6;
|
||||
} else {
|
||||
return port2.bits(2,5) << 0 | reset << 4 | 1 << 5 | port1.bit(6) << 6 | port2.bit(6) << 7;
|
||||
}
|
||||
}
|
||||
if(system.model() == Model::GameGear) {
|
||||
bool up = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 0);
|
||||
bool down = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 1);
|
||||
bool left = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 2);
|
||||
bool right = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 3);
|
||||
bool one = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 4);
|
||||
bool two = !platform->inputPoll(ID::Port::Hardware, ID::Device::GameGearControls, 5);
|
||||
if(!up && !down) up = 1, down = 1;
|
||||
if(!left && !right) left = 1, right = 1;
|
||||
if(addr.bit(0) == 0) {
|
||||
return up << 0 | down << 1 | left << 2 | right << 3 | one << 4 | two << 5 | 1 << 6 | 1 << 7;
|
||||
} else {
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
auto Bus::out(uint8 addr, uint8 data) -> void {
|
||||
switch(addr >> 6) {
|
||||
|
||||
case 2: {
|
||||
return !addr.bit(0) ? vdp.data(data) : vdp.control(data);
|
||||
}
|
||||
|
||||
case 3: {
|
||||
return; //unmapped
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
12
higan/ms/bus/bus.hpp
Normal file
12
higan/ms/bus/bus.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
struct Bus : Processor::Z80::Bus {
|
||||
auto read(uint16 addr) -> uint8 override;
|
||||
auto write(uint16 addr, uint8 data) -> void override;
|
||||
|
||||
auto in(uint8 addr) -> uint8 override;
|
||||
auto out(uint8 addr, uint8 data) -> void override;
|
||||
|
||||
private:
|
||||
uint8 ram[0x2000];
|
||||
};
|
||||
|
||||
extern Bus bus;
|
109
higan/ms/cartridge/cartridge.cpp
Normal file
109
higan/ms/cartridge/cartridge.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
Cartridge cartridge;
|
||||
#include "mapper.cpp"
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
information = {};
|
||||
|
||||
switch(system.model()) {
|
||||
case Model::MasterSystem:
|
||||
if(auto pathID = platform->load(ID::MasterSystem, "Master System", "ms")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
case Model::GameGear:
|
||||
if(auto pathID = platform->load(ID::GameGear, "Game Gear", "gg")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
}
|
||||
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
information.title = document["information/title"].text();
|
||||
|
||||
if(auto node = document["board/rom"]) {
|
||||
rom.size = node["size"].natural();
|
||||
rom.mask = bit::round(rom.size) - 1;
|
||||
if(rom.size) {
|
||||
rom.data = new uint8[rom.mask + 1];
|
||||
if(auto name = node["name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
|
||||
fp->read(rom.data, rom.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(auto node = document["board/ram"]) {
|
||||
ram.size = node["size"].natural();
|
||||
ram.mask = bit::round(ram.size) - 1;
|
||||
if(ram.size) {
|
||||
ram.data = new uint8[ram.mask + 1];
|
||||
if(auto name = node["name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read)) {
|
||||
fp->read(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto name = document["board/ram/name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Write)) {
|
||||
fp->write(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
delete[] rom.data;
|
||||
delete[] ram.data;
|
||||
rom = {};
|
||||
ram = {};
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
memory::fill(&mapper, sizeof(Mapper));
|
||||
mapper.romPage0 = 0;
|
||||
mapper.romPage1 = 1;
|
||||
mapper.romPage2 = 2;
|
||||
}
|
||||
|
||||
auto Cartridge::Memory::mirror(uint addr, uint size) -> uint {
|
||||
uint base = 0;
|
||||
uint mask = 1 << 21;
|
||||
while(addr >= size) {
|
||||
while(!(addr & mask)) mask >>= 1;
|
||||
addr -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
return base + addr;
|
||||
}
|
||||
|
||||
auto Cartridge::Memory::read(uint addr) -> uint8 {
|
||||
if(!size) return 0x00;
|
||||
return this->data[mirror(addr, size)];
|
||||
}
|
||||
|
||||
auto Cartridge::Memory::write(uint addr, uint8 data) -> void {
|
||||
if(!size) return;
|
||||
this->data[mirror(addr, size)] = data;
|
||||
}
|
||||
|
||||
}
|
57
higan/ms/cartridge/cartridge.hpp
Normal file
57
higan/ms/cartridge/cartridge.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
struct Cartridge {
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto sha256() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
//mapper.cpp
|
||||
auto read(uint16 addr) -> maybe<uint8>;
|
||||
auto write(uint16 addr, uint8 data) -> bool;
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
string sha256;
|
||||
string manifest;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
struct Memory {
|
||||
uint8* data = nullptr;
|
||||
uint size = 0;
|
||||
uint mask = 0;
|
||||
|
||||
static auto mirror(uint addr, uint size) -> uint;
|
||||
auto read(uint addr) -> uint8;
|
||||
auto write(uint addr, uint8 data) -> void;
|
||||
};
|
||||
|
||||
Memory rom;
|
||||
Memory ram;
|
||||
|
||||
struct Mapper {
|
||||
//$fffc
|
||||
uint2 shift;
|
||||
uint1 ramPage2;
|
||||
uint1 ramEnablePage2;
|
||||
uint1 ramEnablePage3;
|
||||
uint1 romWriteEnable;
|
||||
|
||||
//$fffd
|
||||
uint8 romPage0;
|
||||
|
||||
//$fffe
|
||||
uint8 romPage1;
|
||||
|
||||
//$ffff
|
||||
uint8 romPage2;
|
||||
} mapper;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
92
higan/ms/cartridge/mapper.cpp
Normal file
92
higan/ms/cartridge/mapper.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
auto Cartridge::read(uint16 addr) -> maybe<uint8> {
|
||||
uint2 page = addr >> 14;
|
||||
addr &= 0x3fff;
|
||||
|
||||
switch(page) {
|
||||
|
||||
case 0: {
|
||||
if(addr <= 0x03ff) return rom.read(addr);
|
||||
return rom.read(mapper.romPage0 << 14 | addr);
|
||||
}
|
||||
|
||||
case 1: {
|
||||
return rom.read(mapper.romPage1 << 14 | addr);
|
||||
}
|
||||
|
||||
case 2: {
|
||||
if(mapper.ramEnablePage2) {
|
||||
return ram.read(mapper.ramPage2 << 14 | addr);
|
||||
}
|
||||
|
||||
return rom.read(mapper.romPage2 << 14 | addr);
|
||||
}
|
||||
|
||||
case 3: {
|
||||
if(mapper.ramEnablePage3) {
|
||||
return ram.read(addr);
|
||||
}
|
||||
|
||||
return nothing;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto Cartridge::write(uint16 addr, uint8 data) -> bool {
|
||||
if(addr == 0xfffc) {
|
||||
mapper.shift = data.bits(0,1);
|
||||
mapper.ramPage2 = data.bit(2);
|
||||
mapper.ramEnablePage2 = data.bit(3);
|
||||
mapper.ramEnablePage3 = data.bit(4);
|
||||
mapper.romWriteEnable = data.bit(7);
|
||||
}
|
||||
|
||||
if(addr == 0xfffd) {
|
||||
mapper.romPage0 = data;
|
||||
}
|
||||
|
||||
if(addr == 0xfffe) {
|
||||
mapper.romPage1 = data;
|
||||
}
|
||||
|
||||
if(addr == 0xffff) {
|
||||
mapper.romPage2 = data;
|
||||
}
|
||||
|
||||
uint2 page = addr >> 14;
|
||||
addr &= 0x3fff;
|
||||
|
||||
switch(page) {
|
||||
|
||||
case 0: {
|
||||
return false;
|
||||
}
|
||||
|
||||
case 1: {
|
||||
return false;
|
||||
}
|
||||
|
||||
case 2: {
|
||||
if(mapper.ramEnablePage2) {
|
||||
ram.write(mapper.ramPage2 << 14 | addr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
case 3: {
|
||||
if(mapper.ramEnablePage3) {
|
||||
ram.write(addr, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
27
higan/ms/controller/controller.cpp
Normal file
27
higan/ms/controller/controller.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
#include "gamepad/gamepad.cpp"
|
||||
|
||||
Controller::Controller(uint port) : port(port) {
|
||||
if(!handle()) create(Controller::Enter, 100);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
}
|
||||
|
||||
auto Controller::Enter() -> void {
|
||||
while(true) {
|
||||
scheduler.synchronize();
|
||||
if(auto device = peripherals.controllerPort1) if(device->active()) device->main();
|
||||
if(auto device = peripherals.controllerPort2) if(device->active()) device->main();
|
||||
}
|
||||
}
|
||||
|
||||
auto Controller::main() -> void {
|
||||
step(1);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
}
|
13
higan/ms/controller/controller.hpp
Normal file
13
higan/ms/controller/controller.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
struct Controller : Thread {
|
||||
Controller(uint port);
|
||||
virtual ~Controller();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
virtual auto readData() -> uint7 { return 0x7f; }
|
||||
|
||||
const uint port;
|
||||
};
|
||||
|
||||
#include "gamepad/gamepad.hpp"
|
13
higan/ms/controller/gamepad/gamepad.cpp
Normal file
13
higan/ms/controller/gamepad/gamepad.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
Gamepad::Gamepad(uint port) : Controller(port) {
|
||||
}
|
||||
|
||||
auto Gamepad::readData() -> uint7 {
|
||||
uint7 data = 0x7f;
|
||||
data.bit(0) = !platform->inputPoll(port, ID::Device::Gamepad, Up);
|
||||
data.bit(1) = !platform->inputPoll(port, ID::Device::Gamepad, Down);
|
||||
data.bit(2) = !platform->inputPoll(port, ID::Device::Gamepad, Left);
|
||||
data.bit(3) = !platform->inputPoll(port, ID::Device::Gamepad, Right);
|
||||
data.bit(4) = !platform->inputPoll(port, ID::Device::Gamepad, One);
|
||||
data.bit(5) = !platform->inputPoll(port, ID::Device::Gamepad, Two);
|
||||
return data;
|
||||
}
|
9
higan/ms/controller/gamepad/gamepad.hpp
Normal file
9
higan/ms/controller/gamepad/gamepad.hpp
Normal file
@@ -0,0 +1,9 @@
|
||||
struct Gamepad : Controller {
|
||||
enum : uint {
|
||||
Up, Down, Left, Right, One, Two,
|
||||
};
|
||||
|
||||
Gamepad(uint port);
|
||||
|
||||
auto readData() -> uint7 override;
|
||||
};
|
60
higan/ms/cpu/cpu.cpp
Normal file
60
higan/ms/cpu/cpu.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(state.nmiLine) {
|
||||
state.nmiLine = 0; //edge-sensitive
|
||||
irq(0, 0x0066, 0xff);
|
||||
}
|
||||
|
||||
if(state.intLine) {
|
||||
//level-sensitive
|
||||
irq(1, 0x0038, 0xff);
|
||||
}
|
||||
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(vdp);
|
||||
synchronize(psg);
|
||||
for(auto peripheral : peripherals) synchronize(*peripheral);
|
||||
}
|
||||
|
||||
//called once per frame
|
||||
auto CPU::pollPause() -> void {
|
||||
if(system.model() == Model::MasterSystem) {
|
||||
static bool pause = 0;
|
||||
bool state = platform->inputPoll(ID::Port::Hardware, ID::Device::MasterSystemControls, 1);
|
||||
if(!pause && state) setNMI(1);
|
||||
pause = state;
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::setNMI(bool value) -> void {
|
||||
state.nmiLine = value;
|
||||
}
|
||||
|
||||
auto CPU::setINT(bool value) -> void {
|
||||
state.intLine = value;
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
Z80::bus = &MasterSystem::bus;
|
||||
Z80::power();
|
||||
create(CPU::Enter, system.colorburst());
|
||||
|
||||
r.pc = 0x0000; //reset vector address
|
||||
|
||||
memory::fill(&state, sizeof(State));
|
||||
}
|
||||
|
||||
}
|
23
higan/ms/cpu/cpu.hpp
Normal file
23
higan/ms/cpu/cpu.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
//Zilog Z80
|
||||
|
||||
struct CPU : Processor::Z80, Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto pollPause() -> void;
|
||||
auto setNMI(bool value) -> void;
|
||||
auto setINT(bool value) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
vector<Thread*> peripherals;
|
||||
|
||||
private:
|
||||
struct State {
|
||||
boolean nmiLine;
|
||||
boolean intLine;
|
||||
} state;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
118
higan/ms/interface/game-gear.cpp
Normal file
118
higan/ms/interface/game-gear.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
GameGearInterface::GameGearInterface() {
|
||||
information.manufacturer = "Sega";
|
||||
information.name = "Game Gear";
|
||||
information.overscan = false;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = false;
|
||||
information.capability.cheats = false;
|
||||
|
||||
media.append({ID::GameGear, "Game Gear", "gg"});
|
||||
|
||||
Port hardware{ID::Port::Hardware, "Hardware"};
|
||||
|
||||
{ Device device{ID::Device::GameGearControls, "Controls"};
|
||||
device.inputs.append({0, "Up"});
|
||||
device.inputs.append({0, "Down"});
|
||||
device.inputs.append({0, "Left"});
|
||||
device.inputs.append({0, "Right"});
|
||||
device.inputs.append({0, "1"});
|
||||
device.inputs.append({0, "2"});
|
||||
device.inputs.append({0, "Start"});
|
||||
hardware.devices.append(device);
|
||||
}
|
||||
|
||||
ports.append(move(hardware));
|
||||
}
|
||||
|
||||
auto GameGearInterface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
}
|
||||
|
||||
auto GameGearInterface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto GameGearInterface::videoSize() -> VideoSize {
|
||||
return {160, 144};
|
||||
}
|
||||
|
||||
auto GameGearInterface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
uint w = 160;
|
||||
uint h = 144;
|
||||
uint m = min(width / w, height / h);
|
||||
return {w * m, h * m};
|
||||
}
|
||||
|
||||
auto GameGearInterface::videoFrequency() -> double {
|
||||
return 60.0;
|
||||
}
|
||||
|
||||
auto GameGearInterface::videoColors() -> uint32 {
|
||||
return 1 << 12;
|
||||
}
|
||||
|
||||
auto GameGearInterface::videoColor(uint32 color) -> uint64 {
|
||||
uint4 B = color >> 8;
|
||||
uint4 G = color >> 4;
|
||||
uint4 R = color >> 0;
|
||||
|
||||
uint64 r = image::normalize(R, 4, 16);
|
||||
uint64 g = image::normalize(G, 4, 16);
|
||||
uint64 b = image::normalize(B, 4, 16);
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
}
|
||||
|
||||
auto GameGearInterface::audioFrequency() -> double {
|
||||
return 44'100.0;
|
||||
}
|
||||
|
||||
auto GameGearInterface::loaded() -> bool {
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto GameGearInterface::load(uint id) -> bool {
|
||||
if(id == ID::GameGear) return system.load(this, Model::GameGear);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto GameGearInterface::save() -> void {
|
||||
system.save();
|
||||
}
|
||||
|
||||
auto GameGearInterface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto GameGearInterface::connect(uint port, uint device) -> void {
|
||||
peripherals.connect(port, device);
|
||||
}
|
||||
|
||||
auto GameGearInterface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
|
||||
auto GameGearInterface::run() -> void {
|
||||
system.run();
|
||||
}
|
||||
|
||||
auto GameGearInterface::serialize() -> serializer {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto GameGearInterface::unserialize(serializer& s) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto GameGearInterface::cap(const string& name) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto GameGearInterface::get(const string& name) -> any {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto GameGearInterface::set(const string& name, const any& value) -> bool {
|
||||
return false;
|
||||
}
|
9
higan/ms/interface/interface.cpp
Normal file
9
higan/ms/interface/interface.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
Settings settings;
|
||||
#include "master-system.cpp"
|
||||
#include "game-gear.cpp"
|
||||
|
||||
}
|
97
higan/ms/interface/interface.hpp
Normal file
97
higan/ms/interface/interface.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
namespace MasterSystem {
|
||||
|
||||
struct ID {
|
||||
enum : uint {
|
||||
System,
|
||||
MasterSystem,
|
||||
GameGear,
|
||||
};
|
||||
|
||||
struct Port { enum : uint {
|
||||
Hardware,
|
||||
Controller1,
|
||||
Controller2,
|
||||
};};
|
||||
|
||||
struct Device { enum : uint {
|
||||
None,
|
||||
MasterSystemControls,
|
||||
GameGearControls,
|
||||
Gamepad,
|
||||
};};
|
||||
};
|
||||
|
||||
struct MasterSystemInterface : Emulator::Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
MasterSystemInterface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto videoSize() -> VideoSize override;
|
||||
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto load(uint id) -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto connect(uint port, uint device) -> void override;
|
||||
auto power() -> void override;
|
||||
auto run() -> void override;
|
||||
|
||||
auto serialize() -> serializer override;
|
||||
auto unserialize(serializer&) -> bool override;
|
||||
|
||||
auto cap(const string& name) -> bool override;
|
||||
auto get(const string& name) -> any override;
|
||||
auto set(const string& name, const any& value) -> bool override;
|
||||
};
|
||||
|
||||
struct GameGearInterface : Emulator::Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
GameGearInterface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
||||
auto videoSize() -> VideoSize override;
|
||||
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
|
||||
auto videoFrequency() -> double override;
|
||||
auto videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto load(uint id) -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto connect(uint port, uint device) -> void override;
|
||||
auto power() -> void override;
|
||||
auto run() -> void override;
|
||||
|
||||
auto serialize() -> serializer override;
|
||||
auto unserialize(serializer&) -> bool override;
|
||||
|
||||
auto cap(const string& name) -> bool override;
|
||||
auto get(const string& name) -> any override;
|
||||
auto set(const string& name, const any& value) -> bool override;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
uint controllerPort1 = 0;
|
||||
uint controllerPort2 = 0;
|
||||
};
|
||||
|
||||
extern Settings settings;
|
||||
|
||||
}
|
134
higan/ms/interface/master-system.cpp
Normal file
134
higan/ms/interface/master-system.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
MasterSystemInterface::MasterSystemInterface() {
|
||||
information.manufacturer = "Sega";
|
||||
information.name = "Master System";
|
||||
information.overscan = true;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = false;
|
||||
information.capability.cheats = false;
|
||||
|
||||
media.append({ID::MasterSystem, "Master System", "ms"});
|
||||
|
||||
Port hardware{ID::Port::Hardware, "Hardware"};
|
||||
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
|
||||
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
|
||||
|
||||
{ Device device{ID::Device::MasterSystemControls, "Controls"};
|
||||
device.inputs.append({0, "Reset"});
|
||||
device.inputs.append({0, "Pause"});
|
||||
hardware.devices.append(device);
|
||||
}
|
||||
|
||||
{ Device device{ID::Device::None, "None"};
|
||||
controllerPort1.devices.append(device);
|
||||
controllerPort2.devices.append(device);
|
||||
}
|
||||
|
||||
{ Device device{ID::Device::Gamepad, "Gamepad"};
|
||||
device.inputs.append({0, "Up"});
|
||||
device.inputs.append({0, "Down"});
|
||||
device.inputs.append({0, "Left"});
|
||||
device.inputs.append({0, "Right"});
|
||||
device.inputs.append({0, "1"});
|
||||
device.inputs.append({0, "2"});
|
||||
controllerPort1.devices.append(device);
|
||||
controllerPort2.devices.append(device);
|
||||
}
|
||||
|
||||
ports.append(move(hardware));
|
||||
ports.append(move(controllerPort1));
|
||||
ports.append(move(controllerPort2));
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::videoSize() -> VideoSize {
|
||||
return {256, 240};
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
auto a = arc ? 8.0 / 7.0 : 1.0;
|
||||
uint w = 256;
|
||||
uint h = 240;
|
||||
uint m = min(width / (w * a), height / h);
|
||||
return {uint(w * a * m), uint(h * m)};
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::videoFrequency() -> double {
|
||||
return 60.0;
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::videoColors() -> uint32 {
|
||||
return 1 << 6;
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::videoColor(uint32 color) -> uint64 {
|
||||
uint2 B = color >> 4;
|
||||
uint2 G = color >> 2;
|
||||
uint2 R = color >> 0;
|
||||
|
||||
uint64 r = image::normalize(R, 2, 16);
|
||||
uint64 g = image::normalize(G, 2, 16);
|
||||
uint64 b = image::normalize(B, 2, 16);
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::audioFrequency() -> double {
|
||||
return 44'100.0;
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::loaded() -> bool {
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::load(uint id) -> bool {
|
||||
if(id == ID::MasterSystem) return system.load(this, Model::MasterSystem);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::save() -> void {
|
||||
system.save();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::connect(uint port, uint device) -> void {
|
||||
peripherals.connect(port, device);
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::run() -> void {
|
||||
system.run();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::serialize() -> serializer {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::unserialize(serializer& s) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::cap(const string& name) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::get(const string& name) -> any {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::set(const string& name, const any& value) -> bool {
|
||||
return false;
|
||||
}
|
46
higan/ms/ms.hpp
Normal file
46
higan/ms/ms.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
//license: GPLv3
|
||||
//started: 2016-08-17
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <emulator/thread.hpp>
|
||||
#include <emulator/scheduler.hpp>
|
||||
|
||||
#include <processor/z80/z80.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
#define platform Emulator::platform
|
||||
using File = Emulator::File;
|
||||
using Scheduler = Emulator::Scheduler;
|
||||
extern Scheduler scheduler;
|
||||
struct Interface;
|
||||
|
||||
enum class Model : uint {
|
||||
MasterSystem,
|
||||
GameGear,
|
||||
};
|
||||
|
||||
struct Thread : Emulator::Thread {
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
Emulator::Thread::create(entrypoint, frequency);
|
||||
scheduler.append(*this);
|
||||
}
|
||||
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(clock() >= thread.clock()) scheduler.resume(thread);
|
||||
}
|
||||
};
|
||||
|
||||
#include <ms/controller/controller.hpp>
|
||||
|
||||
#include <ms/cpu/cpu.hpp>
|
||||
#include <ms/vdp/vdp.hpp>
|
||||
#include <ms/psg/psg.hpp>
|
||||
|
||||
#include <ms/system/system.hpp>
|
||||
#include <ms/cartridge/cartridge.hpp>
|
||||
#include <ms/bus/bus.hpp>
|
||||
}
|
||||
|
||||
#include <ms/interface/interface.hpp>
|
26
higan/ms/psg/psg.cpp
Normal file
26
higan/ms/psg/psg.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
PSG psg;
|
||||
|
||||
auto PSG::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), psg.main();
|
||||
}
|
||||
|
||||
auto PSG::main() -> void {
|
||||
step(1);
|
||||
stream->sample(0.0, 0.0);
|
||||
}
|
||||
|
||||
auto PSG::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto PSG::power() -> void {
|
||||
create(PSG::Enter, system.colorburst());
|
||||
stream = Emulator::audio.createStream(2, system.colorburst());
|
||||
}
|
||||
|
||||
}
|
13
higan/ms/psg/psg.hpp
Normal file
13
higan/ms/psg/psg.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
//TI SN76489
|
||||
|
||||
struct PSG : Thread {
|
||||
shared_pointer<Emulator::Stream> stream;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
};
|
||||
|
||||
extern PSG psg;
|
44
higan/ms/system/peripherals.cpp
Normal file
44
higan/ms/system/peripherals.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
Peripherals peripherals;
|
||||
|
||||
auto Peripherals::unload() -> void {
|
||||
delete controllerPort1;
|
||||
delete controllerPort2;
|
||||
controllerPort1 = nullptr;
|
||||
controllerPort2 = nullptr;
|
||||
}
|
||||
|
||||
auto Peripherals::reset() -> void {
|
||||
connect(ID::Port::Controller1, settings.controllerPort1);
|
||||
connect(ID::Port::Controller2, settings.controllerPort2);
|
||||
}
|
||||
|
||||
auto Peripherals::connect(uint port, uint device) -> void {
|
||||
cpu.peripherals.reset();
|
||||
|
||||
if(system.model() == Model::MasterSystem) {
|
||||
if(port == ID::Port::Controller1) {
|
||||
settings.controllerPort1 = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete controllerPort1;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: controllerPort1 = new Controller(ID::Port::Controller1); break;
|
||||
case ID::Device::Gamepad: controllerPort1 = new Gamepad(ID::Port::Controller1); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(port == ID::Port::Controller2) {
|
||||
settings.controllerPort2 = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete controllerPort2;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: controllerPort2 = new Controller(ID::Port::Controller2); break;
|
||||
case ID::Device::Gamepad: controllerPort2 = new Gamepad(ID::Port::Controller2); break;
|
||||
}
|
||||
}
|
||||
|
||||
cpu.peripherals.append(controllerPort1);
|
||||
cpu.peripherals.append(controllerPort2);
|
||||
}
|
||||
}
|
59
higan/ms/system/system.cpp
Normal file
59
higan/ms/system/system.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
#include "peripherals.cpp"
|
||||
System system;
|
||||
Scheduler scheduler;
|
||||
|
||||
auto System::run() -> void {
|
||||
if(scheduler.enter() == Scheduler::Event::Frame) {
|
||||
cpu.pollPause();
|
||||
vdp.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
auto System::load(Emulator::Interface* interface, Model model) -> bool {
|
||||
information = {};
|
||||
information.model = model;
|
||||
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
if(!cartridge.load()) return false;
|
||||
|
||||
this->interface = interface;
|
||||
information.colorburst = Emulator::Constants::Colorburst::NTSC;
|
||||
return information.loaded = true;
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
peripherals.unload();
|
||||
cartridge.unload();
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
Emulator::video.setPalette();
|
||||
|
||||
Emulator::audio.reset();
|
||||
Emulator::audio.setInterface(interface);
|
||||
|
||||
scheduler.reset();
|
||||
cartridge.power();
|
||||
cpu.power();
|
||||
vdp.power();
|
||||
psg.power();
|
||||
scheduler.primary(cpu);
|
||||
|
||||
peripherals.reset();
|
||||
}
|
||||
|
||||
}
|
35
higan/ms/system/system.hpp
Normal file
35
higan/ms/system/system.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
struct System {
|
||||
auto loaded() const -> bool { return information.loaded; }
|
||||
auto model() const -> Model { return information.model; }
|
||||
auto colorburst() const -> double { return information.colorburst; }
|
||||
|
||||
auto run() -> void;
|
||||
|
||||
auto load(Emulator::Interface* interface, Model model) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
private:
|
||||
Emulator::Interface* interface = nullptr;
|
||||
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
Model model = Model::MasterSystem;
|
||||
string manifest;
|
||||
double colorburst = 0.0;
|
||||
} information;
|
||||
};
|
||||
|
||||
struct Peripherals {
|
||||
auto unload() -> void;
|
||||
auto reset() -> void;
|
||||
auto connect(uint port, uint device) -> void;
|
||||
|
||||
Controller* controllerPort1 = nullptr;
|
||||
Controller* controllerPort2 = nullptr;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
extern Peripherals peripherals;
|
56
higan/ms/vdp/background.cpp
Normal file
56
higan/ms/vdp/background.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
auto VDP::Background::scanline() -> void {
|
||||
state.x = 0;
|
||||
state.y = vdp.io.vcounter;
|
||||
}
|
||||
|
||||
auto VDP::Background::run() -> void {
|
||||
uint8 hoffset = state.x++;
|
||||
uint9 voffset = state.y;
|
||||
|
||||
if(voffset >= vdp.vlines()
|
||||
|| hoffset < (vdp.io.hscroll & 7)
|
||||
) {
|
||||
output.color = 0;
|
||||
output.palette = 0;
|
||||
output.priority = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
bool hscroll = !vdp.io.horizontalScrollLock || voffset >= 16;
|
||||
bool vscroll = !vdp.io.verticalScrollLock || hoffset <= 191;
|
||||
|
||||
if(hscroll) hoffset -= vdp.io.hscroll;
|
||||
if(vscroll) voffset += vdp.io.vscroll;
|
||||
|
||||
uint14 nameTableAddress;
|
||||
if(vdp.vlines() == 192) {
|
||||
if(voffset >= 224) voffset -= 224;
|
||||
nameTableAddress = vdp.io.nameTableAddress << 11;
|
||||
} else {
|
||||
voffset &= 255;
|
||||
nameTableAddress = (vdp.io.nameTableAddress & ~1) << 11 | 0x700;
|
||||
}
|
||||
nameTableAddress += ((voffset >> 3) << 6) + ((hoffset >> 3) << 1);
|
||||
|
||||
uint16 tiledata;
|
||||
tiledata = vdp.vram[nameTableAddress + 0] << 0;
|
||||
tiledata |= vdp.vram[nameTableAddress + 1] << 8;
|
||||
|
||||
uint14 patternAddress = tiledata.bits(0,8) << 5;
|
||||
if(tiledata.bit(9)) hoffset ^= 7;
|
||||
if(tiledata.bit(10)) voffset ^= 7;
|
||||
output.palette = tiledata.bit(11);
|
||||
output.priority = tiledata.bit(12);
|
||||
|
||||
auto index = 7 - (hoffset & 7);
|
||||
patternAddress += (voffset & 7) << 2;
|
||||
output.color.bit(0) = vdp.vram[patternAddress + 0].bit(index);
|
||||
output.color.bit(1) = vdp.vram[patternAddress + 1].bit(index);
|
||||
output.color.bit(2) = vdp.vram[patternAddress + 2].bit(index);
|
||||
output.color.bit(3) = vdp.vram[patternAddress + 3].bit(index);
|
||||
}
|
||||
|
||||
auto VDP::Background::power() -> void {
|
||||
memory::fill(&state, sizeof(State));
|
||||
memory::fill(&output, sizeof(Output));
|
||||
}
|
167
higan/ms/vdp/io.cpp
Normal file
167
higan/ms/vdp/io.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
auto VDP::vcounter() -> uint8 {
|
||||
if(io.lines240) {
|
||||
//NTSC 256x240
|
||||
return io.vcounter;
|
||||
} else if(io.lines224) {
|
||||
//NTSC 256x224
|
||||
return io.vcounter <= 234 ? io.vcounter : io.vcounter - 6;
|
||||
} else {
|
||||
//NTSC 256x192
|
||||
return io.vcounter <= 218 ? io.vcounter : io.vcounter - 6;
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
auto VDP::hcounter() -> uint8 {
|
||||
uint hcounter = io.hcounter >> 1;
|
||||
return hcounter <= 233 ? hcounter : hcounter - 86;
|
||||
}
|
||||
|
||||
auto VDP::data() -> uint8 {
|
||||
io.controlLatch = 0;
|
||||
|
||||
auto data = io.vramLatch;
|
||||
io.vramLatch = vram[io.address++];
|
||||
return data;
|
||||
}
|
||||
|
||||
auto VDP::status() -> uint8 {
|
||||
io.controlLatch = 0;
|
||||
|
||||
uint8 result = 0x00;
|
||||
result |= io.intFrame << 7;
|
||||
result |= io.spriteOverflow << 6;
|
||||
result |= io.spriteCollision << 5;
|
||||
result |= io.fifthSprite << 0;
|
||||
|
||||
io.intLine = 0;
|
||||
io.intFrame = 0;
|
||||
io.spriteOverflow = 0;
|
||||
io.spriteCollision = 0;
|
||||
io.fifthSprite = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto VDP::data(uint8 data) -> void {
|
||||
io.controlLatch = 0;
|
||||
|
||||
if(io.code <= 2) {
|
||||
vram[io.address++] = data;
|
||||
} else {
|
||||
uint mask = 0;
|
||||
if(system.model() == Model::MasterSystem) mask = 0x1f;
|
||||
if(system.model() == Model::GameGear) mask = 0x3f;
|
||||
cram[io.address++ & mask] = data;
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::control(uint8 data) -> void {
|
||||
if(io.controlLatch == 0) {
|
||||
io.controlLatch = 1;
|
||||
io.address.bits(0,7) = data.bits(0,7);
|
||||
return;
|
||||
} else {
|
||||
io.controlLatch = 0;
|
||||
io.address.bits(8,13) = data.bits(0,5);
|
||||
io.code.bits(0,1) = data.bits(6,7);
|
||||
}
|
||||
|
||||
if(io.code == 0) {
|
||||
io.vramLatch = vram[io.address++];
|
||||
}
|
||||
|
||||
if(io.code == 2) {
|
||||
registerWrite(io.address.bits(11,8), io.address.bits(7,0));
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::registerWrite(uint4 addr, uint8 data) -> void {
|
||||
switch(addr) {
|
||||
|
||||
//mode control 1
|
||||
case 0x0: {
|
||||
io.externalSync = data.bit(0);
|
||||
io.extendedHeight = data.bit(1);
|
||||
io.mode4 = data.bit(2);
|
||||
io.spriteShift = data.bit(3);
|
||||
io.lineInterrupts = data.bit(4);
|
||||
io.leftClip = data.bit(5);
|
||||
io.horizontalScrollLock = data.bit(6);
|
||||
io.verticalScrollLock = data.bit(7);
|
||||
return;
|
||||
}
|
||||
|
||||
//mode control 2
|
||||
case 0x1: {
|
||||
io.spriteDouble = data.bit(0);
|
||||
io.spriteTile = data.bit(1);
|
||||
io.lines240 = data.bit(3);
|
||||
io.lines224 = data.bit(4);
|
||||
io.frameInterrupts = data.bit(5);
|
||||
io.displayEnable = data.bit(6);
|
||||
return;
|
||||
}
|
||||
|
||||
//name table base address
|
||||
case 0x2: {
|
||||
io.nameTableMask = data.bit(0);
|
||||
io.nameTableAddress = data.bits(1,3);
|
||||
return;
|
||||
}
|
||||
|
||||
//color table base address
|
||||
case 0x3: {
|
||||
io.colorTableAddress = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//pattern table base address
|
||||
case 0x4: {
|
||||
io.patternTableAddress = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//sprite attribute table base address
|
||||
case 0x5: {
|
||||
io.spriteAttributeTableMask = data.bit(0);
|
||||
io.spriteAttributeTableAddress = data.bits(1,6);
|
||||
return;
|
||||
}
|
||||
|
||||
//sprite pattern table base address
|
||||
case 0x6: {
|
||||
io.spritePatternTableMask = data.bits(0,1);
|
||||
io.spritePatternTableAddress = data.bit(2);
|
||||
return;
|
||||
}
|
||||
|
||||
//backdrop color
|
||||
case 0x7: {
|
||||
io.backdropColor = data.bits(0,3);
|
||||
return;
|
||||
}
|
||||
|
||||
//horizontal scroll offset
|
||||
case 0x8: {
|
||||
io.hscroll = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//vertical scroll offset
|
||||
case 0x9: {
|
||||
io.vscroll = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//line counter
|
||||
case 0xa: {
|
||||
io.lineCounter = data.bits(0,7);
|
||||
return;
|
||||
}
|
||||
|
||||
//0xb - 0xf unmapped
|
||||
|
||||
}
|
||||
}
|
68
higan/ms/vdp/sprite.cpp
Normal file
68
higan/ms/vdp/sprite.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
auto VDP::Sprite::scanline() -> void {
|
||||
state.x = 0;
|
||||
state.y = vdp.io.vcounter;
|
||||
objects.reset();
|
||||
|
||||
uint limit = vdp.io.spriteTile ? 15 : 7;
|
||||
uint14 attributeAddress = vdp.io.spriteAttributeTableAddress << 8;
|
||||
for(uint index : range(64)) {
|
||||
uint8 y = vdp.vram[attributeAddress + index];
|
||||
uint8 x = vdp.vram[attributeAddress + 0x80 + (index << 1)];
|
||||
uint8 pattern = vdp.vram[attributeAddress + 0x81 + (index << 1)];
|
||||
if(vdp.vlines() == 192 && y == 0xd0) break;
|
||||
|
||||
if(vdp.io.spriteShift) x -= 8;
|
||||
y += 1;
|
||||
if(state.y < y) continue;
|
||||
if(state.y > y + limit) continue;
|
||||
|
||||
if(limit == 15) pattern.bit(0) = 0;
|
||||
|
||||
objects.append({x, y, pattern});
|
||||
if(objects.size() == 8) {
|
||||
vdp.io.spriteOverflow = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::Sprite::run() -> void {
|
||||
output.color = 0;
|
||||
|
||||
if(state.y >= vdp.vlines()) return;
|
||||
|
||||
uint limit = vdp.io.spriteTile ? 15 : 7;
|
||||
for(auto& o : objects) {
|
||||
if(state.x < o.x) continue;
|
||||
if(state.x > o.x + 7) continue;
|
||||
|
||||
uint x = state.x - o.x;
|
||||
uint y = state.y - o.y;
|
||||
|
||||
uint14 address = vdp.io.spritePatternTableAddress << 13;
|
||||
address += o.pattern << 5;
|
||||
address += (y & limit) << 2;
|
||||
|
||||
auto index = 7 - (x & 7);
|
||||
uint4 color;
|
||||
color.bit(0) = vdp.vram[address + 0].bit(index);
|
||||
color.bit(1) = vdp.vram[address + 1].bit(index);
|
||||
color.bit(2) = vdp.vram[address + 2].bit(index);
|
||||
color.bit(3) = vdp.vram[address + 3].bit(index);
|
||||
if(color == 0) continue;
|
||||
|
||||
if(output.color) {
|
||||
vdp.io.spriteCollision = true;
|
||||
break;
|
||||
}
|
||||
|
||||
output.color = color;
|
||||
}
|
||||
|
||||
state.x++;
|
||||
}
|
||||
|
||||
auto VDP::Sprite::power() -> void {
|
||||
memory::fill(&state, sizeof(State));
|
||||
memory::fill(&output, sizeof(Output));
|
||||
}
|
121
higan/ms/vdp/vdp.cpp
Normal file
121
higan/ms/vdp/vdp.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include <ms/ms.hpp>
|
||||
|
||||
namespace MasterSystem {
|
||||
|
||||
VDP vdp;
|
||||
#include "io.cpp"
|
||||
#include "background.cpp"
|
||||
#include "sprite.cpp"
|
||||
|
||||
auto VDP::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), vdp.main();
|
||||
}
|
||||
|
||||
auto VDP::main() -> void {
|
||||
if(io.vcounter <= vlines()) {
|
||||
if(io.lcounter-- == 0) {
|
||||
io.lcounter = io.lineCounter;
|
||||
io.intLine = 1;
|
||||
}
|
||||
} else {
|
||||
io.lcounter = io.lineCounter;
|
||||
}
|
||||
|
||||
if(io.vcounter == vlines() + 1) {
|
||||
io.intFrame = 1;
|
||||
}
|
||||
|
||||
background.scanline();
|
||||
sprite.scanline();
|
||||
|
||||
//684 clocks/scanline
|
||||
uint y = io.vcounter;
|
||||
if(y < vlines()) {
|
||||
uint32* screen = buffer + (24 + y) * 256;
|
||||
for(uint x : range(256)) {
|
||||
background.run();
|
||||
sprite.run();
|
||||
step(2);
|
||||
|
||||
uint12 color = palette(io.backdropColor);
|
||||
if(background.output.color && (background.output.priority || !sprite.output.color)) {
|
||||
color = palette(background.output.palette << 4 | background.output.color);
|
||||
} else if(sprite.output.color) {
|
||||
color = palette(16 | sprite.output.color);
|
||||
}
|
||||
if(x <= 7 && io.leftClip) color = palette(io.backdropColor);
|
||||
if(!io.displayEnable) color = 0;
|
||||
*screen++ = color;
|
||||
}
|
||||
} else {
|
||||
//Vblank
|
||||
step(512);
|
||||
}
|
||||
step(172);
|
||||
|
||||
if(io.vcounter == 240) scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
auto VDP::step(uint clocks) -> void {
|
||||
while(clocks--) {
|
||||
if(++io.hcounter == 684) {
|
||||
io.hcounter = 0;
|
||||
if(++io.vcounter == 262) {
|
||||
io.vcounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cpu.setINT((io.lineInterrupts && io.intLine) || (io.frameInterrupts && io.intFrame));
|
||||
Thread::step(1);
|
||||
synchronize(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::refresh() -> void {
|
||||
if(system.model() == Model::MasterSystem) {
|
||||
//center the video output vertically in the viewport
|
||||
uint32* screen = buffer;
|
||||
if(vlines() == 224) screen += 16 * 256;
|
||||
if(vlines() == 240) screen += 24 * 256;
|
||||
|
||||
Emulator::video.refresh(screen, 256 * sizeof(uint32), 256, 240);
|
||||
}
|
||||
|
||||
if(system.model() == Model::GameGear) {
|
||||
Emulator::video.refresh(buffer + 48 * 256 + 48, 256 * sizeof(uint32), 160, 144);
|
||||
}
|
||||
}
|
||||
|
||||
auto VDP::vlines() -> uint {
|
||||
if(io.lines240) return 240;
|
||||
if(io.lines224) return 224;
|
||||
return 192;
|
||||
}
|
||||
|
||||
auto VDP::vblank() -> bool {
|
||||
return io.vcounter >= vlines();
|
||||
}
|
||||
|
||||
auto VDP::power() -> void {
|
||||
create(VDP::Enter, system.colorburst() * 15.0 / 5.0);
|
||||
|
||||
memory::fill(&buffer, sizeof(buffer));
|
||||
memory::fill(&io, sizeof(IO));
|
||||
|
||||
background.power();
|
||||
sprite.power();
|
||||
}
|
||||
|
||||
auto VDP::palette(uint5 index) -> uint12 {
|
||||
if(system.model() == Model::MasterSystem) {
|
||||
return cram[index];
|
||||
}
|
||||
|
||||
if(system.model() == Model::GameGear) {
|
||||
return cram[index * 2 + 0] << 0 | cram[index * 2 + 1] << 8;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
147
higan/ms/vdp/vdp.hpp
Normal file
147
higan/ms/vdp/vdp.hpp
Normal file
@@ -0,0 +1,147 @@
|
||||
//TI TMS9918A (derivative)
|
||||
|
||||
struct VDP : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto vlines() -> uint;
|
||||
auto vblank() -> bool;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
//io.cpp
|
||||
auto vcounter() -> uint8;
|
||||
auto hcounter() -> uint8;
|
||||
auto data() -> uint8;
|
||||
auto status() -> uint8;
|
||||
|
||||
auto data(uint8) -> void;
|
||||
auto control(uint8) -> void;
|
||||
auto registerWrite(uint4 addr, uint8 data) -> void;
|
||||
|
||||
//background.cpp
|
||||
struct Background {
|
||||
auto scanline() -> void;
|
||||
auto run() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
struct State {
|
||||
uint x;
|
||||
uint y;
|
||||
} state;
|
||||
|
||||
struct Output {
|
||||
uint4 color;
|
||||
uint1 palette;
|
||||
uint1 priority;
|
||||
} output;
|
||||
} background;
|
||||
|
||||
//sprite.cpp
|
||||
struct Sprite {
|
||||
auto scanline() -> void;
|
||||
auto run() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
struct Object {
|
||||
uint8 x;
|
||||
uint8 y;
|
||||
uint8 pattern;
|
||||
};
|
||||
|
||||
struct State {
|
||||
uint x;
|
||||
uint y;
|
||||
} state;
|
||||
|
||||
struct Output {
|
||||
uint4 color;
|
||||
} output;
|
||||
|
||||
array<Object, 8> objects;
|
||||
} sprite;
|
||||
|
||||
private:
|
||||
auto palette(uint5 index) -> uint12;
|
||||
|
||||
uint32 buffer[256 * 264];
|
||||
uint8 vram[0x4000];
|
||||
uint8 cram[0x40]; //MS = 0x20, GG = 0x40
|
||||
|
||||
struct IO {
|
||||
uint vcounter; //vertical counter
|
||||
uint hcounter; //horizontal counter
|
||||
uint lcounter; //line counter
|
||||
|
||||
//interrupt flags
|
||||
bool intLine;
|
||||
bool intFrame;
|
||||
|
||||
//status flags
|
||||
bool spriteOverflow;
|
||||
bool spriteCollision;
|
||||
uint5 fifthSprite;
|
||||
|
||||
//latches
|
||||
bool controlLatch;
|
||||
uint16 controlData;
|
||||
uint2 code;
|
||||
uint14 address;
|
||||
|
||||
uint8 vramLatch;
|
||||
|
||||
//$00 mode control 1
|
||||
bool externalSync;
|
||||
bool extendedHeight;
|
||||
bool mode4;
|
||||
bool spriteShift;
|
||||
bool lineInterrupts;
|
||||
bool leftClip;
|
||||
bool horizontalScrollLock;
|
||||
bool verticalScrollLock;
|
||||
|
||||
//$01 mode control 2
|
||||
bool spriteDouble;
|
||||
bool spriteTile;
|
||||
bool lines240;
|
||||
bool lines224;
|
||||
bool frameInterrupts;
|
||||
bool displayEnable;
|
||||
|
||||
//$02 name table base address
|
||||
uint1 nameTableMask;
|
||||
uint3 nameTableAddress;
|
||||
|
||||
//$03 color table base address
|
||||
uint8 colorTableAddress;
|
||||
|
||||
//$04 pattern table base address
|
||||
uint8 patternTableAddress;
|
||||
|
||||
//$05 sprite attribute table base address
|
||||
uint1 spriteAttributeTableMask;
|
||||
uint6 spriteAttributeTableAddress;
|
||||
|
||||
//$06 sprite pattern table base address
|
||||
uint2 spritePatternTableMask;
|
||||
uint1 spritePatternTableAddress;
|
||||
|
||||
//$07 backdrop color
|
||||
uint4 backdropColor;
|
||||
|
||||
//$08 horizontal scroll offset
|
||||
uint8 hscroll;
|
||||
|
||||
//$09 vertical scroll offset
|
||||
uint8 vscroll;
|
||||
|
||||
//$0a line counter
|
||||
uint8 lineCounter;
|
||||
} io;
|
||||
};
|
||||
|
||||
extern VDP vdp;
|
14
higan/pce/GNUmakefile
Normal file
14
higan/pce/GNUmakefile
Normal file
@@ -0,0 +1,14 @@
|
||||
processors += huc6280
|
||||
|
||||
objects += pce-interface
|
||||
objects += pce-cpu pce-vdc pce-psg
|
||||
objects += pce-system pce-cartridge
|
||||
objects += pce-controller
|
||||
|
||||
obj/pce-interface.o: pce/interface/interface.cpp $(call rwildcard,pce/interface)
|
||||
obj/pce-cpu.o: pce/cpu/cpu.cpp $(call rwildcard,pce/cpu)
|
||||
obj/pce-vdc.o: pce/vdc/vdc.cpp $(call rwildcard,pce/vdc)
|
||||
obj/pce-psg.o: pce/psg/psg.cpp $(call rwildcard,pce/psg)
|
||||
obj/pce-system.o: pce/system/system.cpp $(call rwildcard,pce/system)
|
||||
obj/pce-cartridge.o: pce/cartridge/cartridge.cpp $(call rwildcard,pce/cartridge)
|
||||
obj/pce-controller.o: pce/controller/controller.cpp $(call rwildcard,pce/controller)
|
77
higan/pce/cartridge/cartridge.cpp
Normal file
77
higan/pce/cartridge/cartridge.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
information = {};
|
||||
|
||||
if(auto pathID = platform->load(ID::PCEngine, "PC Engine", "pce")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
information.title = document["information/title"].text();
|
||||
|
||||
if(auto node = document["board/rom"]) {
|
||||
rom.size = node["size"].natural();
|
||||
if(rom.size) {
|
||||
rom.data = new uint8[rom.size]();
|
||||
if(auto name = node["name"].text()) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
|
||||
fp->read(rom.data, rom.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
delete[] rom.data;
|
||||
rom = {};
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::read(uint20 addr) -> uint8 {
|
||||
if(!rom.size) return 0x00;
|
||||
return rom.data[mirror(addr, rom.size)];
|
||||
}
|
||||
|
||||
auto Cartridge::write(uint20 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::mirror(uint addr, uint size) -> uint {
|
||||
//384KB games have unusual mirroring (only second ROM repeats)
|
||||
if(size == 0x60000) {
|
||||
if(addr <= 0x3ffff) return addr;
|
||||
return 0x40000 + (addr & 0x1ffff);
|
||||
}
|
||||
|
||||
uint base = 0;
|
||||
uint mask = 1 << 20;
|
||||
while(addr >= size) {
|
||||
while(!(addr & mask)) mask >>= 1;
|
||||
addr -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
return base + addr;
|
||||
}
|
||||
|
||||
}
|
34
higan/pce/cartridge/cartridge.hpp
Normal file
34
higan/pce/cartridge/cartridge.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
struct Cartridge {
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto sha256() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
auto read(uint20 addr) -> uint8;
|
||||
auto write(uint20 addr, uint8 data) -> void;
|
||||
|
||||
private:
|
||||
auto mirror(uint addr, uint size) -> uint;
|
||||
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
string sha256;
|
||||
string manifest;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
struct Memory {
|
||||
uint8* data = nullptr;
|
||||
uint size = 0;
|
||||
};
|
||||
|
||||
Memory rom;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
26
higan/pce/controller/controller.cpp
Normal file
26
higan/pce/controller/controller.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
#include "gamepad/gamepad.cpp"
|
||||
|
||||
Controller::Controller() {
|
||||
if(!handle()) create(Controller::Enter, 100);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
}
|
||||
|
||||
auto Controller::Enter() -> void {
|
||||
while(true) {
|
||||
scheduler.synchronize();
|
||||
if(peripherals.controllerPort->active()) peripherals.controllerPort->main();
|
||||
}
|
||||
}
|
||||
|
||||
auto Controller::main() -> void {
|
||||
step(1);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
}
|
12
higan/pce/controller/controller.hpp
Normal file
12
higan/pce/controller/controller.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
struct Controller : Thread {
|
||||
Controller();
|
||||
virtual ~Controller();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
virtual auto readData() -> uint4 { return 0; }
|
||||
virtual auto writeData(uint2) -> void {}
|
||||
};
|
||||
|
||||
#include "gamepad/gamepad.hpp"
|
35
higan/pce/controller/gamepad/gamepad.cpp
Normal file
35
higan/pce/controller/gamepad/gamepad.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
Gamepad::Gamepad() {
|
||||
}
|
||||
|
||||
auto Gamepad::readData() -> uint4 {
|
||||
if(clr) return 0;
|
||||
|
||||
uint4 data;
|
||||
|
||||
if(sel) {
|
||||
bool up = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, Up);
|
||||
bool right = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, Right);
|
||||
bool down = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, Down);
|
||||
bool left = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, Left);
|
||||
data.bit(0) = !(up & !down);
|
||||
data.bit(1) = !(right & !left);
|
||||
data.bit(2) = !(down & !up);
|
||||
data.bit(3) = !(left & !right);
|
||||
} else {
|
||||
bool one = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, One);
|
||||
bool two = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, Two);
|
||||
bool select = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, Select);
|
||||
bool run = platform->inputPoll(ID::Port::Controller, ID::Device::Gamepad, Run);
|
||||
data.bit(0) = !one;
|
||||
data.bit(1) = !two;
|
||||
data.bit(2) = !select;
|
||||
data.bit(3) = !run;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
auto Gamepad::writeData(uint2 data) -> void {
|
||||
sel = data.bit(0);
|
||||
clr = data.bit(1);
|
||||
}
|
13
higan/pce/controller/gamepad/gamepad.hpp
Normal file
13
higan/pce/controller/gamepad/gamepad.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
struct Gamepad : Controller {
|
||||
enum : uint {
|
||||
Up, Down, Left, Right, Two, One, Select, Run,
|
||||
};
|
||||
|
||||
Gamepad();
|
||||
|
||||
auto readData() -> uint4 override;
|
||||
auto writeData(uint2 data) -> void override;
|
||||
|
||||
bool sel;
|
||||
bool clr;
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user