Skip to content

Added support for NES controllers.#38

Merged
d3xMachina merged 8 commits into
d3xMachina:masterfrom
ConnorS-P:add-support-for-nes-controller-d3xMachina
May 23, 2025
Merged

Added support for NES controllers.#38
d3xMachina merged 8 commits into
d3xMachina:masterfrom
ConnorS-P:add-support-for-nes-controller-d3xMachina

Conversation

@ConnorS-P

Copy link
Copy Markdown

Added support for nes controllers.

@ConnorS-P

Copy link
Copy Markdown
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

@d3xMachina d3xMachina left a comment

Copy link
Copy Markdown
Owner

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.

Comment thread BetterJoy/Joycon.cs Outdated
{
Span<byte> resp = stackalloc byte[ReportLength];

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

Copy link
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread BetterJoy/Joycon.cs Outdated
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
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread BetterJoy/Joycon.cs Outdated
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
Copy Markdown
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
Copy Markdown
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.

Comment thread BetterJoy/Joycon.cs Outdated
break;
}

if (resp[15] == 0x02) {

Copy link
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread BetterJoy/Joycon.cs Outdated
}

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

Copy link
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed.

Comment thread BetterJoy/Joycon.cs Outdated
}

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
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed.

Comment thread BetterJoy/Joycon.cs Outdated
JoyconLeft,
JoyconRight,
SNES,
NES,

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Author

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

@ConnorS-P

Copy link
Copy Markdown
Author

Hold on, I think I broke something.

@ConnorS-P

Copy link
Copy Markdown
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

@d3xMachina d3xMachina left a comment

Copy link
Copy Markdown
Owner

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 thread BetterJoy/Joycon.cs Outdated
Comment on lines +488 to +492
//Make sure we're not actually a nes controller
if (Type == ControllerType.JoyconRight)
{
CheckIfRightIsNes();
}

Copy link
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread BetterJoy/Joycon.cs Outdated
{
var respLength = SubcommandCheck(0x02, [], response, false);

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

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Comment thread BetterJoy/Joycon.cs Outdated
SetNFCIR(false);
SetReportMode(ReportMode.StandardFull);

Copy link
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed.

Comment thread ViGEm.NET

Copy link
Copy Markdown
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
Copy Markdown
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.

Comment thread BetterJoy/Joycon.cs
return true;
}

private void CheckIfRightIsNes()

Copy link
Copy Markdown
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
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed

Comment thread BetterJoy/Joycon.cs Outdated

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

d3xMachina commented May 22, 2025

Copy link
Copy Markdown
Owner

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
Copy Markdown
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
Copy Markdown
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