Skip to content

Commit

Permalink
Merge pull request #48 from dmadison/nes-thirdparty
Browse files Browse the repository at this point in the history
NES Third Party Controller Tweaks
  • Loading branch information
dmadison authored Jan 5, 2020
2 parents 81f47fd + 856db2e commit 3ed4c2d
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 37 deletions.
6 changes: 3 additions & 3 deletions examples/NES Mini/NES_DebugPrint/NES_DebugPrint.ino
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ void setup() {
delay(1000);
}

if (nes.isKnockoff()) { // Uh oh, looks like your controller isn't genuine?
nes.setRequestSize(8); // Requires 8 or more bytes for knockoff controllers
if (nes.isThirdParty()) { // Uh oh, looks like your controller isn't genuine?
nes.setRequestSize(8); // Requires 8 or more bytes for third party controllers
}
}

void loop() {
boolean success = nes.update(); // Get new data from the controller
nes.fixKnockoffData(); // If knockoff, fix the data!

if (success == true) { // We've got data!
nes.fixThirdPartyData(); // If third party controller, fix the data!
nes.printDebug(); // Print all of the values!
}
else { // Data is bad :(
Expand Down
8 changes: 5 additions & 3 deletions examples/NES Mini/NES_Demo/NES_Demo.ino
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,24 @@ void setup() {
delay(1000);
}

if (nes.isKnockoff()) { // Uh oh, looks like your controller isn't genuine?
nes.setRequestSize(8); // Requires 8 or more bytes for knockoff controllers
if (nes.isThirdParty()) { // Uh oh, looks like your controller isn't genuine?
nes.setRequestSize(8); // Requires 8 or more bytes for third party controllers
}
}

void loop() {
Serial.println("----- NES Mini Controller Demo -----"); // Making things easier to read

boolean success = nes.update(); // Get new data from the controller
nes.fixKnockoffData(); // If knockoff, fix the data!

if (!success) { // Ruh roh
Serial.println("Controller disconnected!");
delay(1000);
}
else {
// If a third party controller is detected, fix the data!
nes.fixThirdPartyData();

// Read the DPAD (Up/Down/Left/Right)
boolean padUp = nes.dpadUp();

Expand Down
9 changes: 5 additions & 4 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ reset KEYWORD2

getControllerType KEYWORD2
getControlData KEYWORD2
getRequestSize KEYWORD2

setRequestSize KEYWORD2

Expand Down Expand Up @@ -186,10 +187,10 @@ getNumTurntables KEYWORD2

## NES Mini Controller
# (Covered by the Classic Controller keywords)
isNESKnockoff KEYWORD2
fixNESKnockoffData KEYWORD2
isKnockoff KEYWORD2
fixKnockoffData KEYWORD2
isNESThirdParty KEYWORD2
fixNESThirdPartyData KEYWORD2
isThirdParty KEYWORD2
fixThirdPartyData KEYWORD2

## SNES Mini Controller
# (Covered by the Classic Controller keywords)
Expand Down
67 changes: 46 additions & 21 deletions src/controllers/ClassicController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,45 +183,70 @@ void ClassicController_Shared::printDebug(Print& output) const {

// ######### Mini Controller Support #########

boolean ClassicController_Shared::fixNESKnockoffData() {
// Public-facing function to check and "correct" data if using a knockoff
boolean ClassicController_Shared::fixNESThirdPartyData(boolean force) {
// Public-facing function to check and "correct" data if using a third party controller
// Returns 'true' if data was modified
if(isNESKnockoff()) {
manipulateKnockoffData();
if((isNESThirdParty() && getRequestSize() >= 8) || force == true) { // 8 bytes is the minimum for valid data
manipulateThirdPartyData();
return true;
}
return false;
}

boolean ClassicController_Shared::isNESKnockoff() const {
// The NES knockoffs I've come across seem to display the same unchanging pattern
// for the first six control bytes:
boolean ClassicController_Shared::isNESThirdParty() const {
// The third party NES controllers I've come across seem to display the same
// unchanging pattern for the first six control bytes:
//
// 0x81, 0x81, 0x81, 0x81, 0x00, 0x00
//
// This is mostly garbage data that doesn't line up with the Classic Controller
// at all. Using the library's debug output, here is what it translates to:
//
// <^v> | -H+ | ABXY L : (1, 1) R : (21, 1) | LT : 4X RT : 1X Z : LR
// <^v> | -H+ | ABXY L:( 1, 1) R:(21, 1) | LT: 4X RT: 1X Z:LR
//
// You'll notice a few things. ALL possible buttons are pressed. The left analog
// stick is completely down and to the left, while the right analog stick is down
// and to the right. On the back, both trigger "fully depressed" buttons are
// down, and yet the analog trigger values are very low. In short, this is a
// difficult if not impossible state for a normal Classic Controller to be in.
// Because of that, we can reasonably assume that if the bytes match this then the
// connected controller is an NES Knockoff, and can be treated accordingly.

return getControlData(0) == 0x81 && // RX 4:3, LX
getControlData(1) == 0x81 && // RX 2:1, LY
getControlData(2) == 0x81 && // RX 0, LT 4:3, RY
getControlData(3) == 0x81 && // LT 2:0, RT
getControlData(4) == 0x00 && // Button packet 1 (all pressed)
getControlData(5) == 0x00; // Button packet 2 (all pressed)
// connected controller is a third party NES controller, and can be treated accordingly.
//
// -------
//
// Issue #46 states that the 8BitDo Retro Receiver, another 3rd party controller,
// has a different set of data for the first six bytes:
//
// 0x84, 0x86, 0x86, 0x86, 0x00, 0x00
//
// Which, again using the library's debug output, evaluates to:
//
// <^v> | -H+ | ABXY L:( 4, 6) R:(21, 6) | LT: 4X RT: 6X Z:LR
//
// This has been added in as a second conditional check.
//
// If any other 3rd party NES controllers have a different signature than these two,
// I'm going to modify this signature check to only check the last two bytes (4 & 5) as 0.

// 3rd Party Data Signature, Typical
return (getControlData(0) == 0x81 && // RX 4:3, LX
getControlData(1) == 0x81 && // RX 2:1, LY
getControlData(2) == 0x81 && // RX 0, LT 4:3, RY
getControlData(3) == 0x81 && // LT 2:0, RT
getControlData(4) == 0x00 && // Button packet 1 (all pressed)
getControlData(5) == 0x00) // Button packet 2 (all pressed)
||
// 8BitDo Retro Receiver Signature
(getControlData(0) == 0x84 && // RX 4:3, LX
getControlData(1) == 0x86 && // RX 2:1, LY
getControlData(2) == 0x86 && // RX 0, LT 4:3, RY
getControlData(3) == 0x86 && // LT 2:0, RT
getControlData(4) == 0x00 && // Button packet 1 (all pressed)
getControlData(5) == 0x00); // Button packet 2 (all pressed)
}

void ClassicController_Shared::manipulateKnockoffData() {
// The data returned by knockoff NES controllers for the missing control surfaces
void ClassicController_Shared::manipulateThirdPartyData() {
// The data returned by third party NES controllers for the missing control surfaces
// (joysticks, triggers, etc.) is "corrupted", meaning that it doesn't align with
// what you would expect a Classic Controller at rest to display.
//
Expand All @@ -237,9 +262,9 @@ void ClassicController_Shared::manipulateKnockoffData() {
// The left joystick is centered at 31/31, the right joystick is centered at 15/15,
// the analog trigger values are 0, and all buttons are released.
//
// For the NES Mini knockoffs, only the two data packets containing the button booleans
// matter. Bytes 0, 1, 2, and 3 (joysticks and triggers) are replaced entirely. Bytes
// 4 and 5 are overridden by the values in 6 and 7.
// For the third party NES Mini controllers, only the two data packets containing the button
// booleans matter. Bytes 0, 1, 2, and 3 (joysticks and triggers) are replaced entirely.
// Bytes 4 and 5 are overridden by the values in 6 and 7.

setControlData(0, 0x5F);
setControlData(1, 0xDF);
Expand Down
12 changes: 6 additions & 6 deletions src/controllers/ClassicController.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,21 @@ namespace NintendoExtensionCtrl {

void printDebug(Print& output = NXC_SERIAL_DEFAULT) const;

// NES Knockoff Support
// 3rd Party NES Controller Support
public:
boolean isNESKnockoff() const;
boolean fixNESKnockoffData();
boolean isNESThirdParty() const;
boolean fixNESThirdPartyData(boolean force = false);

protected:
void manipulateKnockoffData();
void manipulateThirdPartyData();
};

class NESMiniController_Shared : public ClassicController_Shared {
public:
using ClassicController_Shared::ClassicController_Shared;

boolean isKnockoff() const { return isNESKnockoff(); }
boolean fixKnockoffData() { return fixNESKnockoffData(); }
boolean isThirdParty() const { return isNESThirdParty(); }
boolean fixThirdPartyData(boolean force = false) { return fixNESThirdPartyData(force); }

void printDebug(Print& output = NXC_SERIAL_DEFAULT) const;
};
Expand Down
4 changes: 4 additions & 0 deletions src/internal/ExtensionController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ ExtensionController::ExtensionData & ExtensionController::getExtensionData() con
return data;
}

size_t ExtensionController::getRequestSize() const {
return requestSize;
}

void ExtensionController::setRequestSize(size_t r) {
if (r >= MinRequestSize && r <= MaxRequestSize) {
requestSize = (uint8_t) r;
Expand Down
1 change: 1 addition & 0 deletions src/internal/ExtensionController.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class ExtensionController {
ExtensionType getControllerType() const;
uint8_t getControlData(uint8_t controlIndex) const;
ExtensionData & getExtensionData() const;
size_t getRequestSize() const;

void setRequestSize(size_t size = MinRequestSize);

Expand Down

0 comments on commit 3ed4c2d

Please sign in to comment.