mirror of https://github.com/bsnes-emu/bsnes.git
Update to v101r30 release.
byuu says: Changelog: - SMS: added cartridge ROM/RAM mirroring (fixes Alex Kidd) - SMS: fixed 8x16 sprite mode (fixes Wonder Boy, Ys graphics) - Z80: emulated "ex (sp),hl" instruction - Z80: fixed INx NF (should be set instead of cleared) - Z80: fixed loop condition check for CPxR, INxR, LDxR, OTxR (fixes walking in Wonder Boy) - SFC: removed Debugger and sfc/debugger.hpp - icarus: connected MS, GG, MD importing to the scan dialog - PCE: added emulation skeleton to higan and icarus At this point, Master System games are fairly highly compatible, sans audio. Game Gear games are running, but I need to crop the resolution and support the higher color palette that they can utilize. It's really something else the way they handled the resolution shrink on that thing. The last change is obviously going to be the biggest news. I'm very well aware it's not an ideal time to start on a new emulation core, with the MS and MD cores only just now coming to life with no audio support. But, for whatever reason, my heart's really set on working on the PC Engine. I wanted to write the final higan skeleton core, and get things ready so that whenever I'm in the mood to work on the PCE, I can do so. The skeleton is far and away the most tedious and obnoxious part of the emulator development, because it's basically all just lots of boilerplate templated code, lots of new files to create, etc. I really don't know how things are going to proceed ... but I can say with 99.9% certainty that this will be the final brand new core ever added to higan -- at least one written by me, that is. This was basically the last system from my childhood that I ever cared about. It's the last 2D system with games that I really enjoy playing. No other system is worth dividing my efforts and reducing the quality and amount of time to work on the systems I have. In the future, there will be potential for FDS, Mega CD and PCE-CD support. But those will all be add-ons, and they'll all be really difficult and challenge the entire design of higan's UI (it's entirely cartridge-driven at this time.) None of them will be entirely new cores like this one.
This commit is contained in:
parent
79c83ade70
commit
0ad70a30f8
|
@ -12,7 +12,7 @@ using namespace nall;
|
|||
|
||||
namespace Emulator {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "101.29";
|
||||
static const string Version = "101.30";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
|
|
@ -84,13 +84,29 @@ auto Cartridge::reset() -> void {
|
|||
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(addr < this->size) return this->data[addr];
|
||||
return 0x00;
|
||||
if(!size) return 0x00;
|
||||
return this->data[mirror(addr, size)];
|
||||
}
|
||||
|
||||
auto Cartridge::Memory::write(uint addr, uint8 data) -> void {
|
||||
if(addr < this->size) this->data[addr] = data;
|
||||
if(!size) return;
|
||||
this->data[mirror(addr, size)] = data;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ private:
|
|||
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;
|
||||
};
|
||||
|
|
|
@ -9,13 +9,6 @@ auto CPU::Enter() -> void {
|
|||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
#if 0
|
||||
static uint64 instructionsExecuted = 0;
|
||||
if(instructionsExecuted < 20)
|
||||
print(disassemble(r.pc), "\n");
|
||||
instructionsExecuted++;
|
||||
#endif
|
||||
|
||||
//note: SMS1 extbus value is random; SMS2+ is pulled high ($ff)
|
||||
|
||||
if(state.nmiLine) {
|
||||
|
|
|
@ -3,7 +3,7 @@ auto VDP::Sprite::scanline() -> void {
|
|||
state.y = vdp.io.vcounter;
|
||||
objects.reset();
|
||||
|
||||
bool large = vdp.io.extendedHeight;
|
||||
uint limit = vdp.io.spriteTile ? 15 : 7;
|
||||
uint14 attributeAddress = vdp.io.spriteAttributeTableAddress << 8;
|
||||
for(uint index : range(64)) {
|
||||
uint8 y = vdp.vram[attributeAddress + index];
|
||||
|
@ -14,9 +14,9 @@ auto VDP::Sprite::scanline() -> void {
|
|||
if(vdp.io.spriteShift) x -= 8;
|
||||
y += 1;
|
||||
if(state.y < y) continue;
|
||||
if(state.y > y + (large ? 15 : 7)) continue;
|
||||
if(state.y > y + limit) continue;
|
||||
|
||||
if(large) pattern.bit(0) = 0;
|
||||
if(limit == 15) pattern.bit(0) = 0;
|
||||
|
||||
objects.append({x, y, pattern});
|
||||
if(objects.size() == 8) {
|
||||
|
@ -31,8 +31,7 @@ auto VDP::Sprite::run() -> void {
|
|||
|
||||
if(state.y >= vdp.vlines()) return;
|
||||
|
||||
bool large = vdp.io.extendedHeight;
|
||||
uint mask = vdp.io.extendedHeight ? 15 : 7;
|
||||
uint limit = vdp.io.spriteTile ? 15 : 7;
|
||||
for(auto& o : objects) {
|
||||
if(state.x < o.x) continue;
|
||||
if(state.x > o.x + 7) continue;
|
||||
|
@ -42,7 +41,7 @@ auto VDP::Sprite::run() -> void {
|
|||
|
||||
uint14 address = vdp.io.spritePatternTableAddress << 13;
|
||||
address += o.pattern << 5;
|
||||
address += (y & mask) << 2;
|
||||
address += (y & limit) << 2;
|
||||
|
||||
auto index = 7 - (x & 7);
|
||||
uint4 color;
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,93 @@
|
|||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
information = {};
|
||||
|
||||
if(auto pathID = interface->load(ID::PCEngine, "PC Engine", "pce")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
|
||||
if(auto fp = interface->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 = interface->open(pathID(), name, File::Read, File::Required)) {
|
||||
fp->read(rom.data, rom.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(auto node = document["board/ram"]) {
|
||||
ram.size = node["size"].natural();
|
||||
if(ram.size) {
|
||||
ram.data = new uint8[ram.size]();
|
||||
if(auto name = node["name"].text()) {
|
||||
if(auto fp = interface->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 = interface->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 {
|
||||
}
|
||||
|
||||
auto Cartridge::Memory::mirror(uint addr, uint size) -> uint {
|
||||
uint base = 0;
|
||||
uint mask = 1 << 23;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
string sha256;
|
||||
string manifest;
|
||||
string title;
|
||||
} information;
|
||||
|
||||
struct Memory {
|
||||
uint8* data = nullptr;
|
||||
uint size = 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;
|
||||
};
|
||||
|
||||
extern Cartridge cartridge;
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
struct Controller : Thread {
|
||||
Controller();
|
||||
virtual ~Controller();
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
||||
virtual auto readData() -> uint8 { return 0x00; }
|
||||
};
|
||||
|
||||
#include "gamepad/gamepad.hpp"
|
|
@ -0,0 +1,6 @@
|
|||
Gamepad::Gamepad() {
|
||||
}
|
||||
|
||||
auto Gamepad::readData() -> uint8 {
|
||||
return 0x00;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
struct Gamepad : Controller {
|
||||
enum : uint {
|
||||
Up, Down, Left, Right, Two, One, Select, Run,
|
||||
};
|
||||
|
||||
Gamepad();
|
||||
|
||||
auto readData() -> uint8 override;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
instruction();
|
||||
}
|
||||
|
||||
auto CPU::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(vdc);
|
||||
synchronize(psg);
|
||||
for(auto peripheral : peripherals) synchronize(*peripheral);
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
HuC6280::power();
|
||||
create(CPU::Enter, system.colorburst() * 6.0);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//Hudson Soft HuC6280
|
||||
|
||||
struct CPU : Processor::HuC6280, Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
vector<Thread*> peripherals;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
|
@ -0,0 +1,127 @@
|
|||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
||||
information.manufacturer = "NEC";
|
||||
information.name = "PC Engine";
|
||||
information.overscan = true;
|
||||
information.resettable = false;
|
||||
|
||||
information.capability.states = false;
|
||||
information.capability.cheats = false;
|
||||
|
||||
media.append({ID::PCEngine, "PC Engine", "pce"});
|
||||
|
||||
Port controllerPort{ID::Port::Controller, "Controller Port"};
|
||||
|
||||
{ Device device{ID::Device::None, "None"};
|
||||
controllerPort.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, "II"});
|
||||
device.inputs.append({0, "I"});
|
||||
device.inputs.append({0, "Select"});
|
||||
device.inputs.append({0, "Run"});
|
||||
controllerPort.devices.append(device);
|
||||
}
|
||||
|
||||
ports.append(move(controllerPort));
|
||||
}
|
||||
|
||||
auto Interface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
}
|
||||
|
||||
auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::videoSize() -> VideoSize {
|
||||
return {512, 484};
|
||||
}
|
||||
|
||||
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
|
||||
auto a = arc ? 8.0 / 7.0 : 1.0;
|
||||
uint w = 256;
|
||||
uint h = 242;
|
||||
uint m = min(width / (w * a), height / h);
|
||||
return {uint(w * a * m), uint(h * m)};
|
||||
}
|
||||
|
||||
auto Interface::videoFrequency() -> double {
|
||||
return 60.0;
|
||||
}
|
||||
|
||||
auto Interface::videoColors() -> uint32 {
|
||||
return 1 << 9;
|
||||
}
|
||||
|
||||
auto Interface::videoColor(uint32 color) -> uint64 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto Interface::audioFrequency() -> double {
|
||||
return 44'100.0; //todo: not accurate
|
||||
}
|
||||
|
||||
auto Interface::loaded() -> bool {
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto Interface::load(uint id) -> bool {
|
||||
if(id == ID::PCEngine) return system.load();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::save() -> void {
|
||||
system.save();
|
||||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto Interface::connect(uint port, uint device) -> void {
|
||||
PCEngine::peripherals.connect(port, device);
|
||||
}
|
||||
|
||||
auto Interface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
|
||||
auto Interface::run() -> void {
|
||||
system.run();
|
||||
}
|
||||
|
||||
auto Interface::serialize() -> serializer {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Interface::unserialize(serializer& s) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::cap(const string& name) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Interface::get(const string& name) -> any {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Interface::set(const string& name, const any& value) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
namespace PCEngine {
|
||||
|
||||
struct ID {
|
||||
enum : uint {
|
||||
System,
|
||||
PCEngine,
|
||||
};
|
||||
|
||||
struct Port { enum : uint {
|
||||
Controller,
|
||||
};};
|
||||
|
||||
struct Device { enum : uint {
|
||||
None,
|
||||
Gamepad,
|
||||
};};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
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;
|
||||
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 controllerPort = 0;
|
||||
};
|
||||
|
||||
extern Interface* interface;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
//license: GPLv3
|
||||
//started: 2017-01-11
|
||||
|
||||
#include <emulator/emulator.hpp>
|
||||
#include <emulator/thread.hpp>
|
||||
#include <emulator/scheduler.hpp>
|
||||
|
||||
#include <processor/huc6280/huc6280.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
using File = Emulator::File;
|
||||
using Scheduler = Emulator::Scheduler;
|
||||
extern Scheduler scheduler;
|
||||
|
||||
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 <pce/controller/controller.hpp>
|
||||
|
||||
#include <pce/cpu/cpu.hpp>
|
||||
#include <pce/vdc/vdc.hpp>
|
||||
#include <pce/psg/psg.hpp>
|
||||
|
||||
#include <pce/system/system.hpp>
|
||||
#include <pce/cartridge/cartridge.hpp>
|
||||
}
|
||||
|
||||
#include <pce/interface/interface.hpp>
|
|
@ -0,0 +1,26 @@
|
|||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
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, 44'100.0);
|
||||
stream = Emulator::audio.createStream(2, 44'100.0);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//Programmable Sound Generator
|
||||
|
||||
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;
|
|
@ -0,0 +1,26 @@
|
|||
Peripherals peripherals;
|
||||
|
||||
auto Peripherals::unload() -> void {
|
||||
delete controllerPort;
|
||||
controllerPort = nullptr;
|
||||
}
|
||||
|
||||
auto Peripherals::reset() -> void {
|
||||
connect(ID::Port::Controller, settings.controllerPort);
|
||||
}
|
||||
|
||||
auto Peripherals::connect(uint port, uint device) -> void {
|
||||
if(port == ID::Port::Controller) {
|
||||
settings.controllerPort = device;
|
||||
if(!system.loaded()) return;
|
||||
|
||||
delete controllerPort;
|
||||
switch(device) { default:
|
||||
case ID::Device::None: controllerPort = new Controller; break;
|
||||
case ID::Device::Gamepad: controllerPort = new Gamepad; break;
|
||||
}
|
||||
}
|
||||
|
||||
cpu.peripherals.reset();
|
||||
cpu.peripherals.append(controllerPort);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
System system;
|
||||
Scheduler scheduler;
|
||||
#include "peripherals.cpp"
|
||||
|
||||
auto System::run() -> void {
|
||||
if(scheduler.enter() == Scheduler::Event::Frame) vdc.refresh();
|
||||
}
|
||||
|
||||
auto System::load() -> bool {
|
||||
information = {};
|
||||
|
||||
if(auto fp = interface->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;
|
||||
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();
|
||||
vdc.power();
|
||||
psg.power();
|
||||
scheduler.primary(cpu);
|
||||
|
||||
peripherals.reset();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
struct System {
|
||||
auto loaded() const -> bool { return information.loaded; }
|
||||
auto colorburst() const -> double { return information.colorburst; }
|
||||
|
||||
auto run() -> void;
|
||||
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
string manifest;
|
||||
double colorburst = 0.0;
|
||||
} information;
|
||||
};
|
||||
|
||||
struct Peripherals {
|
||||
auto unload() -> void;
|
||||
auto reset() -> void;
|
||||
auto connect(uint port, uint device) -> void;
|
||||
|
||||
Controller* controllerPort = nullptr;
|
||||
};
|
||||
|
||||
extern System system;
|
||||
extern Peripherals peripherals;
|
|
@ -0,0 +1,30 @@
|
|||
#include <pce/pce.hpp>
|
||||
|
||||
namespace PCEngine {
|
||||
|
||||
VDC vdc;
|
||||
|
||||
auto VDC::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), vdc.main();
|
||||
}
|
||||
|
||||
auto VDC::main() -> void {
|
||||
step(system.colorburst() * 6.0 / 60.0);
|
||||
scheduler.exit(Scheduler::Event::Frame);
|
||||
}
|
||||
|
||||
auto VDC::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto VDC::refresh() -> void {
|
||||
Emulator::video.refresh(buffer, 512 * sizeof(uint32), 512, 484);
|
||||
}
|
||||
|
||||
auto VDC::power() -> void {
|
||||
create(VDC::Enter, system.colorburst() * 6.0);
|
||||
for(auto& pixel : buffer) pixel = 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
//Video Display Controller
|
||||
|
||||
struct VDC : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto refresh() -> void;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
private:
|
||||
uint32 buffer[512 * 484];
|
||||
};
|
||||
|
||||
extern VDC vdc;
|
|
@ -3,6 +3,7 @@ processors := $(call unique,$(processors))
|
|||
objects += $(if $(findstring arm,$(processors)),processor-arm)
|
||||
objects += $(if $(findstring gsu,$(processors)),processor-gsu)
|
||||
objects += $(if $(findstring hg51b,$(processors)),processor-hg51b)
|
||||
objects += $(if $(findstring huc6280,$(processors)),processor-huc6280)
|
||||
objects += $(if $(findstring lr35902,$(processors)),processor-lr35902)
|
||||
objects += $(if $(findstring m68k,$(processors)),processor-m68k)
|
||||
objects += $(if $(findstring r6502,$(processors)),processor-r6502)
|
||||
|
@ -15,6 +16,7 @@ objects += $(if $(findstring z80,$(processors)),processor-z80)
|
|||
obj/processor-arm.o: processor/arm/arm.cpp $(call rwildcard,processor/arm)
|
||||
obj/processor-gsu.o: processor/gsu/gsu.cpp $(call rwildcard,processor/gsu)
|
||||
obj/processor-hg51b.o: processor/hg51b/hg51b.cpp $(call rwildcard,processor/hg51b)
|
||||
obj/processor-huc6280.o: processor/huc6280/huc6280.cpp $(call rwildcard,processor/huc6280)
|
||||
obj/processor-lr35902.o: processor/lr35902/lr35902.cpp $(call rwildcard,processor/lr35902)
|
||||
obj/processor-m68k.o: processor/m68k/m68k.cpp $(call rwildcard,processor/m68k)
|
||||
obj/processor-r6502.o: processor/r6502/r6502.cpp $(call rwildcard,processor/r6502)
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
#include <processor/processor.hpp>
|
||||
#include "huc6280.hpp"
|
||||
|
||||
namespace Processor {
|
||||
|
||||
auto HuC6280::power() -> void {
|
||||
}
|
||||
|
||||
auto HuC6280::instruction() -> void {
|
||||
step(1);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
//Hudson Soft HuC6280A
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Processor {
|
||||
|
||||
struct HuC6280 {
|
||||
virtual auto step(uint clocks) -> void = 0;
|
||||
|
||||
auto power() -> void;
|
||||
auto instruction() -> void;
|
||||
};
|
||||
|
||||
}
|
|
@ -339,6 +339,7 @@ auto Z80::disassemble(uint16 pc, uint8 prefix, uint8 code) -> string {
|
|||
op(0xe0, "ret ", "po")
|
||||
op(0xe1, "pop ", HL)
|
||||
op(0xe2, "jp ", "po", NN)
|
||||
op(0xe3, "ex ", ISP, HL)
|
||||
op(0xe4, "call", "po", NN)
|
||||
op(0xe5, "push", HL)
|
||||
op(0xe6, "and ", A, N)
|
||||
|
|
|
@ -257,6 +257,7 @@ auto Z80::instruction(uint8 code) -> void {
|
|||
op(0xe0, RET_c, PF == 0)
|
||||
op(0xe1, POP_rr, HL)
|
||||
op(0xe2, JP_c_nn, PF == 0)
|
||||
op(0xe3, EX_irr_rr, SP, HL)
|
||||
op(0xe4, CALL_c_nn, PF == 0)
|
||||
op(0xe5, PUSH_rr, HL)
|
||||
op(0xe6, AND_a_n)
|
||||
|
|
|
@ -376,7 +376,7 @@ auto Z80::instructionCPD() -> void {
|
|||
|
||||
auto Z80::instructionCPDR() -> void {
|
||||
instructionCPD();
|
||||
if(!VF || ZF) return;
|
||||
if(!BC) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ auto Z80::instructionCPI() -> void {
|
|||
|
||||
auto Z80::instructionCPIR() -> void {
|
||||
instructionCPI();
|
||||
if(!VF || ZF) return;
|
||||
if(!BC) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
@ -452,6 +452,15 @@ auto Z80::instructionEI() -> void {
|
|||
r.ei = 1; //raise IFF1, IFF2 after the next instruction
|
||||
}
|
||||
|
||||
auto Z80::instructionEX_irr_rr(uint16& x, uint16& y) -> void {
|
||||
uint16 z;
|
||||
z.byte(0) = read(x + 0);
|
||||
z.byte(1) = read(x + 1);
|
||||
write(x + 0, y.byte(0));
|
||||
write(x + 1, y.byte(1));
|
||||
y = z;
|
||||
}
|
||||
|
||||
auto Z80::instructionEX_rr_rr(uint16& x, uint16& y) -> void {
|
||||
auto z = x;
|
||||
x = y;
|
||||
|
@ -501,13 +510,13 @@ auto Z80::instructionIND() -> void {
|
|||
wait(1);
|
||||
auto data = in(C);
|
||||
write(_HL--, data);
|
||||
NF = 0;
|
||||
NF = 1;
|
||||
ZF = --B == 0;
|
||||
}
|
||||
|
||||
auto Z80::instructionINDR() -> void {
|
||||
instructionIND();
|
||||
if(ZF) return;
|
||||
if(!B) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
@ -516,13 +525,13 @@ auto Z80::instructionINI() -> void {
|
|||
wait(1);
|
||||
auto data = in(C);
|
||||
write(_HL++, data);
|
||||
NF = 0;
|
||||
NF = 1;
|
||||
ZF = --B == 0;
|
||||
}
|
||||
|
||||
auto Z80::instructionINIR() -> void {
|
||||
instructionINI();
|
||||
if(ZF) return;
|
||||
if(!B) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
@ -616,7 +625,7 @@ auto Z80::instructionLDD() -> void {
|
|||
|
||||
auto Z80::instructionLDDR() -> void {
|
||||
instructionLDD();
|
||||
if(!VF) return;
|
||||
if(!BC) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
@ -632,7 +641,7 @@ auto Z80::instructionLDI() -> void {
|
|||
|
||||
auto Z80::instructionLDIR() -> void {
|
||||
instructionLDI();
|
||||
if(!VF) return;
|
||||
if(!BC) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
@ -658,14 +667,14 @@ auto Z80::instructionOR_a_r(uint8& x) -> void {
|
|||
|
||||
auto Z80::instructionOTDR() -> void {
|
||||
instructionOUTD();
|
||||
if(ZF) return;
|
||||
if(!B) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
||||
auto Z80::instructionOTIR() -> void {
|
||||
instructionOUTI();
|
||||
if(ZF) return;
|
||||
if(!B) return;
|
||||
wait(5);
|
||||
PC -= 2;
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ struct Z80 {
|
|||
auto instructionDI() -> void;
|
||||
auto instructionDJNZ_e() -> void;
|
||||
auto instructionEI() -> void;
|
||||
auto instructionEX_irr_rr(uint16&, uint16&) -> void;
|
||||
auto instructionEX_rr_rr(uint16&, uint16&) -> void;
|
||||
auto instructionEXX() -> void;
|
||||
auto instructionHALT() -> void;
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace SuperFamicom {
|
||||
|
||||
struct Debugger {
|
||||
struct CPU {
|
||||
function<void (uint24, uint8)> read;
|
||||
function<void (uint24, uint8)> write;
|
||||
function<void (uint24)> execute;
|
||||
function<void ()> nmi;
|
||||
function<void ()> irq;
|
||||
} cpu;
|
||||
|
||||
struct SMP {
|
||||
function<void (uint16, uint8)> read;
|
||||
function<void (uint16, uint8)> write;
|
||||
function<void (uint16)> execute;
|
||||
} smp;
|
||||
|
||||
struct PPU {
|
||||
struct VRAM {
|
||||
function<void (uint17, uint8)> read;
|
||||
function<void (uint17, uint8)> write;
|
||||
} vram;
|
||||
|
||||
struct OAM {
|
||||
function<void (uint10, uint8)> read;
|
||||
function<void (uint10, uint8)> write;
|
||||
} oam;
|
||||
|
||||
struct CGRAM {
|
||||
function<void (uint9, uint8)> read;
|
||||
function<void (uint9, uint8)> write;
|
||||
} cgram;
|
||||
} ppu;
|
||||
};
|
||||
|
||||
extern Debugger debugger;
|
||||
|
||||
}
|
|
@ -4,7 +4,6 @@ namespace SuperFamicom {
|
|||
|
||||
Interface* interface = nullptr;
|
||||
Settings settings;
|
||||
Debugger debugger;
|
||||
|
||||
Interface::Interface() {
|
||||
interface = this;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#include <sfc/debugger.hpp>
|
||||
|
||||
namespace SuperFamicom {
|
||||
|
||||
struct ID {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
system name:PC Engine
|
|
@ -5,6 +5,7 @@ include fc/GNUmakefile
|
|||
include sfc/GNUmakefile
|
||||
include ms/GNUmakefile
|
||||
include md/GNUmakefile
|
||||
include pce/GNUmakefile
|
||||
include gb/GNUmakefile
|
||||
include gba/GNUmakefile
|
||||
include ws/GNUmakefile
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <sfc/interface/interface.hpp>
|
||||
#include <ms/interface/interface.hpp>
|
||||
#include <md/interface/interface.hpp>
|
||||
#include <pce/interface/interface.hpp>
|
||||
#include <gb/interface/interface.hpp>
|
||||
#include <gba/interface/interface.hpp>
|
||||
#include <ws/interface/interface.hpp>
|
||||
|
@ -20,6 +21,7 @@ Program::Program(string_vector args) {
|
|||
emulators.append(new SuperFamicom::Interface);
|
||||
emulators.append(new MasterSystem::Interface);
|
||||
emulators.append(new MegaDrive::Interface);
|
||||
emulators.append(new PCEngine::Interface);
|
||||
emulators.append(new GameBoy::Interface);
|
||||
emulators.append(new GameBoyAdvance::Interface);
|
||||
emulators.append(new WonderSwan::Interface);
|
||||
|
|
|
@ -3,6 +3,7 @@ Icarus::Icarus() {
|
|||
database.superFamicom = BML::unserialize(string::read(locate("Database/Super Famicom.bml")));
|
||||
database.masterSystem = BML::unserialize(string::read(locate("Database/Master System.bml")));
|
||||
database.megaDrive = BML::unserialize(string::read(locate("Database/Mega Drive.bml")));
|
||||
database.pcEngine = BML::unserialize(string::read(locate("Database/PC Engine.bml")));
|
||||
database.gameBoy = BML::unserialize(string::read(locate("Database/Game Boy.bml")));
|
||||
database.gameBoyColor = BML::unserialize(string::read(locate("Database/Game Boy Color.bml")));
|
||||
database.gameBoyAdvance = BML::unserialize(string::read(locate("Database/Game Boy Advance.bml")));
|
||||
|
@ -36,6 +37,7 @@ auto Icarus::manifest(string location) -> string {
|
|||
if(type == ".sfc") return superFamicomManifest(location);
|
||||
if(type == ".ms") return masterSystemManifest(location);
|
||||
if(type == ".md") return megaDriveManifest(location);
|
||||
if(type == ".pce") return pcEngineManifest(location);
|
||||
if(type == ".gb") return gameBoyManifest(location);
|
||||
if(type == ".gbc") return gameBoyColorManifest(location);
|
||||
if(type == ".gba") return gameBoyAdvanceManifest(location);
|
||||
|
@ -74,6 +76,7 @@ auto Icarus::import(string location) -> string {
|
|||
if(type == ".sfc" || type == ".smc") return superFamicomImport(buffer, location);
|
||||
if(type == ".ms" || type == ".sms") return masterSystemImport(buffer, location);
|
||||
if(type == ".md" || type == ".smd" || type == ".gen") return megaDriveImport(buffer, location);
|
||||
if(type == ".pce") return pcEngineImport(buffer, location);
|
||||
if(type == ".gb") return gameBoyImport(buffer, location);
|
||||
if(type == ".gbc") return gameBoyColorImport(buffer, location);
|
||||
if(type == ".gba") return gameBoyAdvanceImport(buffer, location);
|
||||
|
|
|
@ -32,6 +32,11 @@ struct Icarus {
|
|||
auto megaDriveManifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
auto megaDriveImport(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
||||
//pc-engine.cpp
|
||||
auto pcEngineManifest(string location) -> string;
|
||||
auto pcEngineManifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
auto pcEngineImport(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
||||
//game-boy.cpp
|
||||
auto gameBoyManifest(string location) -> string;
|
||||
auto gameBoyManifest(vector<uint8_t>& buffer, string location) -> string;
|
||||
|
@ -80,6 +85,7 @@ private:
|
|||
Markup::Node superFamicom;
|
||||
Markup::Node masterSystem;
|
||||
Markup::Node megaDrive;
|
||||
Markup::Node pcEngine;
|
||||
Markup::Node gameBoy;
|
||||
Markup::Node gameBoyColor;
|
||||
Markup::Node gameBoyAdvance;
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
auto Icarus::pcEngineManifest(string location) -> string {
|
||||
vector<uint8_t> buffer;
|
||||
concatenate(buffer, {location, "program.rom"});
|
||||
return pcEngineManifest(buffer, location);
|
||||
}
|
||||
|
||||
auto Icarus::pcEngineManifest(vector<uint8_t>& buffer, string location) -> string {
|
||||
string manifest;
|
||||
|
||||
if(settings["icarus/UseDatabase"].boolean() && !manifest) {
|
||||
string digest = Hash::SHA256(buffer.data(), buffer.size()).digest();
|
||||
for(auto node : database.pcEngine) {
|
||||
if(node["sha256"].text() == digest) {
|
||||
manifest.append(node.text(), "\n sha256: ", digest, "\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(settings["icarus/UseHeuristics"].boolean() && !manifest) {
|
||||
PCEngineCartridge cartridge{location, buffer.data(), buffer.size()};
|
||||
manifest = cartridge.manifest;
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
auto Icarus::pcEngineImport(vector<uint8_t>& buffer, string location) -> string {
|
||||
auto name = Location::prefix(location);
|
||||
auto source = Location::path(location);
|
||||
string target{settings["Library/Location"].text(), "PC Engine/", name, ".pce/"};
|
||||
//if(directory::exists(target)) return failure("game already exists");
|
||||
|
||||
auto manifest = pcEngineManifest(buffer, location);
|
||||
if(!manifest) return failure("failed to parse ROM image");
|
||||
|
||||
if(!directory::create(target)) return failure("library path unwritable");
|
||||
if(file::exists({source, name, ".sav"}) && !file::exists({target, "save.ram"})) {
|
||||
file::copy({source, name, ".sav"}, {target, "save.ram"});
|
||||
}
|
||||
|
||||
if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, manifest);
|
||||
file::write({target, "program.rom"}, buffer);
|
||||
return success(target);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
struct PCEngineCartridge {
|
||||
PCEngineCartridge(string location, uint8_t* data, uint size);
|
||||
|
||||
string manifest;
|
||||
|
||||
//private:
|
||||
struct Information {
|
||||
} information;
|
||||
};
|
||||
|
||||
PCEngineCartridge::PCEngineCartridge(string location, uint8_t* data, uint size) {
|
||||
manifest.append("board\n");
|
||||
manifest.append(" rom name=program.rom size=0x", hex(size), "\n");
|
||||
manifest.append(" ram name=save.ram size=0x8000\n");
|
||||
manifest.append("\n");
|
||||
manifest.append("information\n");
|
||||
manifest.append(" title: ", Location::prefix(location), "\n");
|
||||
manifest.append(" sha256: ", Hash::SHA256(data, size).digest(), "\n");
|
||||
manifest.append("\n");
|
||||
manifest.append("note: heuristically generated by icarus\n");
|
||||
}
|
|
@ -22,6 +22,7 @@ Settings settings;
|
|||
#include "heuristics/super-famicom.cpp"
|
||||
#include "heuristics/master-system.cpp"
|
||||
#include "heuristics/mega-drive.cpp"
|
||||
#include "heuristics/pc-engine.cpp"
|
||||
#include "heuristics/game-boy.cpp"
|
||||
#include "heuristics/game-boy-advance.cpp"
|
||||
#include "heuristics/game-gear.cpp"
|
||||
|
@ -35,6 +36,7 @@ Settings settings;
|
|||
#include "core/super-famicom.cpp"
|
||||
#include "core/master-system.cpp"
|
||||
#include "core/mega-drive.cpp"
|
||||
#include "core/pc-engine.cpp"
|
||||
#include "core/game-boy.cpp"
|
||||
#include "core/game-boy-color.cpp"
|
||||
#include "core/game-boy-advance.cpp"
|
||||
|
@ -77,6 +79,7 @@ auto nall::main(string_vector args) -> void {
|
|||
"*.sfc:*.smc:"
|
||||
"*.ms:*.sms:"
|
||||
"*.md:*.smd:*.gen:"
|
||||
"*.pce:"
|
||||
"*.gb:"
|
||||
"*.gbc:"
|
||||
"*.gba:"
|
||||
|
|
|
@ -102,9 +102,13 @@ auto ScanDialog::gamePakType(const string& type) -> bool {
|
|||
return type == ".sys"
|
||||
|| type == ".fc"
|
||||
|| type == ".sfc"
|
||||
|| type == ".ms"
|
||||
|| type == ".md"
|
||||
|| type == ".pce"
|
||||
|| type == ".gb"
|
||||
|| type == ".gbc"
|
||||
|| type == ".gba"
|
||||
|| type == ".gg"
|
||||
|| type == ".bs"
|
||||
|| type == ".st";
|
||||
}
|
||||
|
@ -113,9 +117,13 @@ auto ScanDialog::gameRomType(const string& type) -> bool {
|
|||
return type == ".zip"
|
||||
|| type == ".fc" || type == ".nes"
|
||||
|| type == ".sfc" || type == ".smc"
|
||||
|| type == ".ms" || type == ".sms"
|
||||
|| type == ".md" || type == ".smd" || type == ".gen"
|
||||
|| type == ".pce"
|
||||
|| type == ".gb"
|
||||
|| type == ".gbc"
|
||||
|| type == ".gba"
|
||||
|| type == ".gg"
|
||||
|| type == ".bs"
|
||||
|| type == ".st";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue