diff --git a/ares/GNUmakefile b/ares/GNUmakefile index 86e49365b9..f761ad868d 100644 --- a/ares/GNUmakefile +++ b/ares/GNUmakefile @@ -73,6 +73,10 @@ ifneq ($(filter $(cores),cv),) include $(ares.path)/cv/GNUmakefile endif +ifneq ($(filter $(cores),pencil2),) + include $(ares.path)/pencil2/GNUmakefile +endif + ifneq ($(filter $(cores),myvision),) include $(ares.path)/myvision/GNUmakefile endif diff --git a/ares/pencil2/GNUmakefile b/ares/pencil2/GNUmakefile new file mode 100644 index 0000000000..41e00a7c3c --- /dev/null +++ b/ares/pencil2/GNUmakefile @@ -0,0 +1,17 @@ +ares.components += z80 +ares.components += tms9918 +ares.components += sn76489 + +ares.objects += ares-pencil2-cpu +ares.objects += ares-pencil2-vdp +ares.objects += ares-pencil2-psg +ares.objects += ares-pencil2-system +ares.objects += ares-pencil2-cartridge +ares.objects += ares-pencil2-controller + +$(object.path)/ares-pencil2-cpu.o: $(ares.path)/pencil2/cpu/cpu.cpp +$(object.path)/ares-pencil2-vdp.o: $(ares.path)/pencil2/vdp/vdp.cpp +$(object.path)/ares-pencil2-psg.o: $(ares.path)/pencil2/psg/psg.cpp +$(object.path)/ares-pencil2-system.o: $(ares.path)/pencil2/system/system.cpp +$(object.path)/ares-pencil2-cartridge.o: $(ares.path)/pencil2/cartridge/cartridge.cpp +$(object.path)/ares-pencil2-controller.o: $(ares.path)/pencil2/controller/controller.cpp diff --git a/ares/pencil2/cartridge/board/board.cpp b/ares/pencil2/cartridge/board/board.cpp new file mode 100644 index 0000000000..e264c14ed1 --- /dev/null +++ b/ares/pencil2/cartridge/board/board.cpp @@ -0,0 +1,31 @@ +namespace Board { + +#include "pencil2.cpp" + +auto Interface::load(Memory::Readable& memory, string name) -> bool { + if(auto fp = pak->read(name)) { + memory.allocate(fp->size()); + memory.load(fp); + return true; + } + return false; +} + +auto Interface::load(Memory::Writable& memory, string name) -> bool { + if(auto fp = pak->read(name)) { + memory.allocate(fp->size()); + memory.load(fp); + return true; + } + return false; +} + +auto Interface::save(Memory::Writable& memory, string name) -> bool { + if(auto fp = pak->write(name)) { + memory.save(fp); + return true; + } + return false; +} + +} diff --git a/ares/pencil2/cartridge/board/board.hpp b/ares/pencil2/cartridge/board/board.hpp new file mode 100644 index 0000000000..5b2496a27a --- /dev/null +++ b/ares/pencil2/cartridge/board/board.hpp @@ -0,0 +1,23 @@ +namespace Board { + +struct Interface { + VFS::Pak pak; + + Interface(Cartridge& cartridge) : cartridge(cartridge) {} + virtual ~Interface() = default; + virtual auto load() -> void {} + virtual auto save() -> void {} + virtual auto unload() -> void {} + virtual auto read(n16 address) -> n8 { return 0; } + virtual auto write(n16 address, n8 data) -> void {} + virtual auto power() -> void {} + virtual auto serialize(serializer&) -> void {} + + auto load(Memory::Readable&, string name) -> bool; + auto load(Memory::Writable&, string name) -> bool; + auto save(Memory::Writable&, string name) -> bool; + + Cartridge& cartridge; +}; + +} diff --git a/ares/pencil2/cartridge/board/pencil2.cpp b/ares/pencil2/cartridge/board/pencil2.cpp new file mode 100644 index 0000000000..6f37677c1d --- /dev/null +++ b/ares/pencil2/cartridge/board/pencil2.cpp @@ -0,0 +1,25 @@ +struct pencil2 : Interface { + using Interface::Interface; + Memory::Readable rom; + + auto load() -> void override { + Interface::load(rom, "program.rom"); + } + + auto save() -> void override {} + + auto unload() -> void override {} + + auto read(n16 address) -> n8 override { + return rom.read(address); + } + + auto write(n16 address, n8 data) -> void override { + return; + } + + auto power() -> void override {} + + auto serialize(serializer& s) -> void override {} + +}; diff --git a/ares/pencil2/cartridge/cartridge.cpp b/ares/pencil2/cartridge/cartridge.cpp new file mode 100644 index 0000000000..32781622b8 --- /dev/null +++ b/ares/pencil2/cartridge/cartridge.cpp @@ -0,0 +1,57 @@ +#include + +namespace ares::Pencil2 { + +Cartridge& cartridge = cartridgeSlot.cartridge; +#include "board/board.cpp" +#include "slot.cpp" +#include "serialization.cpp" + +auto Cartridge::allocate(Node::Port parent) -> Node::Peripheral { + return node = parent->append(string{system.name(), " Cartridge"}); +} + +auto Cartridge::connect() -> void { + if(!node->setPak(pak = platform->pak(node))) return; + + information = {}; + information.title = pak->attribute("title"); + information.region = pak->attribute("region"); + information.board = pak->attribute("board"); + + if(information.board == "pencil2") board = new Board::pencil2{*this}; + + + if(!board) board = new Board::Interface{*this}; + board->pak = pak; + board->load(); + power(); +} + +auto Cartridge::disconnect() -> void { + if(!node || !board) return; + board->unload(); + board->pak.reset(); + board.reset(); + node.reset(); +} + +auto Cartridge::save() -> void { + if(!node) return; + if(board) board->save(); +} + +auto Cartridge::power() -> void { + if(board) board->power(); +} + +auto Cartridge::read(n16 address) -> n8 { + if(board) return board->read(address); + return 0; +} + +auto Cartridge::write(n16 address, n8 data) -> void { + if(board) return board->write(address, data); +} + +} diff --git a/ares/pencil2/cartridge/cartridge.hpp b/ares/pencil2/cartridge/cartridge.hpp new file mode 100644 index 0000000000..feea5aec05 --- /dev/null +++ b/ares/pencil2/cartridge/cartridge.hpp @@ -0,0 +1,36 @@ +struct Cartridge; +#include "board/board.hpp" + +struct Cartridge { + Node::Peripheral node; + VFS::Pak pak; + + auto title() const -> string { return information.title; } + auto region() const -> string { return information.region; } + + //cartridge.cpp + auto allocate(Node::Port) -> Node::Peripheral; + auto connect() -> void; + auto disconnect() -> void; + + auto save() -> void; + auto power() -> void; + + auto read(n16 address) -> n8; + auto write(n16 address, n8 data) -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + + unique_pointer board; + +//private: + struct Information { + string title; + string region; + string board; + } information; +}; + +#include "slot.hpp" +extern Cartridge& cartridge; diff --git a/ares/pencil2/cartridge/serialization.cpp b/ares/pencil2/cartridge/serialization.cpp new file mode 100644 index 0000000000..3437f2d927 --- /dev/null +++ b/ares/pencil2/cartridge/serialization.cpp @@ -0,0 +1,2 @@ +auto Cartridge::serialize(serializer& s) -> void { +} diff --git a/ares/pencil2/cartridge/slot.cpp b/ares/pencil2/cartridge/slot.cpp new file mode 100644 index 0000000000..733eb3ab47 --- /dev/null +++ b/ares/pencil2/cartridge/slot.cpp @@ -0,0 +1,18 @@ +CartridgeSlot cartridgeSlot{"Cartridge Slot"}; + +CartridgeSlot::CartridgeSlot(string name) : name(name) { +} + +auto CartridgeSlot::load(Node::Object parent) -> void { + port = parent->append(name); + port->setFamily(system.name()); + port->setType("Cartridge"); + port->setAllocate([&](auto name) { return cartridge.allocate(port); }); + port->setConnect([&] { return cartridge.connect(); }); + port->setDisconnect([&] { return cartridge.disconnect(); }); +} + +auto CartridgeSlot::unload() -> void { + cartridge.disconnect(); + port = {}; +} diff --git a/ares/pencil2/cartridge/slot.hpp b/ares/pencil2/cartridge/slot.hpp new file mode 100644 index 0000000000..a0f5e9be0a --- /dev/null +++ b/ares/pencil2/cartridge/slot.hpp @@ -0,0 +1,13 @@ +struct CartridgeSlot { + Node::Port port; + Cartridge cartridge; + + //slot.cpp + CartridgeSlot(string name); + auto load(Node::Object) -> void; + auto unload() -> void; + + const string name; +}; + +extern CartridgeSlot cartridgeSlot; diff --git a/ares/pencil2/controller/controller.cpp b/ares/pencil2/controller/controller.cpp new file mode 100644 index 0000000000..07549600ee --- /dev/null +++ b/ares/pencil2/controller/controller.cpp @@ -0,0 +1,8 @@ +#include + +namespace ares::Pencil2 { + +#include "port.cpp" +#include "gamepad/gamepad.cpp" + +} diff --git a/ares/pencil2/controller/controller.hpp b/ares/pencil2/controller/controller.hpp new file mode 100644 index 0000000000..71b70bae2f --- /dev/null +++ b/ares/pencil2/controller/controller.hpp @@ -0,0 +1,11 @@ +struct Controller : Thread { + Node::Peripheral node; + + virtual ~Controller() = default; + + virtual auto read() -> n8 { return 0xff; } + virtual auto write(n8 data) -> void {} +}; + +#include "port.hpp" +#include "gamepad/gamepad.hpp" diff --git a/ares/pencil2/controller/gamepad/gamepad.cpp b/ares/pencil2/controller/gamepad/gamepad.cpp new file mode 100644 index 0000000000..45d2a56909 --- /dev/null +++ b/ares/pencil2/controller/gamepad/gamepad.cpp @@ -0,0 +1,83 @@ +Gamepad::Gamepad(Node::Port parent) { + node = parent->append("Gamepad"); + + up = node->append("Up"); + down = node->append("Down"); + left = node->append("Left"); + right = node->append("Right"); + l = node->append("L"); + r = node->append("R"); + one = node->append("1"); + two = node->append("2"); + three = node->append("3"); + four = node->append("4"); + five = node->append("5"); + six = node->append("6"); + seven = node->append("7"); + eight = node->append("8"); + nine = node->append("9"); + star = node->append("*"); + zero = node->append("0"); + pound = node->append("#"); +} + +auto Gamepad::read() -> n8 { + platform->input(up); + platform->input(down); + platform->input(left); + platform->input(right); + platform->input(l); + platform->input(r); + platform->input(one); + platform->input(two); + platform->input(three); + platform->input(four); + platform->input(five); + platform->input(six); + platform->input(seven); + platform->input(eight); + platform->input(nine); + platform->input(star); + platform->input(zero); + platform->input(pound); + + if(!(up->value() & down->value())) { + yHold = 0, upLatch = up->value(), downLatch = down->value(); + } else if(!yHold) { + yHold = 1, swap(upLatch, downLatch); + } + + if(!(left->value() & right->value())) { + xHold = 0, leftLatch = left->value(), rightLatch = right->value(); + } else if(!xHold) { + xHold = 1, swap(leftLatch, rightLatch); + } + + n8 data = 0x7f; + if(select == 0) { + if(one->value ()) data.bit(0,3) = 0b1101; + else if(two->value ()) data.bit(0,3) = 0b0111; + else if(three->value()) data.bit(0,3) = 0b1100; + else if(four->value ()) data.bit(0,3) = 0b0010; + else if(five->value ()) data.bit(0,3) = 0b0011; + else if(six->value ()) data.bit(0,3) = 0b1110; + else if(seven->value()) data.bit(0,3) = 0b0101; + else if(eight->value()) data.bit(0,3) = 0b0001; + else if(nine->value ()) data.bit(0,3) = 0b1011; + else if(star->value ()) data.bit(0,3) = 0b1001; + else if(zero->value ()) data.bit(0,3) = 0b1010; + else if(pound->value()) data.bit(0,3) = 0b0110; + data.bit(6) = !r->value(); + } else { + data.bit(0) = !upLatch; + data.bit(1) = !rightLatch; + data.bit(2) = !downLatch; + data.bit(3) = !leftLatch; + data.bit(6) = !l->value(); + } + return data; +} + +auto Gamepad::write(n8 data) -> void { + select = data.bit(0); +} diff --git a/ares/pencil2/controller/gamepad/gamepad.hpp b/ares/pencil2/controller/gamepad/gamepad.hpp new file mode 100644 index 0000000000..3e1d7114ce --- /dev/null +++ b/ares/pencil2/controller/gamepad/gamepad.hpp @@ -0,0 +1,35 @@ +struct Gamepad : Controller { + Node::Input::Button up; + Node::Input::Button down; + Node::Input::Button left; + Node::Input::Button right; + Node::Input::Button l; + Node::Input::Button r; + Node::Input::Button one; + Node::Input::Button two; + Node::Input::Button three; + Node::Input::Button four; + Node::Input::Button five; + Node::Input::Button six; + Node::Input::Button seven; + Node::Input::Button eight; + Node::Input::Button nine; + Node::Input::Button star; + Node::Input::Button zero; + Node::Input::Button pound; + + Gamepad(Node::Port); + + auto read() -> n8 override; + auto write(n8 data) -> void override; + + n1 select; + +private: + b1 yHold; + b1 upLatch; + b1 downLatch; + b1 xHold; + b1 leftLatch; + b1 rightLatch; +}; diff --git a/ares/pencil2/controller/port.cpp b/ares/pencil2/controller/port.cpp new file mode 100644 index 0000000000..1514c4a440 --- /dev/null +++ b/ares/pencil2/controller/port.cpp @@ -0,0 +1,29 @@ +ControllerPort controllerPort1{"Controller Port 1"}; +ControllerPort controllerPort2{"Controller Port 2"}; + +ControllerPort::ControllerPort(string name) : name(name) { +} + +auto ControllerPort::load(Node::Object parent) -> void { + port = parent->append(name); + port->setFamily("ColecoVision"); + port->setType("Controller"); + port->setHotSwappable(true); + port->setAllocate([&](auto name) { return allocate(name); }); + port->setDisconnect([&] { device.reset(); }); + port->setSupported({"Gamepad"}); +} + +auto ControllerPort::unload() -> void { + device = {}; + port = {}; +} + +auto ControllerPort::allocate(string name) -> Node::Peripheral { + if(name == "Gamepad") device = new Gamepad(port); + if(device) return device->node; + return {}; +} + +auto ControllerPort::serialize(serializer& s) -> void { +} diff --git a/ares/pencil2/controller/port.hpp b/ares/pencil2/controller/port.hpp new file mode 100644 index 0000000000..808004bcee --- /dev/null +++ b/ares/pencil2/controller/port.hpp @@ -0,0 +1,20 @@ +struct ControllerPort { + Node::Port port; + unique_pointer device; + + //port.cpp + ControllerPort(string name); + auto load(Node::Object) -> void; + auto unload() -> void; + auto allocate(string name) -> Node::Peripheral; + + auto read() -> n8 { if(device) return device->read(); return 0xff; } + auto write(n8 data) -> void { if(device) return device->write(data); } + + auto serialize(serializer&) -> void; + + const string name; +}; + +extern ControllerPort controllerPort1; +extern ControllerPort controllerPort2; diff --git a/ares/pencil2/cpu/cpu.cpp b/ares/pencil2/cpu/cpu.cpp new file mode 100644 index 0000000000..29a94129f6 --- /dev/null +++ b/ares/pencil2/cpu/cpu.cpp @@ -0,0 +1,68 @@ +#include + +namespace ares::Pencil2 { + +CPU cpu; +#include "memory.cpp" +#include "debugger.cpp" +#include "serialization.cpp" + +auto CPU::load(Node::Object parent) -> void { + ram.allocate(0x0800); + expansion.allocate(0x1000); + + node = parent->append("CPU"); + + debugger.load(node); +} + +auto CPU::unload() -> void { + ram.reset(); + expansion.reset(); + node = {}; + debugger = {}; +} + +auto CPU::main() -> void { + if(state.nmiPending) { + state.nmiPending = 0; //edge-sensitive + debugger.interrupt("NMI"); + nmi(); + } + + if(state.irqLine) { + //level-sensitive + debugger.interrupt("IRQ"); + irq(); + } + + debugger.instruction(); + instruction(); +} + +auto CPU::step(uint clocks) -> void { + Thread::step(clocks); + Thread::synchronize(); +} + +auto CPU::setNMI(bool value) -> void { + if(!state.nmiLine && value) state.nmiPending = 1; + state.nmiLine = value; +} + +auto CPU::setIRQ(bool value) -> void { + state.irqLine = value; +} + +auto CPU::power() -> void { + Z80::bus = this; + Z80::power(); + Thread::create(system.crystalFrequency() / 3.0, {&CPU::main, this}); + + PC = 0x0000; //reset vector address + state = {}; + io = {}; + ram.fill(0); +} + +} diff --git a/ares/pencil2/cpu/cpu.hpp b/ares/pencil2/cpu/cpu.hpp new file mode 100644 index 0000000000..2416512f51 --- /dev/null +++ b/ares/pencil2/cpu/cpu.hpp @@ -0,0 +1,60 @@ +struct CPU : Z80, Z80::Bus, Thread { + Node::Object node; + Memory::Writable ram; + Memory::Writable expansion; + + struct Debugger { + //debugger.cpp + auto load(Node::Object) -> void; + auto instruction() -> void; + auto interrupt(string_view) -> void; + + struct Memory { + Node::Debugger::Memory ram; + Node::Debugger::Memory expansion; + } memory; + + struct Tracer { + Node::Debugger::Tracer::Instruction instruction; + Node::Debugger::Tracer::Notification interrupt; + } tracer; + } debugger; + + auto synchronizing() const -> bool override { return scheduler.synchronizing(); } + + //cpu.cpp + auto load(Node::Object) -> void; + auto unload() -> void; + + auto main() -> void; + auto step(u32 clocks) -> void override; + + auto setNMI(bool value) -> void; + auto setIRQ(bool value) -> void; + + auto power() -> void; + + //memory.cpp + auto read(n16 address) -> n8 override; + auto write(n16 address, n8 data) -> void override; + + auto in(n16 address) -> n8 override; + auto out(n16 address, n8 data) -> void override; + + //serialization.cpp + auto serialize(serializer&) -> void; + +private: + struct State { + bool nmiPending = 0; + bool nmiLine = 0; + bool irqLine = 0; + } state; + + struct IO { + bool replaceRAM = 0; + bool replaceBIOS = 0; + } io; +}; + +extern CPU cpu; diff --git a/ares/pencil2/cpu/debugger.cpp b/ares/pencil2/cpu/debugger.cpp new file mode 100644 index 0000000000..f0f92357e7 --- /dev/null +++ b/ares/pencil2/cpu/debugger.cpp @@ -0,0 +1,36 @@ +auto CPU::Debugger::load(Node::Object parent) -> void { + memory.ram = parent->append("CPU RAM"); + memory.ram->setSize(cpu.ram.size()); + memory.ram->setRead([&](u32 address) -> u8 { + return cpu.ram[address]; + }); + memory.ram->setWrite([&](u32 address, u8 data) -> void { + cpu.ram[address] = data; + }); + + memory.expansion = parent->append("CPU EXPRAM"); + memory.expansion->setSize(cpu.expansion.size()); + memory.expansion->setRead([&](u32 address) -> u8 { + return cpu.expansion[address]; + }); + memory.expansion->setWrite([&](u32 address, u8 data) -> void { + cpu.expansion[address] = data; + }); + + tracer.instruction = parent->append("Instruction", "CPU"); + tracer.instruction->setAddressBits(16); + + tracer.interrupt = parent->append("Interrupt", "CPU"); +} + +auto CPU::Debugger::instruction() -> void { + if(tracer.instruction->enabled() && tracer.instruction->address(cpu.PC)) { + tracer.instruction->notify(cpu.disassembleInstruction(), cpu.disassembleContext()); + } +} + +auto CPU::Debugger::interrupt(string_view type) -> void { + if(tracer.interrupt->enabled()) { + tracer.interrupt->notify(type); + } +} diff --git a/ares/pencil2/cpu/memory.cpp b/ares/pencil2/cpu/memory.cpp new file mode 100644 index 0000000000..bceff68ba7 --- /dev/null +++ b/ares/pencil2/cpu/memory.cpp @@ -0,0 +1,36 @@ +auto CPU::read(n16 address) -> n8 { + n8 data = 0xff; + if(auto result = platform->cheat(address)) return *result; + if(address >= 0x0000 && address <= 0x1fff && io.replaceBIOS) return expansion.read(address); + if(address >= 0x2000 && address <= 0x7fff && io.replaceRAM ) return expansion.read(address); + if(address >= 0x0000 && address <= 0x1fff) return system.bios[address & 0x1fff]; + if(address >= 0x6000 && address <= 0x7fff) return ram.read(address - 0x6000); + if(address >= 0x8000 && address <= 0xffff) return cartridge.read(address - 0x8000); + return data; +} + +auto CPU::write(n16 address, n8 data) -> void { + if(address >= 0x0000 && address <= 0x1fff && io.replaceBIOS) return expansion.write(address, data); + if(address >= 0x2000 && address <= 0x7fff && io.replaceRAM) return expansion.write(address, data); + if(address >= 0x6000 && address <= 0x7fff) return ram.write(address - 0x6000, data); + if(address >= 0x8000 && address <= 0xffff) return; +} + +auto CPU::in(n16 address) -> n8 { + address &= 0xff; + n8 data = 0xff; + if(address >= 0xa0 && address <= 0xbf) return !address.bit(0) ? vdp.data() : vdp.status(); + if(address >= 0xe0 && address <= 0xff && address.bit(1) == 0) return controllerPort1.read(); + if(address >= 0xe0 && address <= 0xff && address.bit(1) == 1) return controllerPort2.read(); + return data; +} + +auto CPU::out(n16 address, n8 data) -> void { + address &= 0xff; + if(address == 0x53) io.replaceRAM = data.bit(0); + if(address == 0x7f) io.replaceBIOS = data.bit(1); + if(address >= 0x80 && address <= 0x9f) controllerPort1.write(0), controllerPort2.write(0); + if(address >= 0xa0 && address <= 0xbf) return !address.bit(0) ? vdp.data(data) : vdp.control(data); + if(address >= 0xc0 && address <= 0xdf) controllerPort1.write(1), controllerPort2.write(1); + if(address >= 0xe0 && address <= 0xff) return psg.write(data); +} diff --git a/ares/pencil2/cpu/serialization.cpp b/ares/pencil2/cpu/serialization.cpp new file mode 100644 index 0000000000..6f3e3539e4 --- /dev/null +++ b/ares/pencil2/cpu/serialization.cpp @@ -0,0 +1,11 @@ +auto CPU::serialize(serializer& s) -> void { + Z80::serialize(s); + Thread::serialize(s); + s(ram); + s(expansion); + s(state.nmiPending); + s(state.nmiLine); + s(state.irqLine); + s(io.replaceBIOS); + s(io.replaceRAM); +} diff --git a/ares/pencil2/pencil2.hpp b/ares/pencil2/pencil2.hpp new file mode 100644 index 0000000000..d013949cef --- /dev/null +++ b/ares/pencil2/pencil2.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include +#include +#include + +namespace ares::Pencil2 { + #include + auto enumerate() -> vector; + auto load(Node::System& node, string name) -> bool; + + struct Model { + inline static auto Pencil2() -> bool; + }; + + struct Region { + inline static auto PAL() -> bool; + }; + + #include + + #include + #include + #include + + #include + #include +} diff --git a/ares/pencil2/psg/psg.cpp b/ares/pencil2/psg/psg.cpp new file mode 100644 index 0000000000..a4616d4d25 --- /dev/null +++ b/ares/pencil2/psg/psg.cpp @@ -0,0 +1,49 @@ +#include + +namespace ares::Pencil2 { + +PSG psg; +#include "serialization.cpp" + +auto PSG::load(Node::Object parent) -> void { + node = parent->append("PSG"); + + stream = node->append("PSG"); + stream->setChannels(1); + stream->setFrequency((system.crystalFrequency() / 3.0) / 16.0); + stream->addHighPassFilter(20.0, 1); +} + +auto PSG::unload() -> void { + node->remove(stream); + stream.reset(); + node.reset(); +} + +auto PSG::main() -> void { + auto channels = SN76489::clock(); + double output = 0.0; + output += volume[channels[0]]; + output += volume[channels[1]]; + output += volume[channels[2]]; + output += volume[channels[3]]; + stream->frame(output / 4.0); + step(1); +} + +auto PSG::step(u32 clocks) -> void { + Thread::step(clocks); + Thread::synchronize(cpu); +} + +auto PSG::power() -> void { + SN76489::power(); + Thread::create((system.crystalFrequency() / 3.0) / 16.0, {&PSG::main, this}); + + for(u32 level : range(15)) { + volume[level] = pow(2, level * -2.0 / 6.0); + } + volume[15] = 0; +} + +} diff --git a/ares/pencil2/psg/psg.hpp b/ares/pencil2/psg/psg.hpp new file mode 100644 index 0000000000..ff29a18ac0 --- /dev/null +++ b/ares/pencil2/psg/psg.hpp @@ -0,0 +1,20 @@ +struct PSG : SN76489, Thread { + Node::Object node; + Node::Audio::Stream stream; + + //psg.cpp + auto load(Node::Object) -> void; + auto unload() -> void; + + auto main() -> void; + auto step(u32 clocks) -> void; + auto power() -> void; + + //serialization.cpp + auto serialize(serializer&) -> void; + +private: + f64 volume[16]; +}; + +extern PSG psg; diff --git a/ares/pencil2/psg/serialization.cpp b/ares/pencil2/psg/serialization.cpp new file mode 100644 index 0000000000..d5e3011763 --- /dev/null +++ b/ares/pencil2/psg/serialization.cpp @@ -0,0 +1,4 @@ +auto PSG::serialize(serializer& s) -> void { + SN76489::serialize(s); + Thread::serialize(s); +} diff --git a/ares/pencil2/system/controls.cpp b/ares/pencil2/system/controls.cpp new file mode 100644 index 0000000000..e4e65d7093 --- /dev/null +++ b/ares/pencil2/system/controls.cpp @@ -0,0 +1,11 @@ +auto System::Controls::load(Node::Object parent) -> void { + node = parent->append("Controls"); + + reset = node->append("Reset"); +} + +auto System::Controls::poll() -> void { + auto value = reset->value(); + platform->input(reset); + if(!value && reset->value()) system.power(); //todo: implement soft reset +} diff --git a/ares/pencil2/system/serialization.cpp b/ares/pencil2/system/serialization.cpp new file mode 100644 index 0000000000..a01f0cf560 --- /dev/null +++ b/ares/pencil2/system/serialization.cpp @@ -0,0 +1,48 @@ +static const string SerializerVersion = "v131"; + +auto System::serialize(bool synchronize) -> serializer { + if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize); + serializer s; + + u32 signature = SerializerSignature; + char version[16] = {}; + char description[512] = {}; + memory::copy(&version, (const char*)SerializerVersion, SerializerVersion.size()); + + s(signature); + s(synchronize); + s(version); + s(description); + + serialize(s, synchronize); + return s; +} + +auto System::unserialize(serializer& s) -> bool { + u32 signature = 0; + bool synchronize = true; + char version[16] = {}; + char description[512] = {}; + + s(signature); + s(synchronize); + s(version); + s(description); + + if(signature != SerializerSignature) return false; + if(string{version} != SerializerVersion) return false; + + if(synchronize) power(); + serialize(s, synchronize); + return true; +} + +auto System::serialize(serializer& s, bool synchronize) -> void { + scheduler.setSynchronize(synchronize); + s(cartridge); + s(cpu); + s(vdp); + s(psg); + s(controllerPort1); + s(controllerPort2); +} diff --git a/ares/pencil2/system/system.cpp b/ares/pencil2/system/system.cpp new file mode 100644 index 0000000000..c31777119b --- /dev/null +++ b/ares/pencil2/system/system.cpp @@ -0,0 +1,101 @@ +#include + +namespace ares::Pencil2 { + +auto enumerate() -> vector { + return { + "[Hanimex] Pencil 2 (PAL)", + }; +} + +auto load(Node::System& node, string name) -> bool { + if(!enumerate().find(name)) return false; + return system.load(node, name); +} + +Scheduler scheduler; +System system; +#include "controls.cpp" +#include "serialization.cpp" + +auto System::game() -> string { + if(cartridge.node) { + return cartridge.title(); + } + + return "(no cartridge connected)"; +} + +auto System::run() -> void { + scheduler.enter(); + controls.poll(); +} + +auto System::load(Node::System& root, string name) -> bool { + if(node) unload(); + + information = {}; + if(name.find("Pencil 2")) { + information.name = "Pencil 2"; + information.model = Model::Pencil2; + } + if(name.find("PAL")) { + information.region = Region::PAL; + information.crystalFrequency = 10'738'635; + } + + node = Node::System::create(information.name); + node->setGame({&System::game, this}); + node->setRun({&System::run, this}); + node->setPower({&System::power, this}); + node->setSave({&System::save, this}); + node->setUnload({&System::unload, this}); + node->setSerialize({&System::serialize, this}); + node->setUnserialize({&System::unserialize, this}); + root = node; + if(!node->setPak(pak = platform->pak(node))) return false; + + if(auto fp = pak->read("mt.u4")) { + fp->read({bios, 0x2000}); + } + + scheduler.reset(); + controls.load(node); + cpu.load(node); + vdp.load(node); + psg.load(node); + cartridgeSlot.load(node); + controllerPort1.load(node); + controllerPort2.load(node); + return true; +} + +auto System::save() -> void { + if(!node) return; + cartridge.save(); +} + +auto System::unload() -> void { + if(!node) return; + save(); + cpu.unload(); + vdp.unload(); + psg.unload(); + cartridgeSlot.unload(); + controllerPort1.port = {}; + controllerPort2.port = {}; + pak.reset(); + node.reset(); +} + +auto System::power(bool reset) -> void { + for(auto& setting : node->find()) setting->setLatch(); + + cartridge.power(); + cpu.power(); + vdp.power(); + psg.power(); + scheduler.power(cpu); +} + +} diff --git a/ares/pencil2/system/system.hpp b/ares/pencil2/system/system.hpp new file mode 100644 index 0000000000..53d9c6bed5 --- /dev/null +++ b/ares/pencil2/system/system.hpp @@ -0,0 +1,53 @@ +struct System { + Node::System node; + VFS::Pak pak; + + struct Controls { + Node::Object node; + Node::Input::Button reset; + + //controls.cpp + auto load(Node::Object) -> void; + auto poll() -> void; + } controls; + + enum class Model : u32 { Pencil2 }; + enum class Region : u32 { PAL }; + + auto name() const -> string { return information.name; } + auto model() const -> Model { return information.model; } + auto region() const -> Region { return information.region; } + auto crystalFrequency() const -> f64 { return information.crystalFrequency; } + + //system.cpp + auto game() -> string; + auto run() -> void; + + auto load(Node::System& node, string name) -> bool; + auto save() -> void; + auto unload() -> void; + auto power(bool reset = false) -> void; + + //serialization.cpp + auto serialize(bool synchronize) -> serializer; + auto unserialize(serializer&) -> bool; + + u8 bios[0x2000]; + +private: + struct Information { + string name = "Pencil 2"; + Model model = Model::Pencil2; + Region region = Region::PAL; + f64 crystalFrequency = 10'738'635; + } information; + + //serialization.cpp + auto serialize(serializer&, bool synchronize) -> void; +}; + +extern System system; + +auto Model::Pencil2() -> bool { return system.model() == System::Model::Pencil2; } + +auto Region::PAL() -> bool { return system.region() == System::Region::PAL; } diff --git a/ares/pencil2/vdp/color.cpp b/ares/pencil2/vdp/color.cpp new file mode 100644 index 0000000000..53f3dd7ccf --- /dev/null +++ b/ares/pencil2/vdp/color.cpp @@ -0,0 +1,21 @@ +auto VDP::color(n32 color) -> n64 { + switch(color.bit(0,3)) { + case 0: return 0x0000'0000'0000ull; //transparent + case 1: return 0x0000'0000'0000ull; //black + case 2: return 0x2121'c8c8'4242ull; //medium green + case 3: return 0x5e5e'dcdc'7878ull; //light green + case 4: return 0x5454'5555'ededull; //dark blue + case 5: return 0x7d7d'7676'fcfcull; //light blue + case 6: return 0xd4d4'5252'4d4dull; //dark red + case 7: return 0x4242'ebeb'f5f5ull; //cyan + case 8: return 0xfcfc'5555'5454ull; //medium red + case 9: return 0xffff'7979'7878ull; //light red + case 10: return 0xd4d4'c1c1'5454ull; //dark yellow + case 11: return 0xe6e6'cece'8080ull; //light yellow + case 12: return 0x2121'b0b0'3b3bull; //dark green + case 13: return 0xc9c9'5b5b'babaull; //magenta + case 14: return 0xcccc'cccc'ccccull; //gray + case 15: return 0xffff'ffff'ffffull; //white + } + unreachable; +} diff --git a/ares/pencil2/vdp/serialization.cpp b/ares/pencil2/vdp/serialization.cpp new file mode 100644 index 0000000000..c71b7df001 --- /dev/null +++ b/ares/pencil2/vdp/serialization.cpp @@ -0,0 +1,4 @@ +auto VDP::serialize(serializer& s) -> void { + TMS9918::serialize(s); + Thread::serialize(s); +} diff --git a/ares/pencil2/vdp/vdp.cpp b/ares/pencil2/vdp/vdp.cpp new file mode 100644 index 0000000000..4e6c0b2ebe --- /dev/null +++ b/ares/pencil2/vdp/vdp.cpp @@ -0,0 +1,66 @@ +#include + +namespace ares::Pencil2 { + +VDP vdp; +#include "color.cpp" +#include "serialization.cpp" + +auto VDP::load(Node::Object parent) -> void { + vram.allocate(16_KiB, 0x00); + + node = parent->append("VDP"); + + screen = node->append("Screen", 284, 243); + screen->colors(1 << 4, {&VDP::color, this}); + screen->setSize(284, 243); + screen->setScale(1.0, 1.0); + screen->setAspect(8.0, 7.0); + screen->setViewport(0, 0, 284, 243); + screen->refreshRateHint(system.crystalFrequency() / 2.0, 228, 262); + + TMS9918::load(screen); +} + +auto VDP::unload() -> void { + TMS9918::unload(); + screen->quit(); + node->remove(screen); + screen.reset(); + node.reset(); +} + +auto VDP::step(u32 clocks) -> void { + Thread::step(clocks); + Thread::synchronize(cpu); +} + +auto VDP::irq(bool line) -> void { + cpu.setNMI(line); +} + +auto VDP::frame() -> void { + if(screen->overscan()) { + screen->setSize(284, 243); + screen->setViewport(0, 0, 284, 243); + } else { + int x = 16; + int y = 16; + int width = 284 - 32; + int height = 243 - 32; + + screen->setSize(width, height); + screen->setViewport(x, y, width, height); + } + + screen->frame(); + scheduler.exit(Event::Frame); +} + +auto VDP::power() -> void { + TMS9918::power(); + Thread::create(system.crystalFrequency() / 2.0, [&] { main(); }); + screen->power(); +} + +} diff --git a/ares/pencil2/vdp/vdp.hpp b/ares/pencil2/vdp/vdp.hpp new file mode 100644 index 0000000000..af5a7e4321 --- /dev/null +++ b/ares/pencil2/vdp/vdp.hpp @@ -0,0 +1,21 @@ +struct VDP : TMS9918, Thread { + Node::Object node; + Node::Video::Screen screen; + + //vdp.cpp + auto load(Node::Object) -> void; + auto unload() -> void; + + auto step(u32 clocks) -> void override; + auto irq(bool line) -> void override; + auto frame() -> void override; + auto power() -> void; + + //color.cpp + auto color(n32) -> n64; + + //serialization.cpp + auto serialize(serializer&) -> void; +}; + +extern VDP vdp; diff --git a/desktop-ui/GNUmakefile b/desktop-ui/GNUmakefile index f5237c5930..cf2f044c86 100644 --- a/desktop-ui/GNUmakefile +++ b/desktop-ui/GNUmakefile @@ -55,7 +55,7 @@ hiro.resource := resource/ares.rc include $(hiro.path)/GNUmakefile profile := performance -cores := a26 fc sfc n64 sg ms md ps1 pce ng msx cv myvision gb gba ws ngp spec +cores := a26 fc sfc n64 sg ms md ps1 pce ng msx cv pencil2 myvision gb gba ws ngp spec #cores += saturn ares.path := ../ares diff --git a/desktop-ui/emulator/emulators.cpp b/desktop-ui/emulator/emulators.cpp index e1bb072f36..a1de2588c9 100644 --- a/desktop-ui/emulator/emulators.cpp +++ b/desktop-ui/emulator/emulators.cpp @@ -13,6 +13,13 @@ namespace ares::Atari2600 { #include "colecovision.cpp" #endif +#ifdef CORE_PENCIL2 + namespace ares::Pencil2 { + auto load(Node::System& node, string name) -> bool; + } + #include "pencil2.cpp" +#endif + #ifdef CORE_MYVISION namespace ares::MyVision { auto load(Node::System& node, string name) -> bool; @@ -174,6 +181,10 @@ auto Emulator::construct() -> void { emulators.append(new ColecoVision); #endif + #ifdef CORE_PENCIL2 + emulators.append(new Pencil2); + #endif + #ifdef CORE_MYVISION emulators.append(new MyVision); #endif diff --git a/desktop-ui/emulator/pencil2.cpp b/desktop-ui/emulator/pencil2.cpp new file mode 100644 index 0000000000..a661e7deb7 --- /dev/null +++ b/desktop-ui/emulator/pencil2.cpp @@ -0,0 +1,81 @@ +struct Pencil2 : Emulator { + Pencil2(); + auto load() -> bool override; + auto save() -> bool override; + auto pak(ares::Node::Object) -> shared_pointer override; +}; + +Pencil2::Pencil2() { + manufacturer = "Hanimex"; + name = "Pencil 2"; + + firmware.append({"BIOS", "World", "b03f77561ee75d4ce17752b9df8522389bfe4a0b1ec608cddfd4ea3af193580d"}); + + for(auto id : range(2)) { + InputPort port{string{"Controller Port ", 1 + id}}; + + { InputDevice device{"Gamepad"}; + device.digital("Up", virtualPorts[id].pad.up); + device.digital("Down", virtualPorts[id].pad.down); + device.digital("Left", virtualPorts[id].pad.left); + device.digital("Right", virtualPorts[id].pad.right); + device.digital("L", virtualPorts[id].pad.south); + device.digital("R", virtualPorts[id].pad.east); + device.digital("1", virtualPorts[id].pad.west); + device.digital("2", virtualPorts[id].pad.north); + device.digital("3", virtualPorts[id].pad.l_bumper); + device.digital("4", virtualPorts[id].pad.l_trigger); + device.digital("5", virtualPorts[id].pad.r_bumper); + device.digital("6", virtualPorts[id].pad.r_trigger); + device.digital("7", virtualPorts[id].pad.lstick_click); + device.digital("8", virtualPorts[id].pad.rstick_click); + device.digital("9", virtualPorts[id].pad.rstick_down); + device.digital("0", virtualPorts[id].pad.rstick_right); + device.digital("*", virtualPorts[id].pad.select); + device.digital("#", virtualPorts[id].pad.start); + port.append(device); } + + ports.append(port); + } +} + +auto Pencil2::load() -> bool { + game = mia::Medium::create("Pencil 2"); + if(!game->load(Emulator::load(game, configuration.game))) return false; + + system = mia::System::create("Pencil 2"); + if(!system->load(firmware[0].location)) return errorFirmware(firmware[0]), false; + + auto region = Emulator::region(); + if(!ares::Pencil2::load(root, {"[Hanimex] Pencil 2 (", region, ")"})) return false; + + if(auto port = root->find("Cartridge Slot")) { + port->allocate(); + port->connect(); + } + + if(auto port = root->find("Controller Port 1")) { + port->allocate("Gamepad"); + port->connect(); + } + + if(auto port = root->find("Controller Port 2")) { + port->allocate("Gamepad"); + port->connect(); + } + + return true; +} + +auto Pencil2::save() -> bool { + root->save(); + system->save(system->location); + game->save(game->location); + return true; +} + +auto Pencil2::pak(ares::Node::Object node) -> shared_pointer { + if(node->name() == "Pencil 2") return system->pak; + if(node->name() == "Pencil 2 Cartridge") return game->pak; + return {}; +} diff --git a/mia/medium/medium.cpp b/mia/medium/medium.cpp index ac47f4d9fa..965d47aeed 100644 --- a/mia/medium/medium.cpp +++ b/mia/medium/medium.cpp @@ -4,6 +4,7 @@ namespace Media { #include "arcade.cpp" #include "atari-2600.cpp" #include "colecovision.cpp" + #include "pencil2.cpp" #include "myvision.cpp" #include "famicom.cpp" #include "famicom-disk-system.cpp" @@ -47,6 +48,7 @@ auto Medium::create(string name) -> shared_pointer { if(name == "Arcade") return new Media::Arcade; if(name == "Atari 2600") return new Media::Atari2600; if(name == "ColecoVision") return new Media::ColecoVision; + if(name == "Pencil 2") return new Media::Pencil2; if(name == "MyVision") return new Media::MyVision; if(name == "Famicom") return new Media::Famicom; if(name == "Famicom Disk System") return new Media::FamicomDiskSystem; diff --git a/mia/medium/pencil2.cpp b/mia/medium/pencil2.cpp new file mode 100644 index 0000000000..1d07f53d23 --- /dev/null +++ b/mia/medium/pencil2.cpp @@ -0,0 +1,55 @@ +struct Pencil2 : Cartridge { + auto name() -> string override { return "Pencil 2"; } + auto extensions() -> vector override { return {"rom"}; } + auto load(string location) -> bool override; + auto save(string location) -> bool override; + auto analyze(vector& rom) -> string; +}; + +auto Pencil2::load(string location) -> bool { + vector rom; + if(directory::exists(location)) { + append(rom, {location, "program.rom"}); + } else if(file::exists(location)) { + rom = Cartridge::read(location); + } + if(!rom) return false; + + this->location = location; + this->manifest = analyze(rom); + auto document = BML::unserialize(manifest); + if(!document) return false; + + pak = new vfs::directory; + pak->setAttribute("board", document["game/board" ].string()); + pak->setAttribute("title", document["game/title"].string()); + pak->setAttribute("region", document["game/region"].string()); + pak->append("manifest.bml", manifest); + pak->append("program.rom", rom); + + return true; +} + +auto Pencil2::save(string location) -> bool { + auto document = BML::unserialize(manifest); + + return true; +} + +auto Pencil2::analyze(vector& rom) -> string { + string hash = Hash::SHA256(rom).digest(); + string board = "pencil2"; + + string s; + s += "game\n"; + s +={" name: ", Medium::name(location), "\n"}; + s +={" title: ", Medium::name(location), "\n"}; + s += " region: PAL\n"; //database required to detect region + s +={" sha256: ", hash, "\n"}; + s +={" board: ", board, "\n"}; + s += " memory\n"; + s += " type: ROM\n"; + s +={" size: 0x", hex(rom.size()), "\n"}; + s += " content: Program\n"; + return s; +} diff --git a/mia/system/pencil2.cpp b/mia/system/pencil2.cpp new file mode 100644 index 0000000000..a1de8c7de6 --- /dev/null +++ b/mia/system/pencil2.cpp @@ -0,0 +1,19 @@ +struct Pencil2 : System { + auto name() -> string override { return "Pencil 2"; } + auto load(string location) -> bool override; + auto save(string location) -> bool override; +}; + +auto Pencil2::load(string location) -> bool { + auto bios = Pak::read(location); + if(!bios) return false; + + this->location = locate(); + pak = new vfs::directory; + pak->append("mt.u4", bios); + return true; +} + +auto Pencil2::save(string location) -> bool { + return true; +} diff --git a/mia/system/system.cpp b/mia/system/system.cpp index 345af3e5d6..369ed208b5 100644 --- a/mia/system/system.cpp +++ b/mia/system/system.cpp @@ -2,6 +2,7 @@ namespace Systems { #include "arcade.cpp" #include "atari-2600.cpp" #include "colecovision.cpp" + #include "pencil2.cpp" #include "myvision.cpp" #include "famicom.cpp" #include "game-boy.cpp" @@ -39,6 +40,7 @@ auto System::create(string name) -> shared_pointer { if(name == "Arcade") return new Systems::Arcade; if(name == "Atari 2600") return new Systems::Atari2600; if(name == "ColecoVision") return new Systems::ColecoVision; + if(name == "Pencil 2") return new Systems::Pencil2; if(name == "MyVision") return new Systems::MyVision; if(name == "Famicom") return new Systems::Famicom; if(name == "Game Boy") return new Systems::GameBoy;