Closed AzureMarker closed 8 months ago
I honestly thought circle pad pro was automatically recognised by the system (I don’t have one, since I own a New 3DS XL with a built-in C-Stick)
I honestly thought circle pad pro was automatically recognised by the system (I don’t have one, since I own a New 3DS XL with a built-in C-Stick)
I thought the same thing, and initially started working on wrapping the ir:rst
service. However, I found that the service only supports the New 3DS, and libctru already automatically initializes/uses it in the HID implementation:
https://github.com/devkitPro/libctru/blob/e253c2c00543ab96067dc241de8dd32d72e6ec99/libctru/source/services/hid.c#L65-L67
They leave the hard work of getting the CPP input to the user :upside_down_face:
They leave the hard work of getting the CPP input to the user 🙃
I guess that makes sense. They noticed the difficulty to try to find a CPP and decided to make Hid
more “stable” and not include it (since Hid runs in all apps regardless during the initial setup). Also… not many people miss that accessory 😂
Yeah I don't blame them, especially since it seems like no one knew how to get a connection working anyway. The Circle Pad Pro is pretty niche, but I really want that second joystick for my PC controller emulation :smiling_imp:.
Really neat!
With the idea of how IR is receiving packets back and forth, is it worth possibly creating almost like an "IRServer" or "IRThread", etc that internally manages waiting for packets? This way svc doesn't have to be exposed to the user similar to the cam
service.
My thought is it would be more user safe if a mpsc receiver channel was returned from the initialization and you could use recv
instead of a more raw call to svc
.
The connect function could be moved to the IrUser
module and pass in an &Hid
, the device id (if there are more such as the pokewalker), and a button combination that could cancel the connection loop. At the different error points return a specific error and at the end return a mpsc::receiver
or something. Thoughts?
I would write up an example but I'm currently on mobile.
ctru-rs is supposed to be a thin or minimal wrapper around libctru which makes it nicer and safer to use than raw C calls. With that in mind, we don't want to introduce more overhead or complexity than necessary, so the user application (or another library) should be where that IR thread and other connection logic goes. Especially when it involves handling user input. We don't want to force developers to handle initialization of the service/CPP in a specific way like the demo (i.e. taking in Hid
and blocking execution). The suggestions are interesting and would be fine in their own crate though.
I think it's fine to expose some svc calls, especially when it's almost harmless like the one used here.
In that case, if some of the SVC functions are added then the cam
module could be simplified.
I guess the main concern would be the safety implications of the exposed functions. Ex. An invalid, null, or non-event handle being passed
My N3DS XL won't recognize the c-stick as a CPP, it seems. It tries to check the infrared instead (I used my iPhone X's FaceID to shoot infrared in the ir sensor and it returns an error! 😆).
Found this on the libctru docs. Note that the main service for New3DS HID is ir:rst, and these two service are mutually exclusive: when one is initialized and reading data from New3DS HID, the other cannot access it.
The example is initializing hid
which in turn initializes ir:rst
within libctru
. My guess as to why it isn't getting inputs from the c-stick.
Found this on the libctru docs.
Note that the main service for New3DS HID is ir:rst, and these two service are mutually exclusive: when one is initialized and reading data from New3DS HID, the other cannot access it.
The example is initializing
hid
which in turn initializesir:rst
withinlibctru
. My guess as to why it isn't getting inputs from the c-stick.
Ok, great, we just have to make a runtime check for New3DS and state the example won’t work without the actual CPP.
Something to note:
There are a handful of devices that utilize ir
including the NFC Reader, PokeWalker, and even some games directly access it (old Pokemon games syncing for local play). I'm curious how ir
is utilized to handle the different packets since everything past hardware probably doesn't have a hardcoded device ID.
Found this on the libctru docs.
Note that the main service for New3DS HID is ir:rst, and these two service are mutually exclusive: when one is initialized and reading data from New3DS HID, the other cannot access it.
The example is initializing
hid
which in turn initializesir:rst
withinlibctru
. My guess as to why it isn't getting inputs from the c-stick.
Maybe if the ir:USER service gets initialized before HID it will work. I don't have a New 3DS to test with (closest is Citra), but I'll try pushing that change to the example.
@AzureMarker I understand if you don't have time to work on this, but this PR has been stale for a while. Do you intend on working on this? I would take care of it myself if I could, but I don't own a CirclePadPro.
Yeah I definitely plan on merging this! I've just been busy with work and life recently 🙃. Thanks for keeping up the momentum!
Hi! I work on https://github.com/wheremyfoodat/Panda3DS and have been having some trouble with CPP emulation. Trying to set up Rust and this fork of ctru-rs has been giving me some headaches, so is there perhaps any simple CPP test ROM you have using this? Preferably something that does output via svcOutputDebugString too.
Thanks for your work! It's nice to see someone making CPP homebrew a thing. Also ctru-rs is very cool too.
Hi! I work on https://github.com/wheremyfoodat/Panda3DS and have been having some trouble with CPP emulation.
I'm happy to see you here! I've had more than a look at your project so I'm glad to see our work has sparked your interest.
Trying to set up Rust and this fork of ctru-rs has been giving me some headaches, so is there perhaps any simple CPP test ROM you have using this? Preferably something that does output via svcOutputDebugString too.
Sadly this PR has stagnated for quite a while and isn't up-to-date with our current build systems and integration. I would have liked to finish working on this myself, however I'm not in possession of an Old3DS with a CirclePadPro, so I can't test and develop this module.
Luckily, the fixes needed to get the example working are easy for who's used to the Rust toolchain, so I quickly built it for you.
Here's a google drive folder with the .3dsx files.
The provided examples do not use svcOutputDebugString
, rather printing to the stdout
.
Again, I'm incredibly sorry as this is the most I can do. If there are further issues, I can guide you in the process of building the examples yourself (which is already very well documented in our wiki and for which you'll need cargo-3ds).
Thank you so so much ❤❤❤
Sadly this PR has stagnated for quite a while and isn't up-to-date with our current build systems and integration. I would have liked to finish working on this myself, however I'm not in possession of an Old3DS with a CirclePadPro, so I can't test and develop this module.
I'm really looking forward to getting time to work on this and merge it, eventually! It definitely will be merged by end of this year. I'll have more time over winter vacation :). Unfortunately lots of life stuff is higher priority than having fun with side projects :/.
Took the freedom of merging the master
branch into this. At least this PR should result more usable and up-to-date than before, even though I still can't really work on it.
Sadly this PR has stagnated for quite a while and isn't up-to-date with our current build systems and integration. I would have liked to finish working on this myself, however I'm not in possession of an Old3DS with a CirclePadPro, so I can't test and develop this module.
I'm really looking forward to getting time to work on this and merge it, eventually! It definitely will be merged by end of this year. I'll have more time over winter vacation :). Unfortunately lots of life stuff is higher priority than having fun with side projects :/.
:D
Did some work on the PR, but it needs a little more still. The double buffering change seems to have actually increased the stuttering/flashing, so not sure what I'm doing wrong there. Edit: fixed!
I'll also need to open a PR on test-runner since it depends on Console, and I had to change the signature to make double buffering work.
Wow, this is very fortuitous timing - I'm putting together a project that uses citro3d, and I've done some of the work to get bindings for the texture code in nice Rust. The last thing it needs in order to be a good demonstration for using the bindings to render a proper model is C-Stick support for moving the camera around; I've made do with the gyroscope so far, but it's not ideal.
@Jhynjhiruu If you have a New 3DS you can use the ir:rrst service which is initialized by the hid component in libctru automatically. It will give the New 3DS c-stick position (roughly, just the main direction): https://libctru.devkitpro.org/hid_8h.html#a2f80701c36e79c0640d91c788feee0b3a557de4e103c1e2c316b9e0962c221ab6
@Jhynjhiruu If you have a New 3DS you can use the ir:rrst service which is initialized by the hid component in libctru automatically. It will give the New 3DS c-stick position (roughly, just the main direction): https://libctru.devkitpro.org/hid_8h.html#a2f80701c36e79c0640d91c788feee0b3a557de4e103c1e2c316b9e0962c221ab6
I do have a New 3DS, but I also have an Old 3DS and a Circle Pad Pro, and I don't want to limit my code to just working on one of them. Plus, not having analogue controls for the camera wouldn't be so fun.
Nice! If you find any issues with this code and your Circle Pad Pro or New 3DS just let me know.
Nice! If you find any issues with this code and your Circle Pad Pro or New 3DS just let me know.
Should I implement it using this branch and report back?
Yeah that should work
Yeah that should work
Alright, I'll give it a go
Interesting; I appear to have written code that works completely fine on my real Circle Pad Pro but doesn't support my New 3DS. Odd.
I'm not entirely sure what's going wrong here. IrUser is initialised before Hid, and yet it still doesn't receive any data from the C-Stick. My code also seems to be running very slowly, so it almost seems like one of the delays is wrong.
I'm not entirely sure what's going wrong here. IrUser is initialised before Hid, and yet it still doesn't receive any data from the C-Stick. My code also seems to be running very slowly, so it almost seems like one of the delays is wrong.
Unless there is another source, 3dbrew mentions that only one can read from it when both are initialized but nothing about the order. ir:rst
may override completely or whichever is initialized last takes full control. This may have to be tested unless already done. (Unless you aren't using ir:rst
at all then this can be ignored!)
I'm not entirely sure what's going wrong here. IrUser is initialised before Hid, and yet it still doesn't receive any data from the C-Stick. My code also seems to be running very slowly, so it almost seems like one of the delays is wrong.
Unless there is another source, 3dbrew mentions that only one can read from it when both are initialized but nothing about the order.
ir:rst
may override completely or whichever is initialized last takes full control. This may have to be tested unless already done. (Unless you aren't usingir:rst
at all then this can be ignored!)
I don't... think I'm using ir:rst
?
Either way, the example in this PR (ir-user-circle-pad-pro.rs
) doesn't work on my New 3DS. The input response is the same as in my project:
CirclePadProInputResponse {
c_stick_x: 0x800,
c_stick_y: 0x800,
battery_level: 0x1f,
zl_pressed: false,
zr_pressed: false,
r_pressed: false,
unknown_field: 0x0,
}
The ReceiveBuffer
seems to be doing something, it's just not what it's supposed to be. Comparing to my real Circle Pad Pro, the ReceiveBuffer
seems to update much more slowly, and the data seems to be f3 a5 20 06 10 00 08 80 ff 00
on repeat.
I notice that the real CPP's IrUserStatusInfo.connection_role
is 1, whereas on my New 3DS it's 2. Otherwise, all of the info appears to be identical or equivalent. I'm a bit stumped.
According to a comment in the example:
// Initialize HID after ir:USER because libctru also initializes ir:rst,
// which is mutually exclusive with ir:USER. Initializing HID before ir:USER
// on New 3DS causes ir:USER to not work.
I haven't tested the example yet on N3DS myself to see if there are any issues. Seems like complications will be particular to the N3DS though so playing around with initialization ordering or other small things may offer ideas since this doesn't seem to be too well documented.
According to a comment in the example:
// Initialize HID after ir:USER because libctru also initializes ir:rst, // which is mutually exclusive with ir:USER. Initializing HID before ir:USER // on New 3DS causes ir:USER to not work.
I haven't tested the example yet on N3DS myself to see if there are any issues. Seems like complications will be particular to the N3DS though so playing around with initialization ordering or other small things may offer ideas since this doesn't seem to be too well documented.
My code does initialise HID after ir:USER, I think, and the example definitely does. Neither works on my console.
Have you tried not initializing HID via libctru at all?
Have you tried not initializing HID via libctru at all?
No, I haven't, since then I wouldn't have an easy way to exit the app. I'll try it though.
Have you tried not initializing HID via libctru at all?
No, I haven't, since then I wouldn't have an easy way to exit the app. I'll try it though.
Nope, didn't work. Still doesn't work.
Have you tried not initializing HID via libctru at all?
Nope, didn't work. Still doesn't work.
Friendly reminder that libctru
initialises a handle to hid
by default here.
You may have to close Hid or do something else before being able to use the service properly.
Have you tried not initializing HID via libctru at all?
Nope, didn't work. Still doesn't work.
Friendly reminder that
libctru
initialises a handle tohid
by default here.You may have to close Hid or do something else before being able to use the service properly.
I had totally forgotten about that!
However, disabling this (by defining a new #[no_mangle] unsafe extern "C" fn __appInit()
, which calls srvInit()
and nothing else) still doesn't fix the issue. Eh??
Have you tried not initializing HID via libctru at all?
Nope, didn't work. Still doesn't work.
Friendly reminder that
libctru
initialises a handle tohid
by default here. You may have to close Hid or do something else before being able to use the service properly.I had totally forgotten about that! However, disabling this (by defining a new
#[no_mangle] unsafe extern "C" fn __appInit()
, which callssrvInit()
and nothing else) still doesn't fix the issue. Eh??
Here, found it. You need to re-implement this weak function https://github.com/devkitPro/libctru/blob/d0936b879bd2088cae61b69707fe70cb66ede651/libctru/source/services/hid.c#L35. If you set the return to false any use of hid
will not use iirst
.
Have you tried not initializing HID via libctru at all?
Nope, didn't work. Still doesn't work.
Friendly reminder that
libctru
initialises a handle tohid
by default here. You may have to close Hid or do something else before being able to use the service properly.I had totally forgotten about that! However, disabling this (by defining a new
#[no_mangle] unsafe extern "C" fn __appInit()
, which callssrvInit()
and nothing else) still doesn't fix the issue. Eh??Here, found it. You need to re-implement this weak function https://github.com/devkitPro/libctru/blob/d0936b879bd2088cae61b69707fe70cb66ede651/libctru/source/services/hid.c#L35. If you set the return to false any use of
hid
will not useiirst
.
That still doesn't fix the issue. At this point I'm convinced there's a bug of some kind.
I think it might be worth adding a default __appInit()
function somewhere in ctru-rs
that does what I've just done, since we generally want to get our own handles to Hid
etc. and there's not much point initialising more than we need.
I obtained the following log from Panda3DS, running Majora's Mask 3D, logging only srv::GetServiceHandle
and any calls to HID and ir:user functions:
...
srv::getServiceHandle (Service: hid:USER, nameLength: 8, flags: 0)
HID::GetIPCHandles
HID::EnableAccelerometer
HID::EnableGyroscopeLow
HID::GetGyroscopeLowCalibrateParam
HID::GetGyroscopeLowRawToDpsCoefficient
HID::GetGyroscopeLowCalibrateParam
HID::GetGyroscopeLowRawToDpsCoefficient
...
Later on, when loading a save and the game attempts to initialize CPP:
srv::getServiceHandle (Service: ir:USER, nameLength: 7, flags: 0)
IR:USER: InitializeIrnopShared (shared mem size = 00001000, sharedMemHandle = 2C)
IR:USER: GetConnectionStatusEvent
IR:USER: RequireConnection (device: 1)
So initializing HID before ir:USER seems to be fine, but it does indeed seem that libctru initializing ir:rst
during hidInit
on New 3DS might be problematic.
I obtained the following log from Panda3DS, running Majora's Mask 3D, logging only
srv::GetServiceHandle
and any calls to HID and ir:user functions:... srv::getServiceHandle (Service: hid:USER, nameLength: 8, flags: 0) HID::GetIPCHandles HID::EnableAccelerometer HID::EnableGyroscopeLow HID::GetGyroscopeLowCalibrateParam HID::GetGyroscopeLowRawToDpsCoefficient HID::GetGyroscopeLowCalibrateParam HID::GetGyroscopeLowRawToDpsCoefficient ... Later on, when loading a save and the game attempts to initialize CPP: srv::getServiceHandle (Service: ir:USER, nameLength: 7, flags: 0) IR:USER: InitializeIrnopShared (shared mem size = 00001000, sharedMemHandle = 2C) IR:USER: GetConnectionStatusEvent IR:USER: RequireConnection (device: 1)
So initializing HID before ir:USER seems to be fine, but it does indeed seem that libctru initializing
ir:rst
duringhidInit
on New 3DS might be problematic.
Don't suppose you can determine the parameters MM uses for initialising the CPP? Timings etc.
Just overriding hidShouldUseIrrst()
to return false
doesn't change anything, irritatingly.
I have a half-decompilation of the Majora's Mask IR code on my computer, though it's rather complicated. "Timing" is done via the IR events (eg once the game calls IR::RequireConnection to pair with the CPP, it uses WaitSync1 on the IR connection event. This way it sleeps until the connection attempts has ended and IR shared memory is properly initialized. After that, I think it uses the send/recv events for synchronizing package transfers)
I have a half-decompilation of the Majora's Mask IR code on my computer, though it's rather complicated. "Timing" is done via the IR events (eg once the game calls IR::RequireConnection to pair with the CPP, it uses WaitSync1 on the IR connection event. This way it sleeps until the connection attempts has ended and IR shared memory is properly initialized. After that, I think it uses the send/recv events for synchronizing package transfers)
@AzureMarker Any chance of trying something like this out?
I'm pretty sure that's how it already works. I based my implementation on Majora's Mask (even got into quite a few gdb sessions).
I'm pretty sure that's how it already works. I based my implementation on Majora's Mask (even got into quite a few gdb sessions).
Welp. Is there anything else I can do? Info to capture?
Is there any way to know if the ir:rst service is in use? Maybe something else is initializing it. Or maybe calling the close function could help kill whatever active session there might be? I don't have a New 3DS to test with.
Is there any way to know if the ir:rst service is in use? Maybe something else is initializing it. Or maybe calling the close function could help kill whatever active session there might be? I don't have a New 3DS to test with.
The handy variable that says whether ir:rst
is in use is unfortunately static, so I can't read it. However, the handles and the pointer to shared memory aren't static, so I can read those just fine, and they're all 0s, which strongly implies that ir:rst
isn't in use.
@ian-h-chamberlain @Meziu I think all the comments have been addressed, so let me know if we can merge the PR or if you have more comments. Thanks for taking a look! It's almost been a year since I opened the PR :sweat_smile:.
@ian-h-chamberlain @Meziu I think all the comments have been addressed, so let me know if we can merge the PR or if you have more comments. Thanks for taking a look! It's almost been a year since I opened the PR 😅.
Well, it would be nice to get the example code working on New 3DS first...
I think this is the first time anyone from homebrew has connected to the Circle Pad Pro (or they never talked about it online). It took a while, but I figured out you just have to be persistent during the connection phase (do immediate retries).
This PR adds a service wrapper for the ir:USER service, and a example/demo of using it to connect to the Circle Pad Pro (the main usage of the service as far as I can tell).
Note that the New 3DS internally emulates the Circle Pad Pro, so using
ir:USER
on a New 3DS system should give you the c-stick inputs. Their:rst
service is slightly different, where it only works on the New 3DS (doesn't give CPP inputs). Most retail 3DS games useir:USER
to be compatible with both old and new 3DS systems.Reference links:
BTW if you're curious what I want to use this for: https://github.com/AzureMarker/n3ds-controller
Demo:
Note: the screen no longer flickers after enabling double buffering.
https://user-images.githubusercontent.com/4417660/212457545-5f12e5b4-85f3-4faa-9552-74ce43bacce8.mp4