Skip to content

Commit

Permalink
ms: Add support for the Mega Mouse
Browse files Browse the repository at this point in the history
This pull request adds support for the Sega Mega Mouse. As the name implies,
this mouse was released for the MegaDrive, but as it is the case for 3/6 Button
controllers, the Mega Mouse can be safely connected to a Master System console and
even used, provided that the game software is programmed accordingly.

To my knowledge there are no official games on the SMS with mouse support, but there
is one demo I released (SMS-A-Sketch) which supports the Mega Mouse, and I have
an unreleased game that also supports it. I think it would be great to have ares
support the Mega Mouse on SMS as the only alternative at this point is to use
real hardware.
  • Loading branch information
raphnet authored and LukeUsher committed Sep 8, 2022
1 parent 4c3c07e commit e2b96c8
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 1 deletion.
1 change: 1 addition & 0 deletions ares/ms/controller/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ namespace ares::MasterSystem {
#include "sports-pad/sports-pad.cpp"
#include "md-control-pad/md-control-pad.cpp"
#include "md-fighting-pad/md-fighting-pad.cpp"
#include "mega-mouse/mega-mouse.cpp"

}
1 change: 1 addition & 0 deletions ares/ms/controller/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ struct Controller {
#include "sports-pad/sports-pad.hpp"
#include "md-control-pad/md-control-pad.hpp"
#include "md-fighting-pad/md-fighting-pad.hpp"
#include "mega-mouse/mega-mouse.hpp"
145 changes: 145 additions & 0 deletions ares/ms/controller/mega-mouse/mega-mouse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
MegaMouse::MegaMouse(Node::Port parent) {
node = parent->append<Node::Peripheral>("Mega Mouse");

x = node->append<Node::Input::Axis>("X");
y = node->append<Node::Input::Axis>("Y");
left = node->append<Node::Input::Button>("Left");
right = node->append<Node::Input::Button>("Right");
middle = node->append<Node::Input::Button>("Middle");
start = node->append<Node::Input::Button>("Start");

status[0] = 0x0; // TH high
status[1] = 0xB; // TH low
status[2] = 0xF; // 1st nibble
status[3] = 0xF; // 2nd
// status[4..9] // 3rd .. 8th nibbles

// Is is said that some games do not do the hanshake correctly
// and rely heavily on timing, so we try to mimick the real
// timing closely.

// One game has a programming error where it polls for TL low
// instead of TL high for one of the nibbles so it falls rights
// through and works by chance. With a 1MHz clock here, vertical
// cursor motion was intermittent. So 2MHz is used.
double timerfreq = 2'000'000;

// time between falling edge of clock (TR) and
// handshake on TL.
//
// For first nibble, the measured time is min. 12us, max 34us
// For folling nibbles are available in 12-14 us.
t_handshake = 14 * timerfreq / 1000000;

// data changes a bit before the TL handshake occurs,
// precisely 4.8us before. But for some games, 4.8 does not
// work correclty and it helps to have the data update sooner. So
// 10 is used here.
t_data = 10 * timerfreq / 1000000;

Thread::create(timerfreq, {&MegaMouse::main, this});
Thread::synchronize(cpu);
}

MegaMouse::~MegaMouse() {
Thread::destroy();
}

auto MegaMouse::main() -> void {
// process clocking after building the data to
// cause at least some lag. In real life, it takes
// about 10us to the mouse to react and change TL
// to reflect TR...
if (timeout)
{
timeout--;

if (timeout == t_data) {
index++;
if (index > 9) {
index = 1;
}
}
if (timeout == 0) {
tl = tr;
}
}
Thread::step(1);
Thread::synchronize(cpu);
}

auto MegaMouse::read() -> n7 {
n7 data;

if (th) {
// When TH is high, the mouse drives a constant on the data pins.
data.bit(0,3) = status[0];
}
else {
// When TH is low, output is clocked
data.bit(0,3) = status[index];
}

data.bit(4) = tl;
data.bit(5) = tr;
data.bit(6) = 1;

return data;
}

auto MegaMouse::write(n4 data) -> void {
// Falling TH
if (!data.bit(3) && th) {
// When TH falls low, make sure the second nibble is driven. The
// index is otherwise controlled by clocking on TR.
index = 1;

platform->input(x);
platform->input(y);
platform->input(left);
platform->input(right);
platform->input(middle);
platform->input(start);

// Button byte
status[4] = 0;
status[5].bit(0) = left->value();
status[5].bit(1) = right->value();
status[5].bit(2) = middle->value();
status[5].bit(3) = start->value();

i16 ax = x->value();
i16 ay = -y->value();

if (ax > maxspeed) {
ax = maxspeed;
} else if (ax < -maxspeed) {
ax = -maxspeed;
}

if (ay > maxspeed) {
ay = maxspeed;
} else if (ay < -maxspeed) {
ay = -maxspeed;
}

// X byte
status[4].bit(0) = ax.bit(8);
status[6] = ax.bit(4,7);
status[7] = ax.bit(0,3);

// Y byte
status[4].bit(1) = ay.bit(8);
status[8] = ay.bit(4,7);
status[9] = ay.bit(0,3);
}

// Clock in is TR. The mouse reacts and ACKs by updating TL,
// but not instantly.
tr = data.bit(2);
if (tr != tl) {
timeout = t_handshake;
}

th = data.bit(3);
}
27 changes: 27 additions & 0 deletions ares/ms/controller/mega-mouse/mega-mouse.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
struct MegaMouse : Controller, Thread {
Node::Input::Axis x;
Node::Input::Axis y;
Node::Input::Button left;
Node::Input::Button right;
Node::Input::Button middle;
Node::Input::Button start;

MegaMouse(Node::Port);
~MegaMouse();

auto main() -> void;
auto read() -> n7 override;
auto write(n4 data) -> void override;

private:
n1 th = 1;
n1 tr = 1;
n1 tl = 1;
n8 index = 0;
n4 status[10];
s16 maxspeed = 255;
n32 timeout = 0;

n32 t_data;
n32 t_handshake;
};
3 changes: 2 additions & 1 deletion ares/ms/controller/port.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ auto ControllerPort::load(Node::Object parent) -> void {
port->setType("Controller");
port->setHotSwappable(true);
port->setAllocate([&](auto name) { return allocate(name); });
port->setSupported({"Gamepad", "Light Phaser", "Paddle", "Sports Pad", "MD Control Pad", "MD Fighting Pad"});
port->setSupported({"Gamepad", "Light Phaser", "Paddle", "Sports Pad", "MD Control Pad", "MD Fighting Pad", "Mega Mouse"});
}

auto ControllerPort::unload() -> void {
Expand All @@ -25,6 +25,7 @@ auto ControllerPort::allocate(string name) -> Node::Peripheral {
if(name == "Sports Pad") device = new SportsPad(port);
if(name == "MD Control Pad") device = new MdControlPad(port);
if(name == "MD Fighting Pad") device = new MdFightingPad(port);
if(name == "Mega Mouse") device = new MegaMouse(port);
if(device) return device->node;
return {};
}
Expand Down
9 changes: 9 additions & 0 deletions desktop-ui/emulator/master-system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ MasterSystem::MasterSystem() {
device.digital("Start", virtualPorts[id].pad.start);
port.append(device); }

{ InputDevice device{"Mega Mouse"};
device.relative("X", virtualPorts[id].mouse.x);
device.relative("Y", virtualPorts[id].mouse.y);
device.digital ("Left", virtualPorts[id].mouse.left);
device.digital ("Right", virtualPorts[id].mouse.right);
device.digital ("Middle", virtualPorts[id].mouse.middle);
device.digital ("Start", virtualPorts[id].mouse.extra);
port.append(device); }

ports.append(port);
}
}
Expand Down

0 comments on commit e2b96c8

Please sign in to comment.