Luigi's notes about WiFi
Current Wifi state
Wifi is WIP and highly experimental, keep it in mind
- Ad-hoc: Nintendo multiplayer games fail to connect together. At one point the host sends data frames, the client receives them right but seems to not care about them or throw them out for some reason. Quite possibly a timing problem.
- SoftAP, WFC: doesn't work. Seems to be due to timing issues.
- SoftAP, homebrew: I was fckin wrong. With some work, homebrews based on the dswifi lib can now communicate with the internet. For example you can use ClIRC to chat on #desmume (irc.freenode.net, port 6667).
Undocumented hardware stuff
Based on observations on real hardware, may be wrong or incomplete tho.
- setting bit0 of W_US_COMPARE0 keeps W_BEACONCOUNT1 from triggering IRQ14 until W_US_COUNT matches W_US_COMPARE
- (emulating this effect causes even worse results than what's done right now in desmume)
- does setting W_MACADDR to FF:FF:FF:FF:FF:FF let one see all the wifi traffic, like monitor mode on a PC wifi card?
Spying on the traffic
- Mario sends his beacon
- Luigi sends a standard auth frame
- Mario replies with another standard auth frame
- Luigi sends a standard assoc request
- Mario replies with a standard assoc response
- Mario sends this data frame: (data+CF-Poll)
28 02 d8 02 03 09 bf 00 00 00 MM MM MM MM MM MM
MM MM MM MM MM MM SS SS de 01 02 00 00 80
- Luigi replies with this ack, and nothing else for now:
58 11 fe 01 MM MM MM MM MM MM LL LL LL LL LL LL
03 09 bf 00 00 10 SS SS
(where MMMMMMMMMMMM is Mario's MAC, LLLLLLLLLLLL is Luigi's MAC, and SSSS is the sender's sequence number)
That ack is different from the 'standard' ack, which is regularly sent during the auth/assoc process:
d4 00 00 00 XX XX XX XX XX XX
(where XXXXXXXXXXXX is the recipient's MAC)
In fact, the 'standard' ack is a control frame, while the mysterious one is a CF-Ack frame (data frame, subtype 5).
Possibly CF-Ack frames are sent automatically in response to Data+CF-Poll frames? Nope. Seeing the traffic that follows, it can't be.
Back into desmume
Observing the packets that are exchanged, a weird glitch can be observed on Mario's side: the auth and assoc frames sent to Luigi have their sequence control set to zero. All other packets are fine. All of Luigi's packets are fine too.
This is weird, because those packets SHOULD have sequence controls. None of the 'manual sequence control' bits are set when the packets are being sent, so wtf?
Actually, the 'TX sequence control' value is being overwritten with 0 right between the TX start order and TX start IRQ, at 0x037FE658.
The packet data is being copied before the TX start order, but for whatever reason, it is being copied again during the preamble.
Unable to find the reason to this so far, I tried adding a simple hack to fix the sequence number of affected packets before sending them. This made things even worse: Luigi doesn't even try to set TXBUF_REPLY in order to send a reply.
This is like the W_USCOUNTER.Bit0 thing. Emulating more correctly gives worse results. All that is telling something. There definitely is something wrong inside the wifi emulation. But what? Possibly the RFPINS/RFSTATUS thing... possibly something else... argh...
The func that tries to copy the packet too much is at 0x037FB12C.
Actually, I have no idea if the overcopying bug comes from Nintendo's software or desmume's wifi implementation... but its main consequence can be avoided in a non-hackish way. According to GBAtek, pre-transmit packet adjustments, including setting the sequence number, should be done when the TXSTART IRQ is triggered, not before.
Also, when the packets' sequence numbers are correct, Luigi, after having successfully associated with Mario, will set its AID_LOW and AID_HIGH registers to 1, which seems right to me. About TXBUF_REPLY not getting set, well... perhaps the RXSTART handler is actually supposed to set it to zero when the packet doesn't need to be replied to. That would make sense, since it only analyzes the header of the received packet, what about the data coming next? Supposedly, the packet's body is analyzed later, and a reply should be sent...
At 0x023A04F8, there is a function that writes a packet into the wifi RAM, then sets TXBUF_REPLY as to send it. However, it doesn't latch the register, and,
oddly, it resets AID_LOW/HIGH to zero it messes with AID_LOW.
This function has no duplicate in the 0x03XXXXXX region. It also never gets called. TODO: Find potential calls to it, either direct or indirect. No direct branches, no pointers... the function is dead code! Either that, or Nintendo used some weirdass calling convention I'm not aware about. In either case, it is really weird. This function is the only one that can do something interesting with TXBUF_REPLY! Come on! It has to be called from somewhere!
Experimentation on hardware confirms that the function at 0x023A04F8 is used in replying to Mario. Nopping out the opcode that writes to 0x04808094 in the function, causes multiplayer to break kinda like it's breaking in desmume.
Yet another hack reveals that the function in question is called from 0x037F8DBC. Though, this function isn't being called for the first Luigi reply... but it is still interesting to figure out how the chain works.
Weirdass calling convention going on. At 0x027F7FEC there is a jump table, each entry is 8 bytes: 4 unknown bytes and the address of the function to call. R3 is used as an index into the table. In our case R3=1.
More h4xx0ring shows that the function being used is at 0x027F0114 and not 0x023A04F8. NSMB's binary unpacking methods are weird. The parent function is at 0x037F8B4C.
This function I've been looking at isn't used when sending Luigi's first data frame. Oh, and the other function that messes with TXBUF_REPLY (0x0239BF80/whatever, called from the RXSTART handler) isn't used either. Probably, the packet in question is sent via a regular TX slot, for whatever reason.
That function is also being called in DeSmuME, right after Luigi receives its association response. A packet template is written at 0x04804000 and TXBUF_REPLY1 is set to 0x8000. However, that value is never latched into TXBUF_REPLY2. Probably the manual ACK packet must have been sent before easy automated communication starts.
TODO: find out how Luigi sends his first data packet
I have no idea where the fuck that 0x0158 packet I believed to be the first frame, comes from. My attempts at making the game not send that packet failed so far. I made a second real-life capture, and no 0x0158 packet. The regular 0x0118 reply is sent instead. Where the fuck does that crap come from?
Unemulated/incomplete features that could break it
- RFSTATUS/RFPINS (kind of hacked atm)
- RXSTAT registers (not emulated at all; perhaps some of them are used for sensing multiplayer cmd/reply packets? -- testing RXSTAT emulation: doesn't seem to change much)
- USCOUNTER bit0 (current method incorrect, yet works better than anything else)
- TXBUF_TIM (possibly the host fills sent beacons' TIMs automatically. The beacon NSMB generates has the TIM set to 05 05 00 02 00 00 00, however it is 05 05 01 02 00 00 00 in real life. Test of this doesn't change much either.)
* REPLY TX slot (but we aren't to this stage yet. For now Luigi never tried to write anything other than zero into it) MPREPLY is implemented
- IRQ12 and double IRQ7 when sending from CMD TX slot (how does the 'double IRQ7' thing work? for now only IRQ12 and one IRQ7 are triggered)
- TXSEQNO increment when sending from CMD TX slot (GBAtek says it's incremented by two, however real-life capture shows that it is incremented by one)
- When receiving a packet, its duration field should be set to something, but what? Also, does Nintendo's software care about this?
Envisaged methods to figure out the wifi shit
- Reverse-engineering NSMB's ARM7 binary: will most likely be a long, time-consuming, painful process, and perhaps it will not help that much (part of the wifi is done by the ARM9 binary).
- Modifying NSMB so that Luigi dumps its wifi registers when it receives the first data frame from Mario: may be helpful but may as well not be helpful
- Building a fake NSMB Luigi client: not that easy to do. Hardware quirks and/or timing issues prevent it from working. Note: a proper low-level wifi library is needed. dswifi is too high-level for our needs, and some of its functionality may be interfering with the multiplayer comm.
Decapping the wifi chip and reverse-engineering thatmeans for that lacking
Things to do
- Run a broken NSMB ROM as Luigi (broken means MP reply functionality broken, for example, all occurences of 0x04808094 replaced by 0xFFFFFFF0), a normal ROM as Mario, and sniff the wifi traffic. See how many 0x0228 packets Mario will send. On desmume, Mario is literally flooding, I get the feeling that it's not correct, the timing is really too tight. Though, that doesn't come from the CMDCOUNT interval, experimentation on real hardware confirmed that the interval is 10µs as GBAtek says.
- First attempt at that is, uh... Mario does kinda flood, though perhaps not as much as on desmume, I haven't counted. But if it fails to autoreply, Luigi will send a 0x0158 packet. That doesn't happen on desmume. Really weird.
- Examine the packet transmission procedure closely. Find out when the transmission starts (a line on GBAtek suggests that it should start upon the next IRQ14 and not immediately, this has to be confirmed), which IRQs get triggered, which values RFSTATUS and RFPINS take and when, etc...
- Do the above for the receiving procedure, too
- Find out how the MP reply thing works. When the reply is sent and upon which conditions.
- Find out what RXFILTER does and how to use it. Changing its value has no effect on beacons, perhaps it has an effect on other packets? And do the same for RXFILTER2.
Notes for posterity
MPCMD transfer sequence:
- transfer start: IRQ7
- transfer done: wait for N microseconds, N = 112 + ((10 + WIFI_IOREG(0xC4)) * number_of_slaves) (wait for slave replies, slaves know when to reply somehow)
- wait done, send ACK: IRQ7 (whether slaves get to see the ACK or it's handled by hardware is unknown, but the corresponding IRQ7 happens)
- ACK transfer done, IRQ12 (followed by IRQ1 and other typical transfer-end events)
Observed RFSTATUS/RFPINS values: (take with a grain of salt)
- when sending a packet, after IRQ7: RFSTATUS/RFPINS = 3/0x0046
- when done transferring on MPCMD slot (prior to reply-wait): 5/0x0084
- when done transferring on any other slot: dunno? I guess it should change
- when sending MPCMD ack: 8/0x0046
- when MPCMD frame done, after IRQ12: 9/0x00C6 (not correct)
- when receiving a packet, after IRQ6: 6/0x0087
- when done receiving a packet, after IRQ0: 9/0x00C6