Skip to content

Conversation

@ConnorS-P
Copy link

Added support for nes controllers.

@ConnorS-P
Copy link
Author

Based on Davidobot#1220

@ConnorS-P ConnorS-P changed the title add-support-for-nes-controller-d3xMachina Added support for nes contr… Added support for NES controllers. May 20, 2025
Copy link
Owner

@d3xMachina d3xMachina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution. Please look at the review.

{
Span<byte> resp = stackalloc byte[ReportLength];

for (int i = 0; i < 100; ++i) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100 is too much, reduce this to 5. (same as ReadSPICheck) and keep the style as

for ()
{

}

instead of

for () {

}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Span<byte> resp = stackalloc byte[ReportLength];

for (int i = 0; i < 100; ++i) {
int respLength = SubcommandCheck(0x02, Array.Empty<byte>(), resp, false, 200);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the ", 200" parameter and use a collection expression [] instead of Array.Empty<byte>().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

for (int i = 0; i < 100; ++i) {
int respLength = SubcommandCheck(0x02, Array.Empty<byte>(), resp, false, 200);

if (respLength >= 15 && resp[15] == 0x03) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

15 is incorrect, it's the index to the first byte of the firmware version. You should use 17. (information here)
You might want to change 0x03 too unless it's really the value that is used for the NES controller. (I don't have one to test). 1 is left joycon, 2 is right joycon and 3 is pro controller.

You should use > and not >= since the index starts at 0.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! I think I misread the output and was for some reason thinking it was replying as a pro controller. I was having a heck of a time getting a response on the original fork (hence the massive attempt numbers) so I also may have read the wrong packet at some point and it just happen to have worked due to firmware versioning.

break;
}

if (resp[15] == 0x02) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any other value than the one for the NES should break the loop. So the content of the for should be changed to :

if (respLength > 17)
{
   if (resp[17] == 0x03) // you should check that the 0x03 value is correct as stated in my previous comment
   {
      Type = ControllerType.NES;
   }

   break;
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

}

//Make sure we're not actually a nes controller
if (this.Type == ControllerType.JoyconRight)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

}

private int SubcommandCheck(byte sc, ReadOnlySpan<byte> bufParameters, Span<byte> response, bool print = true)
private int SubcommandCheck(byte sc, ReadOnlySpan<byte> bufParameters, Span<byte> response, bool print = true, int retries = 10)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this change , int retries = 10

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

JoyconLeft,
JoyconRight,
SNES,
NES,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add NES at the end of the enum or it will break the saved datas for third party controller window.
Also for the third party controller feature to work properly, you need to add to the switch in the TypeToProdId function :

case 6:
    return ProductNES;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, and made the numerics explicit in the enum to reduce brittleness.

@ConnorS-P
Copy link
Author

Thanks for the review! I'll fix these and get back to you.

@ConnorS-P
Copy link
Author

Hold on, I think I broke something.

@ConnorS-P
Copy link
Author

Ok, that should have fixed it. Turns out the NES controllers report differently (left or right) as to what they are.

@ConnorS-P ConnorS-P requested a review from d3xMachina May 21, 2025 05:14
Copy link
Owner

@d3xMachina d3xMachina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more changes and we're good.

Comment on lines 488 to 492
//Make sure we're not actually a nes controller
if (Type == ControllerType.JoyconRight)
{
CheckIfRightIsNes();
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this below just after SetReportMode(ReportMode.SimpleHID, false);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

{
var respLength = SubcommandCheck(0x02, [], response, false);

if (respLength >= 20 && response[0] == 0x21 && response[14] == 0x02)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove && response[0] == 0x21 && response[14] == 0x02 as it is already checked by SubcommandCheck. Keeping respLength >= 20 is fine.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is checked, it is not checked in a way that is useful for us here.

private int SubcommandCheck(byte sc, ReadOnlySpan<byte> bufParameters, Span<byte> response, bool print = true)
    {
        int length = Subcommand(sc, bufParameters, print);
        if (length <= 0)
        {
            DebugPrint($"Subcommand write error: {ErrorMessage()}", DebugType.Comms);
            return length;
        }

        int tries = 0;
        bool responseFound;
        do
        {
            length = Read(response, 100); // don't set the timeout lower than 100 or might not always work
            responseFound = length >= 20 && response[0] == 0x21 && response[14] == sc;
            
            if (length < 0)
            {
                DebugPrint($"Subcommand read error: {ErrorMessage()}", DebugType.Comms);
            }

            tries++;
        } while (tries < 10 && !responseFound && length >= 0);

        if (!responseFound)
        {
            DebugPrint("No response.", DebugType.Comms);
            return length;
        }

        if (print)
        {
            PrintArray<byte>(
                response,
                DebugType.Comms,
                length - 1,
                1,
                $"Response ID {response[0]:X2}." + " Data: {0:S}"
            );
        }

        return length;
    }

The while loop continues until a response is found OR until we run out of tries. In the event we run out of tries, the function just gives us the last read item, regardless of if it passed that check or not. We check it again here so we can run another iteration of the for loop if necessary.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, that was a bug I fixed yesterday in e2d603e, now it returns 0 properly if no response was found so you can remove it.

SetNFCIR(false);
SetReportMode(ReportMode.StandardFull);

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unwanted white spaces.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those files shouldn't be changed. It was changed by your merge commit.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this is my first time working with submodules. I think I fixed it.

return true;
}

private void CheckIfRightIsNes()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should throw a DeviceComFailedException if it failed so it tries to reconnect the controller like so :

private void CheckIfRightIsNes()
{
    Span<byte> response = stackalloc byte[ReportLength];
    var ok = false;

    for (var i = 0; i < 5; ++i)
    {
        var respLength = SubcommandCheck(0x02, [], response, false);
        if (respLength >= 20)
        {
            // NES controllers share the hardware ID of the right joycon, but respond here differently.
            if (response[17] == 0x0A || response[17] == 0x09)
            {
                Type = ControllerType.NES;
            }

            ok = true;
            break;
        }
    }

    if (!ok)
    {
        throw new DeviceComFailedException("reset device info");
    }
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


if (respLength >= 20 && response[0] == 0x21 && response[14] == 0x02)
{
if (response[17] == 0x0A || response[17] == 0x09) //NES controllers share the Right hardware ID of the right joycon, but respond here differently.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add to the comment which value correspond to left and right for 0x0A and 0x09

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@ConnorS-P
Copy link
Author

I got all the stuff that needed fixes and replied to your one comment regarding the duplicated message check. Let me know if that is sufficient or if you need anything tweaked. 👍

@ConnorS-P ConnorS-P requested a review from d3xMachina May 22, 2025 02:00
@d3xMachina
Copy link
Owner

d3xMachina commented May 22, 2025

Thank you, only the duplicate check in CheckIfRightIsNes needs to be removed since it was a bug that was fixed very recently and I can merge.

@ConnorS-P
Copy link
Author

Ok, I removed the redundant check, now we just check for a valid response via looking if the response length is greater than 0.

@d3xMachina d3xMachina merged commit 87dbaba into d3xMachina:master May 23, 2025
@d3xMachina
Copy link
Owner

Thanks, merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants