rust3ds / ctru-rs

Rust wrapper for libctru
https://rust3ds.github.io/ctru-rs/
Other
114 stars 17 forks source link

Fix Circle Pad Pro example on New 3DS #151

Open AzureMarker opened 6 months ago

AzureMarker commented 6 months ago

While implementing the CPP support (ir:USER service) in https://github.com/rust3ds/ctru-rs/pull/86, we found that the New 3DS has some issues getting the demo to work.

The ir:USER and ir:rst services can't both be enabled, so we took some steps to prevent ir:rst from starting, but maybe more needs to be done here?

The CPP has been seen to connect with the New 3DS, but input packets stop working very quickly. We should investigate further.

Note: I (@AzureMarker) only have an old 3DS and CPP, so others will need to help debug on the New 3DS platform.

Meziu commented 5 months ago

I managed to catch some of my "flickering" readings using 3dslink's server.

Example of a bad read:

IrUserStatusInfo { recv_err_result: c8810c03, send_err_result: 0, connection_status: Connected, trying_to_connect_status: 2, connection_role: 2, machine_id: 0, unknown_field_1: 1, network_id: 26, unknown_field_2: 1, unknown_field_3: 0 }

ReceiveBufferInfo:
00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 

ReceiveBuffer:
1c 00 00 00 0a 00 00 00 00 08 80 ff 00 99 00 99 
a5 26 06 10 00 08 80 ff 00 99 a5 26 06 10 00 08 
80 ff 00 99 a5 26 06 10 

Packet count: 1
IrUserPacket { magic_number: a5, destination_network_id: 26, payload_length: 06, payload: [10, 00, 08, 80, ff, 00], checksum: 99 }

CirclePadProInputResponse {
    c_stick_x: 0x800,
    c_stick_y: 0x800,
    battery_level: 0x1f,
    zl_pressed: false,
    zr_pressed: false,
    r_pressed: false,
    unknown_field: 0x0,
}

Example of a good read:

IrUserStatusInfo { recv_err_result: c8610c0e, send_err_result: 0, connection_status: Connected, trying_to_connect_status: 2, connection_role: 2, machine_id: 0, unknown_field_1: 1, network_id: c4, unknown_field_2: 1, unknown_field_3: 0 }

ReceiveBufferInfo:
00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 

ReceiveBuffer:
00 00 00 00 0a 00 00 00 a5 c4 06 10 19 36 26 ff 
00 a3 a5 c4 06 10 08 72 2a ff 00 ca a5 c4 06 10 
81 e1 32 ff 00 b9 a5 c4 

Packet count: 1
IrUserPacket { magic_number: a5, destination_network_id: c4, payload_length: 06, payload: [10, 19, 36, 26, ff, 00], checksum: a3 }

CirclePadProInputResponse {
    c_stick_x: 0x619,
    c_stick_y: 0x263,
    battery_level: 0x1f,
    zl_pressed: false,
    zr_pressed: false,
    r_pressed: false,
    unknown_field: 0x0,
}

Other than the differences in ReceiveBuffer (which seems to constantly refresh with random data, even when the CPP reads are dead), and with network_id/destination_network_id (which seems to be set to a random value with each re-run of the example in both working and non-working runs), the only noticeable difference is the differing recv_err_result. However, even that is inconsistent, and only really decided upon when establishing a connection (I've had bad packets with c8610c0e and good ones with c8810c03, it doesn't seem to mean much).

If there are no "readable" aspects of the code that might signal incorrect reads (and we know both Old3DS and Citra can work just fine with this example), that means that the issue may not be with read data and such. Maybe a "wrong" order of execution? An error in timing synchronization?

Meziu commented 2 months ago

I love wasting time on this.

More testing, more info. Other than randomly changing the timings and other configuration values used (which yielded no observable result) I also changed my testing routine to trigger correct readings.

In particular, I am avoiding using the C-Stick to read changes (which I basically had to jam everywhere each run), and have instead resorted to press one shoulder button (ZR to be exact) so that testing runs are more consistently performed. With this simple change I noticed an improvement in the "lifetime" (it lasts for more packets/seconds, which maybe has to do with some sort of limit to the possible refreshes the whole thing can do) of the IR reads, but I've also noticed one more important characteristic of this behavior.

It seems that after my readings die out the received data is NOT put at a default "all false" state, which is what I had originally thought. Rather, the memory for the RecvBuffer is simply never updated, which means that even though the system detects a packet, nothing new is generated. I noticed this behavior since zl_pressed actually got stuck to true sometimes when the readings died, rather than falling back to false, even as the button was released.

This effectively means that not only is the system not reading input, but it is also simply not updating the buffer in any way, which made me think that this might be neither a synchronization nor a configuration issue (keeping in mind that the svc events are properly triggered, but nothing is actually written).

Frustrated, I decided to take a bit too far the words by @AzureMarker in this comment, and

be persistent

with my recv requests. As such, I am currently connecting and disconnecting from the IR device each iteration of the loop which, as violent as a solution it seems, is actually giving some pretty stable results (mainly because the connection handshake is simulated on New3DS and almost never fails).


I don't believe this is an actually viable solution since, even though my system responds well to it, the average Old3DS with a physical CPP probably can't handle this many re-tries, but it's an interesting note in this research nonetheless.